Version 0.2.9.1
Cherrypick the following changes from bleeding_edge into trunk:
16056 16059 16060 16063 16065 16066 16067 16068 16073
git-svn-id: http://dart.googlecode.com/svn/trunk@16076 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index fb99370..d1dee9b 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -7351,17 +7351,56 @@
}
}
-/// @domName Element
+/**
+ * An abstract class, which all HTML elements extend.
+ */
abstract class Element extends Node implements ElementTraversal native "*Element" {
+ /**
+ * Creates an HTML element from a valid fragment of HTML.
+ *
+ * The [html] fragment must represent valid HTML with a single element root,
+ * which will be parsed and returned.
+ *
+ * Important: the contents of [html] should not contain any user-supplied
+ * data. Without strict data validation it is impossible to prevent script
+ * injection exploits.
+ *
+ * It is instead recommended that elements be constructed via [Element.tag]
+ * and text be added via [text].
+ *
+ * var element = new Element.html('<div class="foo">content</div>');
+ */
factory Element.html(String html) =>
_ElementFactoryProvider.createElement_html(html);
+
+ /**
+ * Creates the HTML element specified by the tag name.
+ *
+ * This is similar to [Document.createElement].
+ * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
+ * this will create an [UnknownElement].
+ *
+ * var divElement = new Element.tag('div');
+ * print(divElement is DivElement); // 'true'
+ * var myElement = new Element.tag('unknownTag');
+ * print(myElement is UnknownElement); // 'true'
+ *
+ * For standard elements it is more preferable to use the type constructors:
+ * var element = new DivElement();
+ */
factory Element.tag(String tag) =>
_ElementFactoryProvider.createElement_tag(tag);
/**
- * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute,
- * Element.removeAttribute
+ * All attributes on this element.
+ *
+ * Any modifications to the attribute map will automatically be applied to
+ * this element.
+ *
+ * This only includes attributes which are not in a namespace
+ * (such as 'xlink:href'), additional attributes can be accessed via
+ * [getNamespacedAttributes].
*/
Map<String, String> get attributes => new _ElementAttributeMap(this);
@@ -7395,8 +7434,16 @@
List<Element> get elements => this.children;
/**
- * @domName childElementCount, firstElementChild, lastElementChild,
- * children, Node.nodes.add
+ * List of the direct children of this element.
+ *
+ * This collection can be used to add and remove elements from the document.
+ *
+ * var item = new DivElement();
+ * item.text = 'Something';
+ * document.body.children.add(item) // Item is now displayed on the page.
+ * for (var element in document.body.children) {
+ * element.style.background = 'red'; // Turns every child of body red.
+ * }
*/
List<Element> get children => new _ChildrenElementList._wrap(this);
@@ -7408,12 +7455,46 @@
children.addAll(copy);
}
+ /**
+ * Finds the first descendant element of this element that matches the
+ * specified group of selectors.
+ *
+ * [selectors] should be a string using CSS selector syntax.
+ *
+ * // Gets the first descendant with the class 'classname'
+ * var element = element.query('.className');
+ * // Gets the element with id 'id'
+ * var element = element.query('#id');
+ * // Gets the first descendant [ImageElement]
+ * var img = element.query('img');
+ *
+ * See also:
+ *
+ * * [CSS Selectors](http://docs.webplatform.org/wiki/css/selectors)
+ */
Element query(String selectors) => $dom_querySelector(selectors);
+ /**
+ * Finds all descendent elements of this element that match the specified
+ * group of selectors.
+ *
+ * [selectors] should be a string using CSS selector syntax.
+ *
+ * var items = element.query('.itemClassName');
+ */
List<Element> queryAll(String selectors) =>
new _FrozenElementList._wrap($dom_querySelectorAll(selectors));
- /** @domName className, classList */
+ /**
+ * The set of CSS classes applied to this element.
+ *
+ * This set makes it easy to add, remove or toggle the classes applied to
+ * this element.
+ *
+ * element.classes.add('selected');
+ * element.classes.toggle('isOnline');
+ * element.classes.remove('selected');
+ */
CssClassSet get classes => new _ElementCssClassSet(this);
void set classes(Collection<String> value) {
@@ -7422,6 +7503,29 @@
classSet.addAll(value);
}
+ /**
+ * Allows access to all custom data attributes (data-*) set on this element.
+ *
+ * The keys for the map must follow these rules:
+ *
+ * * The name must not begin with 'xml'.
+ * * The name cannot contain a semi-colon (';').
+ * * The name cannot contain any capital letters.
+ *
+ * Any keys from markup will be converted to camel-cased keys in the map.
+ *
+ * For example, HTML specified as:
+ *
+ * <div data-my-random-value='value'></div>
+ *
+ * Would be accessed in Dart as:
+ *
+ * var value = element.dataAttributes['myRandomValue'];
+ *
+ * See also:
+ *
+ * * [Custom data attributes](http://www.w3.org/TR/html5/global-attributes.html#custom-data-attribute)
+ */
Map<String, String> get dataAttributes =>
new _DataAttributeMap(attributes);
@@ -7435,19 +7539,39 @@
/**
* Gets a map for manipulating the attributes of a particular namespace.
+ *
* This is primarily useful for SVG attributes such as xref:link.
*/
Map<String, String> getNamespacedAttributes(String namespace) {
return new _NamespacedAttributeMap(this, namespace);
}
- /** @domName Window.getComputedStyle */
+ /**
+ * The set of all CSS values applied to this element, including inherited
+ * and default values.
+ *
+ * The computedStyle contains values that are inherited from other
+ * sources, such as parent elements or stylesheets. This differs from the
+ * [style] property, which contains only the values specified directly on this
+ * element.
+ *
+ * See also:
+ *
+ * * [CSS Inheritance and Cascade](http://docs.webplatform.org/wiki/tutorials/inheritance_and_cascade)
+ */
Future<CssStyleDeclaration> get computedStyle {
// TODO(jacobr): last param should be null, see b/5045788
return getComputedStyle('');
}
- /** @domName Window.getComputedStyle */
+ /**
+ * Returns the computed styles for pseudo-elements such as `::after`,
+ * `::before`, `::marker`, `::line-marker`.
+ *
+ * See also:
+ *
+ * * [Pseudo-elements](http://docs.webplatform.org/wiki/css/selectors/pseudo-elements)
+ */
Future<CssStyleDeclaration> getComputedStyle(String pseudoElement) {
return _createMeasurementFuture(
() => window.$dom_getComputedStyle(this, pseudoElement),
@@ -7455,14 +7579,15 @@
}
/**
- * Adds the specified element to after the last child of this.
+ * Adds the specified element to after the last child of this element.
*/
void append(Element e) {
this.children.add(e);
}
/**
- * Adds the specified text as a text node after the last child of this.
+ * Adds the specified text as a text node after the last child of this
+ * element.
*/
void appendText(String text) {
this.insertAdjacentText('beforeend', text);
@@ -7470,7 +7595,7 @@
/**
* Parses the specified text as HTML and adds the resulting node after the
- * last child of this.
+ * last child of this element.
*/
void appendHtml(String text) {
this.insertAdjacentHtml('beforeend', text);
@@ -7492,7 +7617,16 @@
// TODO(vsm): Implement noSuchMethod or similar for dart2js.
- /** @domName Element.insertAdjacentText */
+ /**
+ * Creates a text node and inserts it into the DOM at the specified location.
+ *
+ * To see the possible values for [where], read the doc for
+ * [insertAdjacentHtml].
+ *
+ * See also:
+ *
+ * * [insertAdjacentHtml]
+ */
void insertAdjacentText(String where, String text) {
if (JS('bool', '!!#.insertAdjacentText', this)) {
_insertAdjacentText(where, text);
@@ -7504,7 +7638,28 @@
@JSName('insertAdjacentText')
void _insertAdjacentText(String where, String text) native;
- /** @domName Element.insertAdjacentHTML */
+ /**
+ * Parses text as an HTML fragment and inserts it into the DOM at the
+ * specified location.
+ *
+ * The [where] parameter indicates where to insert the HTML fragment:
+ *
+ * * 'beforeBegin': Immediately before this element.
+ * * 'afterBegin': As the first child of this element.
+ * * 'beforeEnd': As the last child of this element.
+ * * 'afterEnd': Immediately after this element.
+ *
+ * var html = '<div class="something">content</div>';
+ * // Inserts as the first child
+ * document.body.insertAdjacentHtml('afterBegin', html);
+ * var createdElement = document.body.children[0];
+ * print(createdElement.classes[0]); // Prints 'something'
+ *
+ * See also:
+ *
+ * * [insertAdjacentText]
+ * * [insertAdjacentElement]
+ */
void insertAdjacentHtml(String where, String text) {
if (JS('bool', '!!#.insertAdjacentHtml', this)) {
_insertAdjacentHtml(where, text);
@@ -7516,7 +7671,16 @@
@JSName('insertAdjacentHTML')
void _insertAdjacentHTML(String where, String text) native;
- /** @domName Element.insertAdjacentHTML */
+ /**
+ * Inserts [element] into the DOM at the specified location.
+ *
+ * To see the possible values for [where], read the doc for
+ * [insertAdjacentHtml].
+ *
+ * See also:
+ *
+ * * [insertAdjacentHtml]
+ */
Element insertAdjacentElement(String where, Element element) {
if (JS('bool', '!!#.insertAdjacentElement', this)) {
_insertAdjacentElement(where, element);
diff --git a/sdk/lib/html/dartium/html_dartium.dart b/sdk/lib/html/dartium/html_dartium.dart
index 9f69c01..9396f1e 100644
--- a/sdk/lib/html/dartium/html_dartium.dart
+++ b/sdk/lib/html/dartium/html_dartium.dart
@@ -8826,17 +8826,56 @@
}
}
-/// @domName Element
+/**
+ * An abstract class, which all HTML elements extend.
+ */
abstract class Element extends Node implements ElementTraversal {
+ /**
+ * Creates an HTML element from a valid fragment of HTML.
+ *
+ * The [html] fragment must represent valid HTML with a single element root,
+ * which will be parsed and returned.
+ *
+ * Important: the contents of [html] should not contain any user-supplied
+ * data. Without strict data validation it is impossible to prevent script
+ * injection exploits.
+ *
+ * It is instead recommended that elements be constructed via [Element.tag]
+ * and text be added via [text].
+ *
+ * var element = new Element.html('<div class="foo">content</div>');
+ */
factory Element.html(String html) =>
_ElementFactoryProvider.createElement_html(html);
+
+ /**
+ * Creates the HTML element specified by the tag name.
+ *
+ * This is similar to [Document.createElement].
+ * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
+ * this will create an [UnknownElement].
+ *
+ * var divElement = new Element.tag('div');
+ * print(divElement is DivElement); // 'true'
+ * var myElement = new Element.tag('unknownTag');
+ * print(myElement is UnknownElement); // 'true'
+ *
+ * For standard elements it is more preferable to use the type constructors:
+ * var element = new DivElement();
+ */
factory Element.tag(String tag) =>
_ElementFactoryProvider.createElement_tag(tag);
/**
- * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute,
- * Element.removeAttribute
+ * All attributes on this element.
+ *
+ * Any modifications to the attribute map will automatically be applied to
+ * this element.
+ *
+ * This only includes attributes which are not in a namespace
+ * (such as 'xlink:href'), additional attributes can be accessed via
+ * [getNamespacedAttributes].
*/
Map<String, String> get attributes => new _ElementAttributeMap(this);
@@ -8870,8 +8909,16 @@
List<Element> get elements => this.children;
/**
- * @domName childElementCount, firstElementChild, lastElementChild,
- * children, Node.nodes.add
+ * List of the direct children of this element.
+ *
+ * This collection can be used to add and remove elements from the document.
+ *
+ * var item = new DivElement();
+ * item.text = 'Something';
+ * document.body.children.add(item) // Item is now displayed on the page.
+ * for (var element in document.body.children) {
+ * element.style.background = 'red'; // Turns every child of body red.
+ * }
*/
List<Element> get children => new _ChildrenElementList._wrap(this);
@@ -8883,12 +8930,46 @@
children.addAll(copy);
}
+ /**
+ * Finds the first descendant element of this element that matches the
+ * specified group of selectors.
+ *
+ * [selectors] should be a string using CSS selector syntax.
+ *
+ * // Gets the first descendant with the class 'classname'
+ * var element = element.query('.className');
+ * // Gets the element with id 'id'
+ * var element = element.query('#id');
+ * // Gets the first descendant [ImageElement]
+ * var img = element.query('img');
+ *
+ * See also:
+ *
+ * * [CSS Selectors](http://docs.webplatform.org/wiki/css/selectors)
+ */
Element query(String selectors) => $dom_querySelector(selectors);
+ /**
+ * Finds all descendent elements of this element that match the specified
+ * group of selectors.
+ *
+ * [selectors] should be a string using CSS selector syntax.
+ *
+ * var items = element.query('.itemClassName');
+ */
List<Element> queryAll(String selectors) =>
new _FrozenElementList._wrap($dom_querySelectorAll(selectors));
- /** @domName className, classList */
+ /**
+ * The set of CSS classes applied to this element.
+ *
+ * This set makes it easy to add, remove or toggle the classes applied to
+ * this element.
+ *
+ * element.classes.add('selected');
+ * element.classes.toggle('isOnline');
+ * element.classes.remove('selected');
+ */
CssClassSet get classes => new _ElementCssClassSet(this);
void set classes(Collection<String> value) {
@@ -8897,6 +8978,29 @@
classSet.addAll(value);
}
+ /**
+ * Allows access to all custom data attributes (data-*) set on this element.
+ *
+ * The keys for the map must follow these rules:
+ *
+ * * The name must not begin with 'xml'.
+ * * The name cannot contain a semi-colon (';').
+ * * The name cannot contain any capital letters.
+ *
+ * Any keys from markup will be converted to camel-cased keys in the map.
+ *
+ * For example, HTML specified as:
+ *
+ * <div data-my-random-value='value'></div>
+ *
+ * Would be accessed in Dart as:
+ *
+ * var value = element.dataAttributes['myRandomValue'];
+ *
+ * See also:
+ *
+ * * [Custom data attributes](http://www.w3.org/TR/html5/global-attributes.html#custom-data-attribute)
+ */
Map<String, String> get dataAttributes =>
new _DataAttributeMap(attributes);
@@ -8910,19 +9014,39 @@
/**
* Gets a map for manipulating the attributes of a particular namespace.
+ *
* This is primarily useful for SVG attributes such as xref:link.
*/
Map<String, String> getNamespacedAttributes(String namespace) {
return new _NamespacedAttributeMap(this, namespace);
}
- /** @domName Window.getComputedStyle */
+ /**
+ * The set of all CSS values applied to this element, including inherited
+ * and default values.
+ *
+ * The computedStyle contains values that are inherited from other
+ * sources, such as parent elements or stylesheets. This differs from the
+ * [style] property, which contains only the values specified directly on this
+ * element.
+ *
+ * See also:
+ *
+ * * [CSS Inheritance and Cascade](http://docs.webplatform.org/wiki/tutorials/inheritance_and_cascade)
+ */
Future<CssStyleDeclaration> get computedStyle {
// TODO(jacobr): last param should be null, see b/5045788
return getComputedStyle('');
}
- /** @domName Window.getComputedStyle */
+ /**
+ * Returns the computed styles for pseudo-elements such as `::after`,
+ * `::before`, `::marker`, `::line-marker`.
+ *
+ * See also:
+ *
+ * * [Pseudo-elements](http://docs.webplatform.org/wiki/css/selectors/pseudo-elements)
+ */
Future<CssStyleDeclaration> getComputedStyle(String pseudoElement) {
return _createMeasurementFuture(
() => window.$dom_getComputedStyle(this, pseudoElement),
@@ -8930,14 +9054,15 @@
}
/**
- * Adds the specified element to after the last child of this.
+ * Adds the specified element to after the last child of this element.
*/
void append(Element e) {
this.children.add(e);
}
/**
- * Adds the specified text as a text node after the last child of this.
+ * Adds the specified text as a text node after the last child of this
+ * element.
*/
void appendText(String text) {
this.insertAdjacentText('beforeend', text);
@@ -8945,7 +9070,7 @@
/**
* Parses the specified text as HTML and adds the resulting node after the
- * last child of this.
+ * last child of this element.
*/
void appendHtml(String text) {
this.insertAdjacentHtml('beforeend', text);
diff --git a/sdk/lib/html/templates/html/impl/impl_Element.darttemplate b/sdk/lib/html/templates/html/impl/impl_Element.darttemplate
index b253d2d..fc2bcfb 100644
--- a/sdk/lib/html/templates/html/impl/impl_Element.darttemplate
+++ b/sdk/lib/html/templates/html/impl/impl_Element.darttemplate
@@ -348,17 +348,56 @@
}
}
-/// @domName $DOMNAME
+/**
+ * An abstract class, which all HTML elements extend.
+ */
abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
+ /**
+ * Creates an HTML element from a valid fragment of HTML.
+ *
+ * The [html] fragment must represent valid HTML with a single element root,
+ * which will be parsed and returned.
+ *
+ * Important: the contents of [html] should not contain any user-supplied
+ * data. Without strict data validation it is impossible to prevent script
+ * injection exploits.
+ *
+ * It is instead recommended that elements be constructed via [Element.tag]
+ * and text be added via [text].
+ *
+ * var element = new Element.html('<div class="foo">content</div>');
+ */
factory $CLASSNAME.html(String html) =>
_$(CLASSNAME)FactoryProvider.createElement_html(html);
+
+ /**
+ * Creates the HTML element specified by the tag name.
+ *
+ * This is similar to [Document.createElement].
+ * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
+ * this will create an [UnknownElement].
+ *
+ * var divElement = new Element.tag('div');
+ * print(divElement is DivElement); // 'true'
+ * var myElement = new Element.tag('unknownTag');
+ * print(myElement is UnknownElement); // 'true'
+ *
+ * For standard elements it is more preferable to use the type constructors:
+ * var element = new DivElement();
+ */
factory $CLASSNAME.tag(String tag) =>
_$(CLASSNAME)FactoryProvider.createElement_tag(tag);
/**
- * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute,
- * Element.removeAttribute
+ * All attributes on this element.
+ *
+ * Any modifications to the attribute map will automatically be applied to
+ * this element.
+ *
+ * This only includes attributes which are not in a namespace
+ * (such as 'xlink:href'), additional attributes can be accessed via
+ * [getNamespacedAttributes].
*/
Map<String, String> get attributes => new _ElementAttributeMap(this);
@@ -392,8 +431,16 @@
List<Element> get elements => this.children;
/**
- * @domName childElementCount, firstElementChild, lastElementChild,
- * children, Node.nodes.add
+ * List of the direct children of this element.
+ *
+ * This collection can be used to add and remove elements from the document.
+ *
+ * var item = new DivElement();
+ * item.text = 'Something';
+ * document.body.children.add(item) // Item is now displayed on the page.
+ * for (var element in document.body.children) {
+ * element.style.background = 'red'; // Turns every child of body red.
+ * }
*/
List<Element> get children => new _ChildrenElementList._wrap(this);
@@ -405,12 +452,46 @@
children.addAll(copy);
}
+ /**
+ * Finds the first descendant element of this element that matches the
+ * specified group of selectors.
+ *
+ * [selectors] should be a string using CSS selector syntax.
+ *
+ * // Gets the first descendant with the class 'classname'
+ * var element = element.query('.className');
+ * // Gets the element with id 'id'
+ * var element = element.query('#id');
+ * // Gets the first descendant [ImageElement]
+ * var img = element.query('img');
+ *
+ * See also:
+ *
+ * * [CSS Selectors](http://docs.webplatform.org/wiki/css/selectors)
+ */
Element query(String selectors) => $dom_querySelector(selectors);
+ /**
+ * Finds all descendent elements of this element that match the specified
+ * group of selectors.
+ *
+ * [selectors] should be a string using CSS selector syntax.
+ *
+ * var items = element.query('.itemClassName');
+ */
List<Element> queryAll(String selectors) =>
new _FrozenElementList._wrap($dom_querySelectorAll(selectors));
- /** @domName className, classList */
+ /**
+ * The set of CSS classes applied to this element.
+ *
+ * This set makes it easy to add, remove or toggle the classes applied to
+ * this element.
+ *
+ * element.classes.add('selected');
+ * element.classes.toggle('isOnline');
+ * element.classes.remove('selected');
+ */
CssClassSet get classes => new _ElementCssClassSet(this);
void set classes(Collection<String> value) {
@@ -419,6 +500,29 @@
classSet.addAll(value);
}
+ /**
+ * Allows access to all custom data attributes (data-*) set on this element.
+ *
+ * The keys for the map must follow these rules:
+ *
+ * * The name must not begin with 'xml'.
+ * * The name cannot contain a semi-colon (';').
+ * * The name cannot contain any capital letters.
+ *
+ * Any keys from markup will be converted to camel-cased keys in the map.
+ *
+ * For example, HTML specified as:
+ *
+ * <div data-my-random-value='value'></div>
+ *
+ * Would be accessed in Dart as:
+ *
+ * var value = element.dataAttributes['myRandomValue'];
+ *
+ * See also:
+ *
+ * * [Custom data attributes](http://www.w3.org/TR/html5/global-attributes.html#custom-data-attribute)
+ */
Map<String, String> get dataAttributes =>
new _DataAttributeMap(attributes);
@@ -432,19 +536,39 @@
/**
* Gets a map for manipulating the attributes of a particular namespace.
+ *
* This is primarily useful for SVG attributes such as xref:link.
*/
Map<String, String> getNamespacedAttributes(String namespace) {
return new _NamespacedAttributeMap(this, namespace);
}
- /** @domName Window.getComputedStyle */
+ /**
+ * The set of all CSS values applied to this element, including inherited
+ * and default values.
+ *
+ * The computedStyle contains values that are inherited from other
+ * sources, such as parent elements or stylesheets. This differs from the
+ * [style] property, which contains only the values specified directly on this
+ * element.
+ *
+ * See also:
+ *
+ * * [CSS Inheritance and Cascade](http://docs.webplatform.org/wiki/tutorials/inheritance_and_cascade)
+ */
Future<CssStyleDeclaration> get computedStyle {
// TODO(jacobr): last param should be null, see b/5045788
return getComputedStyle('');
}
- /** @domName Window.getComputedStyle */
+ /**
+ * Returns the computed styles for pseudo-elements such as `::after`,
+ * `::before`, `::marker`, `::line-marker`.
+ *
+ * See also:
+ *
+ * * [Pseudo-elements](http://docs.webplatform.org/wiki/css/selectors/pseudo-elements)
+ */
Future<CssStyleDeclaration> getComputedStyle(String pseudoElement) {
return _createMeasurementFuture(
() => window.$dom_getComputedStyle(this, pseudoElement),
@@ -452,14 +576,15 @@
}
/**
- * Adds the specified element to after the last child of this.
+ * Adds the specified element to after the last child of this element.
*/
void append(Element e) {
this.children.add(e);
}
/**
- * Adds the specified text as a text node after the last child of this.
+ * Adds the specified text as a text node after the last child of this
+ * element.
*/
void appendText(String text) {
this.insertAdjacentText('beforeend', text);
@@ -467,7 +592,7 @@
/**
* Parses the specified text as HTML and adds the resulting node after the
- * last child of this.
+ * last child of this element.
*/
void appendHtml(String text) {
this.insertAdjacentHtml('beforeend', text);
@@ -509,7 +634,16 @@
$endif
$if DART2JS
- /** @domName Element.insertAdjacentText */
+ /**
+ * Creates a text node and inserts it into the DOM at the specified location.
+ *
+ * To see the possible values for [where], read the doc for
+ * [insertAdjacentHtml].
+ *
+ * See also:
+ *
+ * * [insertAdjacentHtml]
+ */
void insertAdjacentText(String where, String text) {
if (JS('bool', '!!#.insertAdjacentText', this)) {
_insertAdjacentText(where, text);
@@ -521,7 +655,28 @@
@JSName('insertAdjacentText')
void _insertAdjacentText(String where, String text) native;
- /** @domName Element.insertAdjacentHTML */
+ /**
+ * Parses text as an HTML fragment and inserts it into the DOM at the
+ * specified location.
+ *
+ * The [where] parameter indicates where to insert the HTML fragment:
+ *
+ * * 'beforeBegin': Immediately before this element.
+ * * 'afterBegin': As the first child of this element.
+ * * 'beforeEnd': As the last child of this element.
+ * * 'afterEnd': Immediately after this element.
+ *
+ * var html = '<div class="something">content</div>';
+ * // Inserts as the first child
+ * document.body.insertAdjacentHtml('afterBegin', html);
+ * var createdElement = document.body.children[0];
+ * print(createdElement.classes[0]); // Prints 'something'
+ *
+ * See also:
+ *
+ * * [insertAdjacentText]
+ * * [insertAdjacentElement]
+ */
void insertAdjacentHtml(String where, String text) {
if (JS('bool', '!!#.insertAdjacentHtml', this)) {
_insertAdjacentHtml(where, text);
@@ -533,7 +688,16 @@
@JSName('insertAdjacentHTML')
void _insertAdjacentHTML(String where, String text) native;
- /** @domName Element.insertAdjacentHTML */
+ /**
+ * Inserts [element] into the DOM at the specified location.
+ *
+ * To see the possible values for [where], read the doc for
+ * [insertAdjacentHtml].
+ *
+ * See also:
+ *
+ * * [insertAdjacentHtml]
+ */
Element insertAdjacentElement(String where, Element element) {
if (JS('bool', '!!#.insertAdjacentElement', this)) {
_insertAdjacentElement(where, element);
diff --git a/tools/VERSION b/tools/VERSION
index 0ade99d..f2f2502 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -1,4 +1,4 @@
MAJOR 0
MINOR 2
BUILD 9
-PATCH 0
+PATCH 1
diff --git a/utils/pub/command_lish.dart b/utils/pub/command_lish.dart
index f775b46..420952c 100644
--- a/utils/pub/command_lish.dart
+++ b/utils/pub/command_lish.dart
@@ -10,6 +10,7 @@
import '../../pkg/args/lib/args.dart';
import '../../pkg/http/lib/http.dart' as http;
+import 'directory_tree.dart';
import 'git.dart' as git;
import 'io.dart';
import 'log.dart' as log;
@@ -33,21 +34,13 @@
/// The URL of the server to which to upload the package.
Uri get server => new Uri.fromString(commandOptions['server']);
- Future onRun() {
+ Future _publish(packageBytes) {
var cloudStorageUrl;
return oauth2.withClient(cache, (client) {
// TODO(nweiz): Cloud Storage can provide an XML-formatted error. We
// should report that error and exit.
- return Futures.wait([
- client.get(server.resolve("/packages/versions/new.json")),
- _filesToPublish.transform((files) {
- log.fine('Archiving and publishing ${entrypoint.root}.');
- return createTarGz(files, baseDir: entrypoint.root.dir);
- }).chain(consumeInputStream),
- _validate()
- ]).chain((results) {
- var response = results[0];
- var packageBytes = results[1];
+ var newUri = server.resolve("/packages/versions/new.json");
+ return client.get(newUri).chain((response) {
var parameters = _parseJson(response);
var url = _expectField(parameters, 'url', response);
@@ -98,19 +91,38 @@
}
} else if (e is oauth2.ExpirationException) {
log.error("Pub's authorization to upload packages has expired and "
- "can't be automatically refreshed.");
- return onRun();
+ "can't be automatically refreshed.");
+ return _publish(packageBytes);
} else if (e is oauth2.AuthorizationException) {
var message = "OAuth2 authorization failed";
if (e.description != null) message = "$message (${e.description})";
log.error("$message.");
- return oauth2.clearCredentials(cache).chain((_) => onRun());
+ return oauth2.clearCredentials(cache).chain((_) =>
+ _publish(packageBytes));
} else {
throw e;
}
});
}
+ Future onRun() {
+ var files;
+ return _filesToPublish.transform((f) {
+ files = f;
+ log.fine('Archiving and publishing ${entrypoint.root}.');
+ return createTarGz(files, baseDir: entrypoint.root.dir);
+ }).chain(consumeInputStream).chain((packageBytes) {
+ // Show the package contents so the user can verify they look OK.
+ var package = entrypoint.root;
+ log.message(
+ 'Publishing "${package.name}" ${package.version}:\n'
+ '${generateTree(files)}');
+
+ // Validate the package.
+ return _validate().chain((_) => _publish(packageBytes));
+ });
+ }
+
/// The basenames of files that are automatically excluded from archives.
final _BLACKLISTED_FILES = const ['pubspec.lock'];
@@ -123,6 +135,14 @@
/// will return all non-hidden files.
Future<List<String>> get _filesToPublish {
var rootDir = entrypoint.root.dir;
+
+ // TODO(rnystrom): listDir() returns real file paths after symlinks are
+ // resolved. This means if libDir contains a symlink, the resulting paths
+ // won't appear to be within it, which confuses relativeTo(). Work around
+ // that here by making sure we have the real path to libDir. Remove this
+ // when #7346 is fixed.
+ rootDir = new File(rootDir).fullPathSync();
+
return Futures.wait([
dirExists(join(rootDir, '.git')),
git.isInstalled
@@ -135,15 +155,25 @@
return listDir(rootDir, recursive: true).chain((entries) {
return Futures.wait(entries.map((entry) {
- return fileExists(entry).transform((isFile) => isFile ? entry : null);
+ return fileExists(entry).transform((isFile) {
+ // Skip directories.
+ if (!isFile) return null;
+
+ // TODO(rnystrom): Making these relative will break archive
+ // creation if the cwd is ever *not* the package root directory.
+ // Should instead only make these relative right before generating
+ // the tree display (which is what really needs them to be).
+ // Make it relative to the package root.
+ return relativeTo(entry, rootDir);
+ });
}));
});
}).transform((files) => files.filter((file) {
if (file == null || _BLACKLISTED_FILES.contains(basename(file))) {
return false;
}
- return !splitPath(relativeTo(file, rootDir))
- .some(_BLACKLISTED_DIRECTORIES.contains);
+
+ return !splitPath(file).some(_BLACKLISTED_DIRECTORIES.contains);
}));
}
@@ -180,15 +210,22 @@
var errors = pair.first;
var warnings = pair.last;
- if (errors.isEmpty && warnings.isEmpty) return new Future.immediate(null);
- if (!errors.isEmpty) throw "Package validation failed.";
+ if (!errors.isEmpty) {
+ throw "Sorry, your package is missing "
+ "${(errors.length > 1) ? 'some requirements' : 'a requirement'} "
+ "and can't be published yet.\nFor more information, see: "
+ "http://pub.dartlang.org/doc/pub-lish.html.\n";
+ }
- var s = warnings.length == 1 ? '' : 's';
- stdout.writeString("Package has ${warnings.length} warning$s. Upload "
- "anyway (y/n)? ");
- return readLine().transform((line) {
- if (new RegExp(r"^[yY]").hasMatch(line)) return;
- throw "Package upload canceled.";
+ var message = 'Looks great! Are you ready to upload your package';
+
+ if (!warnings.isEmpty) {
+ var s = warnings.length == 1 ? '' : 's';
+ message = "Package has ${warnings.length} warning$s. Upload anyway";
+ }
+
+ return confirm(message).transform((confirmed) {
+ if (!confirmed) throw "Package upload canceled.";
});
});
}
diff --git a/utils/pub/directory_tree.dart b/utils/pub/directory_tree.dart
new file mode 100644
index 0000000..f5baa62
--- /dev/null
+++ b/utils/pub/directory_tree.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A simple library for rendering a list of files as a directory tree.
+library directory_tree;
+
+import 'log.dart' as log;
+import 'path.dart' as path;
+
+/// Draws a directory tree for the given list of files. Given a list of files
+/// like:
+///
+/// TODO
+/// example/console_example.dart
+/// example/main.dart
+/// example/web copy/web_example.dart
+/// test/absolute_test.dart
+/// test/basename_test.dart
+/// test/dirname_test.dart
+/// test/extension_test.dart
+/// test/is_absolute_test.dart
+/// test/is_relative_test.dart
+/// test/join_test.dart
+/// test/normalize_test.dart
+/// test/relative_test.dart
+/// test/split_test.dart
+/// .gitignore
+/// README.md
+/// lib/path.dart
+/// pubspec.yaml
+/// test/all_test.dart
+/// test/path_posix_test.dart
+/// test/path_windows_test.dart
+///
+/// this will render:
+///
+/// |-- .gitignore
+/// |-- README.md
+/// |-- TODO
+/// |-- example
+/// | |-- console_example.dart
+/// | |-- main.dart
+/// | '-- web copy
+/// | '-- web_example.dart
+/// |-- lib
+/// | '-- path.dart
+/// |-- pubspec.yaml
+/// '-- test
+/// |-- absolute_test.dart
+/// |-- all_test.dart
+/// |-- basename_test.dart
+/// | (7 more...)
+/// |-- path_windows_test.dart
+/// |-- relative_test.dart
+/// '-- split_test.dart
+///
+String generateTree(List<String> files) {
+ // Parse out the files into a tree of nested maps.
+ var root = {};
+ for (var file in files) {
+ var parts = path.split(file);
+ var directory = root;
+ for (var part in path.split(file)) {
+ directory = directory.putIfAbsent(part, () => {});
+ }
+ }
+
+ // Walk the map recursively and render to a string.
+ var buffer = new StringBuffer();
+ _draw(buffer, '', false, null, root);
+ return buffer.toString();
+}
+
+void _drawLine(StringBuffer buffer, String prefix, bool isLastChild,
+ String name) {
+ // Print lines.
+ buffer.add(prefix);
+ if (name != null) {
+ if (isLastChild) {
+ buffer.add("'-- ");
+ } else {
+ buffer.add("|-- ");
+ }
+ }
+
+ // Print name.
+ buffer.add(name);
+ buffer.add('\n');
+}
+
+String _getPrefix(bool isRoot, bool isLast) {
+ if (isRoot) return "";
+ if (isLast) return " ";
+ return "| ";
+}
+
+void _draw(StringBuffer buffer, String prefix, bool isLast,
+ String name, Map children) {
+ // Don't draw a line for the root node.
+ if (name != null) _drawLine(buffer, prefix, isLast, name);
+
+ // Recurse to the children.
+ var childNames = new List.from(children.keys);
+ childNames.sort();
+
+ _drawChild(bool isLastChild, String child) {
+ var childPrefix = _getPrefix(name == null, isLast);
+ _draw(buffer, '$prefix$childPrefix', isLastChild, child, children[child]);
+ }
+
+ if (childNames.length <= 10) {
+ // Not too many, so show all the children.
+ for (var i = 0; i < childNames.length; i++) {
+ _drawChild(i == childNames.length - 1, childNames[i]);
+ }
+ } else {
+ // Show the first few.
+ _drawChild(false, childNames[0]);
+ _drawChild(false, childNames[1]);
+ _drawChild(false, childNames[2]);
+
+ // Elide the middle ones.
+ buffer.add(prefix);
+ buffer.add(_getPrefix(name == null, isLast));
+ buffer.add('| (${childNames.length - 6} more...)\n');
+
+ // Show the last few.
+ _drawChild(false, childNames[childNames.length - 3]);
+ _drawChild(false, childNames[childNames.length - 2]);
+ _drawChild(true, childNames[childNames.length - 1]);
+ }
+}
diff --git a/utils/pub/io.dart b/utils/pub/io.dart
index 0ecc7b2..39a854d 100644
--- a/utils/pub/io.dart
+++ b/utils/pub/io.dart
@@ -30,45 +30,24 @@
* platform-specific path separators. Parts can be [String], [Directory], or
* [File] objects.
*/
-String join(part1, [part2, part3, part4]) {
- part1 = _getPath(part1);
- if (part2 != null) part2 = _getPath(part2);
- if (part3 != null) part3 = _getPath(part3);
- if (part4 != null) part4 = _getPath(part4);
+String join(part1, [part2, part3, part4, part5, part6, part7, part8]) {
+ var parts = [part1, part2, part3, part4, part5, part6, part7, part8]
+ .map((part) => part == null ? null : _getPath(part));
- // TODO(nweiz): Don't use "?part" in path.dart.
- if (part4 != null) {
- return path.join(part1, part2, part3, part4);
- } else if (part3 != null) {
- return path.join(part1, part2, part3);
- } else if (part2 != null) {
- return path.join(part1, part2);
- } else {
- return path.join(part1);
- }
+ return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5],
+ parts[6], parts[7]);
}
/// Gets the basename, the file name without any leading directory path, for
/// [file], which can either be a [String], [File], or [Directory].
String basename(file) => path.basename(_getPath(file));
-// TODO(nweiz): move this into path.dart.
/// Gets the the leading directory path for [file], which can either be a
/// [String], [File], or [Directory].
-String dirname(file) {
- file = _sanitizePath(file);
+String dirname(file) => path.dirname(_getPath(file));
- int lastSlash = file.lastIndexOf('/', file.length);
- if (lastSlash == -1) {
- return '.';
- } else {
- return file.substring(0, lastSlash);
- }
-}
-
-// TODO(nweiz): move this into path.dart.
-/// Splits [path] into its individual components.
-List<String> splitPath(path) => _sanitizePath(path).split('/');
+/// Splits [entry] into its individual components.
+List<String> splitPath(entry) => path.split(_getPath(entry));
/// Returns whether or not [entry] is nested somewhere within [dir]. This just
/// performs a path comparison; it doesn't look at the actual filesystem.
@@ -77,10 +56,8 @@
return !path.isAbsolute(relative) && splitPath(relative)[0] != '..';
}
-// TODO(nweiz): move this into path.dart.
/// Returns the path to [target] from [base].
-String relativeTo(target, base) =>
- new path.Builder(root: base).relative(target);
+String relativeTo(target, base) => path.relative(target, from: base);
/**
* Asynchronously determines if [path], which can be a [String] file path, a
@@ -469,6 +446,18 @@
/// A StringInputStream reading from stdin.
final _stringStdin = new StringInputStream(stdin);
+/// Displays a message and reads a yes/no confirmation from the user. Returns
+/// a [Future] that completes to `true` if the user confirms or `false` if they
+/// do not.
+///
+/// This will automatically append " (y/n)?" to the message, so [message]
+/// should just be a fragment like, "Are you sure you want to proceed".
+Future<bool> confirm(String message) {
+ log.fine('Showing confirm message: $message');
+ stdout.writeString("$message (y/n)? ");
+ return readLine().transform((line) => new RegExp(r"^[yY]").hasMatch(line));
+}
+
/// Returns a single line read from a [StringInputStream]. By default, reads
/// from stdin.
///
@@ -498,7 +487,9 @@
stream.onLine = () {
removeCallbacks();
- completer.complete(stream.readLine());
+ var line = stream.readLine();
+ log.io('Read line: $line');
+ completer.complete(line);
};
stream.onError = (e) {
@@ -952,7 +943,7 @@
InputStream createTarGz(List contents, {baseDir}) {
var buffer = new StringBuffer();
buffer.add('Creating .tag.gz stream containing:\n');
- contents.forEach(buffer.add);
+ contents.forEach((file) => buffer.add('$file\n'));
log.fine(buffer.toString());
// TODO(nweiz): Propagate errors to the returned stream (including non-zero
@@ -1067,40 +1058,6 @@
throw 'Entry $entry is not a supported type.';
}
-/// Gets the path string for [entry], normalizing backslashes to forward slashes
-/// on Windows.
-String _sanitizePath(entry) {
- entry = _getPath(entry);
- if (Platform.operatingSystem != 'windows') return entry;
-
- var split = _splitAbsolute(entry);
- if (split.first == null) return split.last.replaceAll('\\', '/');
-
- // For absolute Windows paths, we don't want the prefix (either "\\" or e.g.
- // "C:\") to look like a normal path component, so we ensure that it only
- // contains backslashes.
- return '${split.first.replaceAll('/', '\\')}'
- '${split.last.replaceAll('\\', '/')}';
-}
-
-// TODO(nweiz): Add something like this to path.dart.
-/// Splits [entry] into two components: the absolute path prefix and the
-/// remaining path. Takes into account Windows' quirky absolute paths syntaxes.
-Pair<String, String> _splitAbsolute(entry) {
- var path = _getPath(entry);
-
- if (Platform.operatingSystem != 'windows') {
- return !path.startsWith('/') ? new Pair(null, path)
- : new Pair('/', path.substring(1));
- }
-
- // An absolute path on Windows is either UNC (two leading backslashes),
- // or a drive letter followed by a colon and a slash.
- var match = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])').firstMatch(path);
- return match == null ? new Pair(null, path)
- : new Pair(match.group(0), path.substring(match.end));
-}
-
/**
* Gets a [Directory] for [entry], which can either already be one, or be a
* [String].
diff --git a/utils/pub/path.dart b/utils/pub/path.dart
index d803fd1..593396d0 100644
--- a/utils/pub/path.dart
+++ b/utils/pub/path.dart
@@ -37,6 +37,12 @@
String basenameWithoutExtension(String path) =>
_builder.basenameWithoutExtension(path);
+/// Gets the part of [path] before the last separator.
+///
+/// path.dirname('path/to/foo.dart'); // -> 'path/to'
+/// path.dirname('path/to'); // -> 'to'
+String dirname(String path) => _builder.dirname(path);
+
/// Gets the file extension of [path]: the portion of [basename] from the last
/// `.` to the end (including the `.` itself).
///
@@ -52,6 +58,19 @@
/// path.extension('~/.notes.txt'); // -> '.txt'
String extension(String path) => _builder.extension(path);
+// TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
+/// Returns the root of [path], if it's absolute, or the empty string if it's
+/// relative.
+///
+/// // Unix
+/// path.rootPrefix('path/to/foo'); // -> ''
+/// path.rootPrefix('/path/to/foo'); // -> '/'
+///
+/// // Windows
+/// path.rootPrefix(r'path\to\foo'); // -> ''
+/// path.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+String rootPrefix(String path) => _builder.rootPrefix(path);
+
/// Returns `true` if [path] is an absolute path and `false` if it is a
/// relative path. On POSIX systems, absolute paths start with a `/` (forward
/// slash). On Windows, an absolute path starts with `\\`, or a drive letter
@@ -78,17 +97,27 @@
///
/// path.join('path', '/to', 'foo'); // -> '/to/foo'
String join(String part1, [String part2, String part3, String part4,
- String part5, String part6, String part7, String part8]) {
- if (!?part2) return _builder.join(part1);
- if (!?part3) return _builder.join(part1, part2);
- if (!?part4) return _builder.join(part1, part2, part3);
- if (!?part5) return _builder.join(part1, part2, part3, part4);
- if (!?part6) return _builder.join(part1, part2, part3, part4, part5);
- if (!?part7) return _builder.join(part1, part2, part3, part4, part5, part6);
- if (!?part8) return _builder.join(part1, part2, part3, part4, part5, part6,
- part7);
- return _builder.join(part1, part2, part3, part4, part5, part6, part7, part8);
-}
+ String part5, String part6, String part7, String part8]) =>
+ _builder.join(part1, part2, part3, part4, part5, part6, part7, part8);
+
+// TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
+/// Splits [path] into its components using the current platform's [separator].
+///
+/// path.split('path/to/foo'); // -> ['path', 'to', 'foo']
+///
+/// The path will *not* be normalized before splitting.
+///
+/// path.split('path/../foo'); // -> ['path', '..', 'foo']
+///
+/// If [path] is absolute, the root directory will be the first element in the
+/// array. Example:
+///
+/// // Unix
+/// path.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
+///
+/// // Windows
+/// path.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
+List<String> split(String path) => _builder.split(path);
/// Normalizes [path], simplifying it by handling `..`, and `.`, and
/// removing redundant path separators whenever possible.
@@ -103,12 +132,19 @@
/// path.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
/// path.relative('/root/other.dart'); // -> '../other.dart'
///
+/// If the [from] argument is passed, [path] is made relative to that instead.
+///
+/// path.relative('/root/path/a/b.dart',
+/// from: '/root/path'); // -> 'a/b.dart'
+/// path.relative('/root/other.dart',
+/// from: '/root/path'); // -> '../other.dart'
+///
/// Since there is no relative path from one drive letter to another on Windows,
/// this will return an absolute path in that case.
///
-/// // Given current directory is C:\home:
-/// path.relative(r'D:\other'); // -> 'D:\other'
-String relative(String path) => _builder.relative(path);
+/// path.relative(r'D:\other', from: r'C:\home'); // -> 'D:\other'
+String relative(String path, {String from}) =>
+ _builder.relative(path, from: from);
/// Removes a trailing extension from the last part of [path].
///
@@ -163,6 +199,24 @@
String basenameWithoutExtension(String path) =>
_parse(path).basenameWithoutExtension;
+ /// Gets the part of [path] before the last separator.
+ ///
+ /// builder.dirname('path/to/foo.dart'); // -> 'path/to'
+ /// builder.dirname('path/to'); // -> 'to'
+ String dirname(String path) {
+ var parsed = _parse(path);
+ if (parsed.parts.isEmpty) return parsed.root == null ? '.' : parsed.root;
+ if (!parsed.hasTrailingSeparator) {
+ if (parsed.parts.length == 1) {
+ return parsed.root == null ? '.' : parsed.root;
+ }
+ parsed.parts.removeLast();
+ parsed.separators.removeLast();
+ }
+ parsed.separators[parsed.separators.length - 1] = '';
+ return parsed.toString();
+ }
+
/// Gets the file extension of [path]: the portion of [basename] from the last
/// `.` to the end (including the `.` itself).
///
@@ -178,6 +232,22 @@
/// builder.extension('~/.notes.txt'); // -> '.txt'
String extension(String path) => _parse(path).extension;
+ // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
+ /// Returns the root of [path], if it's absolute, or an empty string if it's
+ /// relative.
+ ///
+ /// // Unix
+ /// builder.rootPrefix('path/to/foo'); // -> ''
+ /// builder.rootPrefix('/path/to/foo'); // -> '/'
+ ///
+ /// // Windows
+ /// builder.rootPrefix(r'path\to\foo'); // -> ''
+ /// builder.rootPrefix(r'C:\path\to\foo'); // -> r'C:\'
+ String rootPrefix(String path) {
+ var root = _parse(path).root;
+ return root == null ? '' : root;
+ }
+
/// Returns `true` if [path] is an absolute path and `false` if it is a
/// relative path. On POSIX systems, absolute paths start with a `/` (forward
/// slash). On Windows, an absolute path starts with `\\`, or a drive letter
@@ -208,8 +278,16 @@
var buffer = new StringBuffer();
var needsSeparator = false;
- addPart(condition, part) {
- if (!condition) return;
+ var parts = [part1, part2, part3, part4, part5, part6, part7, part8];
+ for (var i = 1; i < parts.length; i++) {
+ if (parts[i] != null && parts[i - 1] == null) {
+ throw new ArgumentError("join(): part ${i - 1} was null, but part $i "
+ "was not.");
+ }
+ }
+
+ for (var part in parts) {
+ if (part == null) continue;
if (this.isAbsolute(part)) {
// An absolute path discards everything before it.
@@ -231,18 +309,35 @@
!style.separatorPattern.hasMatch(part[part.length - 1]);
}
- addPart(true, part1);
- addPart(?part2, part2);
- addPart(?part3, part3);
- addPart(?part4, part4);
- addPart(?part5, part5);
- addPart(?part6, part6);
- addPart(?part7, part7);
- addPart(?part8, part8);
-
return buffer.toString();
}
+ // TODO(nweiz): add a UNC example for Windows once issue 7323 is fixed.
+ /// Splits [path] into its components using the current platform's
+ /// [separator]. Example:
+ ///
+ /// builder.split('path/to/foo'); // -> ['path', 'to', 'foo']
+ ///
+ /// The path will *not* be normalized before splitting.
+ ///
+ /// builder.split('path/../foo'); // -> ['path', '..', 'foo']
+ ///
+ /// If [path] is absolute, the root directory will be the first element in the
+ /// array. Example:
+ ///
+ /// // Unix
+ /// builder.split('/path/to/foo'); // -> ['/', 'path', 'to', 'foo']
+ ///
+ /// // Windows
+ /// builder.split(r'C:\path\to\foo'); // -> [r'C:\', 'path', 'to', 'foo']
+ List<String> split(String path) {
+ var parsed = _parse(path);
+ // Filter out empty parts that exist due to multiple separators in a row.
+ parsed.parts = parsed.parts.filter((part) => part != '');
+ if (parsed.root != null) parsed.parts.insertRange(0, 1, parsed.root);
+ return parsed.parts;
+ }
+
/// Normalizes [path], simplifying it by handling `..`, and `.`, and
/// removing redundant path separators whenever possible.
///
@@ -278,23 +373,44 @@
/// builder.relative('/root/path/a/b.dart'); // -> 'a/b.dart'
/// builder.relative('/root/other.dart'); // -> '../other.dart'
///
+ /// If the [from] argument is passed, [path] is made relative to that instead.
+ ///
+ /// builder.relative('/root/path/a/b.dart',
+ /// from: '/root/path'); // -> 'a/b.dart'
+ /// builder.relative('/root/other.dart',
+ /// from: '/root/path'); // -> '../other.dart'
+ ///
/// Since there is no relative path from one drive letter to another on
/// Windows, this will return an absolute path in that case.
///
- /// var builder = new Builder(root: r'C:\home');
- /// builder.relative(r'D:\other'); // -> 'D:\other'
- String relative(String path) {
+ /// builder.relative(r'D:\other', from: r'C:\other'); // -> 'D:\other'
+ ///
+ /// This will also return an absolute path if an absolute [path] is passed to
+ /// a builder with a relative [root].
+ ///
+ /// var builder = new Builder(r'some/relative/path');
+ /// builder.relative(r'/absolute/path'); // -> '/absolute/path'
+ String relative(String path, {String from}) {
if (path == '') return '.';
- // If the base path is relative, resolve it relative to the current
- // directory.
- var base = root;
- if (this.isRelative(base)) base = absolute(base);
+ from = from == null ? root : this.join(root, from);
- // If the given path is relative, resolve it relative to the base.
- if (this.isRelative(path)) return this.normalize(path);
+ // We can't determine the path from a relative path to an absolute path.
+ if (this.isRelative(from) && this.isAbsolute(path)) {
+ return this.normalize(path);
+ }
- var baseParsed = _parse(base)..normalize();
+ // If the given path is relative, resolve it relative to the root of the
+ // builder.
+ if (this.isRelative(path)) path = this.resolve(path);
+
+ // If the path is still relative and `from` is absolute, we're unable to
+ // find a path from `from` to `path`.
+ if (this.isRelative(path) && this.isAbsolute(from)) {
+ throw new ArgumentError('Unable to find a path to "$path" from "$from".');
+ }
+
+ var fromParsed = _parse(from)..normalize();
var pathParsed = _parse(path)..normalize();
// If the root prefixes don't match (for example, different drive letters
@@ -302,21 +418,21 @@
// one.
// TODO(rnystrom): Drive letters are case-insentive on Windows. Should
// handle "C:\" and "c:\" being the same root.
- if (baseParsed.root != pathParsed.root) return pathParsed.toString();
+ if (fromParsed.root != pathParsed.root) return pathParsed.toString();
// Strip off their common prefix.
- while (baseParsed.parts.length > 0 && pathParsed.parts.length > 0 &&
- baseParsed.parts[0] == pathParsed.parts[0]) {
- baseParsed.parts.removeAt(0);
- baseParsed.separators.removeAt(0);
+ while (fromParsed.parts.length > 0 && pathParsed.parts.length > 0 &&
+ fromParsed.parts[0] == pathParsed.parts[0]) {
+ fromParsed.parts.removeAt(0);
+ fromParsed.separators.removeAt(0);
pathParsed.parts.removeAt(0);
pathParsed.separators.removeAt(0);
}
// If there are any directories left in the root path, we need to walk up
// out of them.
- pathParsed.parts.insertRange(0, baseParsed.parts.length, '..');
- pathParsed.separators.insertRange(0, baseParsed.parts.length,
+ pathParsed.parts.insertRange(0, fromParsed.parts.length, '..');
+ pathParsed.separators.insertRange(0, fromParsed.parts.length,
style.separator);
// Corner case: the paths completely collapsed.
diff --git a/utils/pub/validator.dart b/utils/pub/validator.dart
index 3e491a9..5c0c850 100644
--- a/utils/pub/validator.dart
+++ b/utils/pub/validator.dart
@@ -57,17 +57,17 @@
var warnings = flatten(validators.map((validator) => validator.warnings));
if (!errors.isEmpty) {
- log.error("== Errors:");
+ log.error("Missing requirements:");
for (var error in errors) {
- log.error("* $error");
+ log.error("* ${Strings.join(error.split('\n'), '\n ')}");
}
log.error("");
}
if (!warnings.isEmpty) {
- log.warning("== Warnings:");
+ log.warning("Suggestions:");
for (var warning in warnings) {
- log.warning("* $warning");
+ log.warning("* ${Strings.join(warning.split('\n'), '\n ')}");
}
log.warning("");
}
diff --git a/utils/pub/validator/lib.dart b/utils/pub/validator/lib.dart
index 64f44c6..39b71af 100644
--- a/utils/pub/validator/lib.dart
+++ b/utils/pub/validator/lib.dart
@@ -21,21 +21,30 @@
Future validate() {
var libDir = join(entrypoint.root.dir, "lib");
+
return dirExists(libDir).chain((libDirExists) {
if (!libDirExists) {
- errors.add('Your package must have a "lib/" directory so users have '
- 'something to import.');
+ errors.add('You must have a "lib" directory.\n'
+ "Without that, users cannot import any code from your package.");
return new Future.immediate(null);
}
+ // TODO(rnystrom): listDir() returns real file paths after symlinks are
+ // resolved. This means if libDir contains a symlink, the resulting paths
+ // won't appear to be within it, which confuses relativeTo(). Work around
+ // that here by making sure we have the real path to libDir. Remove this
+ // when #7346 is fixed.
+ libDir = new File(libDir).fullPathSync();
+
return listDir(libDir).transform((files) {
files = files.map((file) => relativeTo(file, libDir));
if (files.isEmpty) {
- errors.add('The "lib/" directory may not be empty so users have '
- 'something to import');
+ errors.add('You must have a non-empty "lib" directory.\n'
+ "Without that, users cannot import any code from your package.");
} else if (files.length == 1 && files.first == "src") {
- errors.add('The "lib/" directory must contain something other than '
- '"src/" so users have something to import');
+ errors.add('The "lib" directory must contain something other than '
+ '"src".\n'
+ "Otherwise, users cannot import any code from your package.");
}
});
});
diff --git a/utils/pub/validator/license.dart b/utils/pub/validator/license.dart
index 46a1c40..fcc6a49 100644
--- a/utils/pub/validator/license.dart
+++ b/utils/pub/validator/license.dart
@@ -20,9 +20,10 @@
r"^([a-zA-Z0-9]+[-_])?(LICENSE|COPYING)(\..*)?$");
if (files.map(basename).some(licenseLike.hasMatch)) return;
- errors.add("Your package must have a COPYING or LICENSE file containing "
- "an open-source license. For more details, see "
- "http://pub.dartlang.org/doc/pub-lish.html.");
+ errors.add(
+ "You must have a COPYING or LICENSE file in the root directory.\n"
+ "An open-source license helps ensure people can legally use your "
+ "code.");
});
}
}
diff --git a/utils/pub/validator/name.dart b/utils/pub/validator/name.dart
index ecc34e4..687cfc7 100644
--- a/utils/pub/validator/name.dart
+++ b/utils/pub/validator/name.dart
@@ -45,14 +45,15 @@
if (name == "") {
errors.add("$description may not be empty.");
} else if (!new RegExp(r"^[a-zA-Z0-9_]*$").hasMatch(name)) {
- errors.add("$description must be a valid Dart identifier: it may only "
- "contain letters, numbers, and underscores.");
+ errors.add("$description may only contain letters, numbers, and "
+ "underscores.\n"
+ "Using a valid Dart identifier makes the name usable in Dart code.");
} else if (!new RegExp(r"^[a-zA-Z]").hasMatch(name)) {
- errors.add("$description must be a valid Dart identifier: it must begin "
- "with a letter.");
+ errors.add("$description must begin with letter.\n"
+ "Using a valid Dart identifier makes the name usable in Dart code.");
} else if (_RESERVED_WORDS.contains(name.toLowerCase())) {
- errors.add("$description must be a valid Dart identifier: it may not be "
- "a reserved word in Dart.");
+ errors.add("$description may not be a reserved word in Dart.\n"
+ "Using a valid Dart identifier makes the name usable in Dart code.");
} else if (new RegExp(r"[A-Z]").hasMatch(name)) {
warnings.add('$description should be lower-case. Maybe use '
'"${_unCamelCase(name)}"?');
diff --git a/utils/pub/validator/pubspec_field.dart b/utils/pub/validator/pubspec_field.dart
index e7e8ca7..17ddc27 100644
--- a/utils/pub/validator/pubspec_field.dart
+++ b/utils/pub/validator/pubspec_field.dart
@@ -7,6 +7,7 @@
import '../entrypoint.dart';
import '../system_cache.dart';
import '../validator.dart';
+import '../version.dart';
/// A validator that checks that the pubspec has valid "author" and "homepage"
/// fields.
@@ -20,7 +21,7 @@
var author = pubspec.fields['author'];
var authors = pubspec.fields['authors'];
if (author == null && authors == null) {
- errors.add('pubspec.yaml is missing an "author" or "authors" field.');
+ errors.add('Your pubspec.yaml must have an "author" or "authors" field.');
} else {
if (authors == null) authors = [author];
@@ -28,24 +29,29 @@
var hasEmail = new RegExp(r"<[^>]+> *$");
for (var authorName in authors) {
if (!hasName.hasMatch(authorName)) {
- warnings.add('Author "$authorName" in pubspec.yaml is missing a '
+ warnings.add('Author "$authorName" in pubspec.yaml should have a '
'name.');
}
if (!hasEmail.hasMatch(authorName)) {
- warnings.add('Author "$authorName" in pubspec.yaml is missing an '
- 'email address (e.g. "name <email>").');
+ warnings.add('Author "$authorName" in pubspec.yaml should have an '
+ 'email address\n(e.g. "name <email>").');
}
}
}
var homepage = pubspec.fields['homepage'];
if (homepage == null) {
- errors.add('pubspec.yaml is missing a "homepage" field.');
+ errors.add('Your pubspec.yaml is missing a "homepage" field.');
}
var description = pubspec.fields['description'];
if (description == null) {
- errors.add('pubspec.yaml is missing a "description" field.');
+ errors.add('Your pubspec.yaml is missing a "description" field.');
+ }
+
+ var version = pubspec.fields['version'];
+ if (version == null) {
+ errors.add('Your pubspec.yaml is missing a "version" field.');
}
return new Future.immediate(null);
diff --git a/utils/tests/pub/directory_tree_test.dart b/utils/tests/pub/directory_tree_test.dart
new file mode 100644
index 0000000..e9f892b
--- /dev/null
+++ b/utils/tests/pub/directory_tree_test.dart
@@ -0,0 +1,114 @@
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library lock_file_test;
+
+import '../../../pkg/unittest/lib/unittest.dart';
+import '../../pub/directory_tree.dart';
+
+main() {
+ test('no files', () {
+ expect(generateTree([]), equals(""));
+ });
+
+ test('up to ten files in one directory are shown', () {
+ var files = [
+ "a.dart",
+ "b.dart",
+ "c.dart",
+ "d.dart",
+ "e.dart",
+ "f.dart",
+ "g.dart",
+ "h.dart",
+ "i.dart",
+ "j.dart"
+ ];
+ expect(generateTree(files), equals("""
+|-- a.dart
+|-- b.dart
+|-- c.dart
+|-- d.dart
+|-- e.dart
+|-- f.dart
+|-- g.dart
+|-- h.dart
+|-- i.dart
+'-- j.dart
+"""));
+ });
+
+ test('files are elided if there are more than ten', () {
+ var files = [
+ "a.dart",
+ "b.dart",
+ "c.dart",
+ "d.dart",
+ "e.dart",
+ "f.dart",
+ "g.dart",
+ "h.dart",
+ "i.dart",
+ "j.dart",
+ "k.dart"
+ ];
+ expect(generateTree(files), equals("""
+|-- a.dart
+|-- b.dart
+|-- c.dart
+| (5 more...)
+|-- i.dart
+|-- j.dart
+'-- k.dart
+"""));
+ });
+
+ test('a complex example', () {
+ var files = [
+ "TODO",
+ "example/console_example.dart",
+ "example/main.dart",
+ "example/web copy/web_example.dart",
+ "test/absolute_test.dart",
+ "test/basename_test.dart",
+ "test/dirname_test.dart",
+ "test/extension_test.dart",
+ "test/is_absolute_test.dart",
+ "test/is_relative_test.dart",
+ "test/join_test.dart",
+ "test/normalize_test.dart",
+ "test/relative_test.dart",
+ "test/split_test.dart",
+ ".gitignore",
+ "README.md",
+ "lib/path.dart",
+ "pubspec.yaml",
+ "test/all_test.dart",
+ "test/path_posix_test.dart",
+ "test/path_windows_test.dart"
+ ];
+
+ expect(generateTree(files), equals("""
+|-- .gitignore
+|-- README.md
+|-- TODO
+|-- example
+| |-- console_example.dart
+| |-- main.dart
+| '-- web copy
+| '-- web_example.dart
+|-- lib
+| '-- path.dart
+|-- pubspec.yaml
+'-- test
+ |-- absolute_test.dart
+ |-- all_test.dart
+ |-- basename_test.dart
+ | (7 more...)
+ |-- path_windows_test.dart
+ |-- relative_test.dart
+ '-- split_test.dart
+"""));
+ });
+}
diff --git a/utils/tests/pub/oauth2_test.dart b/utils/tests/pub/oauth2_test.dart
index 32e4a75..f640e11 100644
--- a/utils/tests/pub/oauth2_test.dart
+++ b/utils/tests/pub/oauth2_test.dart
@@ -21,6 +21,7 @@
() {
var server = new ScheduledServer();
var pub = startPubLish(server);
+ confirmPublish(pub);
authorizePub(pub, server);
server.handle('GET', '/packages/versions/new.json', (request, response) {
@@ -41,6 +42,7 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
server.handle('GET', '/packages/versions/new.json', (request, response) {
expect(request.headers.value('authorization'),
@@ -63,6 +65,7 @@
.scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
server.handle('POST', '/token', (request, response) {
return consumeInputStream(request.inputStream).transform((bytes) {
@@ -102,6 +105,7 @@
.scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
expectLater(pub.nextErrLine(), equals("Pub's authorization to upload "
"packages has expired and can't be automatically refreshed."));
@@ -129,6 +133,7 @@
]).scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
authorizePub(pub, server, "new access token");
server.handle('GET', '/packages/versions/new.json', (request, response) {
@@ -148,8 +153,12 @@
void authorizePub(ScheduledProcess pub, ScheduledServer server,
[String accessToken="access token"]) {
- expectLater(pub.nextLine(), equals('Pub needs your '
- 'authorization to upload packages on your behalf.'));
+ // TODO(rnystrom): The confirm line is run together with this one because
+ // in normal usage, the user will have entered a newline on stdin which
+ // gets echoed to the terminal. Do something better here?
+ expectLater(pub.nextLine(), equals(
+ 'Looks great! Are you ready to upload your package (y/n)? '
+ 'Pub needs your authorization to upload packages on your behalf.'));
expectLater(pub.nextLine().chain((line) {
var match = new RegExp(r'[?&]redirect_uri=([0-9a-zA-Z%+-]+)[$&]')
diff --git a/utils/tests/pub/path/path_posix_test.dart b/utils/tests/pub/path/path_posix_test.dart
index a34c32d..60ed65a 100644
--- a/utils/tests/pub/path/path_posix_test.dart
+++ b/utils/tests/pub/path/path_posix_test.dart
@@ -33,6 +33,30 @@
expect(builder.extension(r'a.b\c'), r'.b\c');
});
+ test('rootPrefix', () {
+ expect(builder.rootPrefix(''), '');
+ expect(builder.rootPrefix('a'), '');
+ expect(builder.rootPrefix('a/b'), '');
+ expect(builder.rootPrefix('/a/c'), '/');
+ expect(builder.rootPrefix('/'), '/');
+ });
+
+ test('dirname', () {
+ expect(builder.dirname(''), '.');
+ expect(builder.dirname('a'), '.');
+ expect(builder.dirname('a/b'), 'a');
+ expect(builder.dirname('a/b/c'), 'a/b');
+ expect(builder.dirname('a/b.c'), 'a');
+ expect(builder.dirname('a/'), 'a');
+ expect(builder.dirname('a/.'), 'a');
+ expect(builder.dirname(r'a\b/c'), r'a\b');
+ expect(builder.dirname('/a'), '/');
+ expect(builder.dirname('/'), '/');
+ expect(builder.dirname('a/b/'), 'a/b');
+ expect(builder.dirname(r'a/b\c'), 'a');
+ expect(builder.dirname('a//'), 'a/');
+ });
+
test('basename', () {
expect(builder.basename(''), '');
expect(builder.basename('a'), 'a');
@@ -41,7 +65,13 @@
expect(builder.basename('a/b.c'), 'b.c');
expect(builder.basename('a/'), '');
expect(builder.basename('a/.'), '.');
+ expect(builder.basename(r'a\b/c'), 'c');
+ expect(builder.basename('/a'), 'a');
+ // TODO(nweiz): this should actually return '/'
+ expect(builder.basename('/'), '');
+ expect(builder.basename('a/b/'), '');
expect(builder.basename(r'a/b\c'), r'b\c');
+ expect(builder.basename('a//'), '');
});
test('basenameWithoutExtension', () {
@@ -104,10 +134,45 @@
});
test('ignores parts before an absolute path', () {
+ expect(builder.join('a', '/', 'b', 'c'), '/b/c');
expect(builder.join('a', '/b', '/c', 'd'), '/c/d');
expect(builder.join('a', r'c:\b', 'c', 'd'), r'a/c:\b/c/d');
expect(builder.join('a', r'\\b', 'c', 'd'), r'a/\\b/c/d');
});
+
+ test('ignores trailing nulls', () {
+ expect(builder.join('a', null), equals('a'));
+ expect(builder.join('a', 'b', 'c', null, null), equals('a/b/c'));
+ });
+
+ test('disallows intermediate nulls', () {
+ expect(() => builder.join('a', null, 'b'), throwsArgumentError);
+ expect(() => builder.join(null, 'a'), throwsArgumentError);
+ });
+ });
+
+ group('split', () {
+ test('simple cases', () {
+ expect(builder.split(''), []);
+ expect(builder.split('.'), ['.']);
+ expect(builder.split('..'), ['..']);
+ expect(builder.split('foo'), equals(['foo']));
+ expect(builder.split('foo/bar.txt'), equals(['foo', 'bar.txt']));
+ expect(builder.split('foo/bar/baz'), equals(['foo', 'bar', 'baz']));
+ expect(builder.split('foo/../bar/./baz'),
+ equals(['foo', '..', 'bar', '.', 'baz']));
+ expect(builder.split('foo//bar///baz'), equals(['foo', 'bar', 'baz']));
+ expect(builder.split('foo/\\/baz'), equals(['foo', '\\', 'baz']));
+ expect(builder.split('.'), equals(['.']));
+ expect(builder.split(''), equals([]));
+ expect(builder.split('foo/'), equals(['foo']));
+ expect(builder.split('//'), equals(['/']));
+ });
+
+ test('includes the root for absolute paths', () {
+ expect(builder.split('/foo/bar/baz'), equals(['/', 'foo', 'bar', 'baz']));
+ expect(builder.split('/'), equals(['/']));
+ });
});
group('normalize', () {
@@ -211,15 +276,10 @@
group('from relative root', () {
var r = new path.Builder(style: path.Style.posix, root: 'foo/bar');
- // These tests rely on the current working directory, so don't do the
- // right thing if you run them on the wrong platform.
- if (io.Platform.operatingSystem != 'windows') {
- test('given absolute path', () {
- var b = new path.Builder(style: path.Style.posix);
- expect(r.relative('/'), b.join(b.relative('/'), '../..'));
- expect(r.relative('/a/b'), b.join(b.relative('/'), '../../a/b'));
- });
- }
+ test('given absolute path', () {
+ expect(r.relative('/'), equals('/'));
+ expect(r.relative('/a/b'), equals('/a/b'));
+ });
test('given relative path', () {
// The path is considered relative to the root, so it basically just
@@ -238,6 +298,23 @@
var r = new path.Builder(style: path.Style.posix, root: '/dir.ext');
expect(r.relative('/dir.ext/file'), 'file');
});
+
+ test('with a root parameter', () {
+ expect(builder.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(builder.relative('..', from: '/foo/bar'), equals('../../root'));
+ expect(builder.relative('/foo/bar/baz', from: 'foo/bar'),
+ equals('../../../../foo/bar/baz'));
+ expect(builder.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
+
+ test('with a root parameter and a relative root', () {
+ var r = new path.Builder(style: path.Style.posix, root: 'relative/root');
+ expect(r.relative('/foo/bar/baz', from: '/foo/bar'), equals('baz'));
+ expect(() => r.relative('..', from: '/foo/bar'), throwsArgumentError);
+ expect(r.relative('/foo/bar/baz', from: 'foo/bar'),
+ equals('/foo/bar/baz'));
+ expect(r.relative('..', from: 'foo/bar'), equals('../../..'));
+ });
});
group('resolve', () {
diff --git a/utils/tests/pub/path/path_windows_test.dart b/utils/tests/pub/path/path_windows_test.dart
index 1861a67..fdad9b0 100644
--- a/utils/tests/pub/path/path_windows_test.dart
+++ b/utils/tests/pub/path/path_windows_test.dart
@@ -35,6 +35,35 @@
expect(builder.extension(r'a.b/c'), r'');
});
+ test('rootPrefix', () {
+ expect(builder.rootPrefix(''), '');
+ expect(builder.rootPrefix('a'), '');
+ expect(builder.rootPrefix(r'a\b'), '');
+ expect(builder.rootPrefix(r'C:\a\c'), r'C:\');
+ expect(builder.rootPrefix('C:\\'), r'C:\');
+ expect(builder.rootPrefix('C:/'), 'C:/');
+
+ // TODO(nweiz): enable this once issue 7323 is fixed.
+ // expect(builder.rootPrefix(r'\\server\a\b'), r'\\server\');
+ });
+
+ test('dirname', () {
+ expect(builder.dirname(r''), '.');
+ expect(builder.dirname(r'a'), '.');
+ expect(builder.dirname(r'a\b'), 'a');
+ expect(builder.dirname(r'a\b\c'), r'a\b');
+ expect(builder.dirname(r'a\b.c'), 'a');
+ expect(builder.dirname(r'a\'), 'a');
+ expect(builder.dirname('a/'), 'a');
+ expect(builder.dirname(r'a\.'), 'a');
+ expect(builder.dirname(r'a\b/c'), r'a\b');
+ expect(builder.dirname(r'C:\a'), r'C:\');
+ expect(builder.dirname('C:\\'), r'C:\');
+ expect(builder.dirname(r'a\b\'), r'a\b');
+ expect(builder.dirname(r'a/b\c'), 'a/b');
+ expect(builder.dirname(r'a\\'), r'a\');
+ });
+
test('basename', () {
expect(builder.basename(r''), '');
expect(builder.basename(r'a'), 'a');
@@ -45,6 +74,12 @@
expect(builder.basename(r'a/'), '');
expect(builder.basename(r'a\.'), '.');
expect(builder.basename(r'a\b/c'), r'c');
+ expect(builder.basename(r'C:\a'), 'a');
+ // TODO(nweiz): this should actually return 'C:\'
+ expect(builder.basename(r'C:\'), '');
+ expect(builder.basename(r'a\b\'), '');
+ expect(builder.basename(r'a/b\c'), 'c');
+ expect(builder.basename(r'a\\'), '');
});
test('basenameWithoutExtension', () {
@@ -123,6 +158,46 @@
expect(builder.join('a', r'c:\b', 'c', 'd'), r'c:\b\c\d');
expect(builder.join('a', r'\\b', r'\\c', 'd'), r'\\c\d');
});
+
+ test('ignores trailing nulls', () {
+ expect(builder.join('a', null), equals('a'));
+ expect(builder.join('a', 'b', 'c', null, null), equals(r'a\b\c'));
+ });
+
+ test('disallows intermediate nulls', () {
+ expect(() => builder.join('a', null, 'b'), throwsArgumentError);
+ expect(() => builder.join(null, 'a'), throwsArgumentError);
+ });
+ });
+
+ group('split', () {
+ test('simple cases', () {
+ expect(builder.split(''), []);
+ expect(builder.split('.'), ['.']);
+ expect(builder.split('..'), ['..']);
+ expect(builder.split('foo'), equals(['foo']));
+ expect(builder.split(r'foo\bar.txt'), equals(['foo', 'bar.txt']));
+ expect(builder.split(r'foo\bar/baz'), equals(['foo', 'bar', 'baz']));
+ expect(builder.split(r'foo\..\bar\.\baz'),
+ equals(['foo', '..', 'bar', '.', 'baz']));
+ expect(builder.split(r'foo\\bar\\\baz'), equals(['foo', 'bar', 'baz']));
+ expect(builder.split(r'foo\/\baz'), equals(['foo', 'baz']));
+ expect(builder.split('.'), equals(['.']));
+ expect(builder.split(''), equals([]));
+ expect(builder.split('foo/'), equals(['foo']));
+ expect(builder.split(r'C:\'), equals([r'C:\']));
+ });
+
+ test('includes the root for absolute paths', () {
+ expect(builder.split(r'C:\foo\bar\baz'),
+ equals([r'C:\', 'foo', 'bar', 'baz']));
+ expect(builder.split(r'C:\\'), equals([r'C:\']));
+
+ // TODO(nweiz): enable these once issue 7323 is fixed.
+ // expect(builder.split(r'\\server\foo\bar\baz'),
+ // equals([r'\\server\', 'foo', 'bar', 'baz']));
+ // expect(builder.split(r'\\server\'), equals([r'\\server\']));
+ });
});
group('normalize', () {
@@ -225,21 +300,10 @@
group('from relative root', () {
var r = new path.Builder(style: path.Style.windows, root: r'foo\bar');
- // These tests rely on the current working directory, so don't do the
- // right thing if you run them on the wrong platform.
- if (io.Platform.operatingSystem == 'windows') {
- test('given absolute path', () {
- var b = new path.Builder(style: path.Style.windows);
- // TODO(rnystrom): Use a path method here to get the root prefix
- // when one exists.
- var drive = path.current.substring(0, 3);
- expect(r.relative(drive), b.join(b.relative(drive), r'..\..'));
- expect(r.relative(b.join(drive, r'a\b')),
- b.join(b.relative(drive), r'..\..\a\b'));
-
- // TODO(rnystrom): Test behavior when drive letters differ.
- });
- }
+ test('given absolute path', () {
+ expect(r.relative(r'C:\'), equals(r'C:\'));
+ expect(r.relative(r'C:\a\b'), equals(r'C:\a\b'));
+ });
test('given relative path', () {
// The path is considered relative to the root, so it basically just
@@ -259,6 +323,26 @@
expect(r.relative(r'C:\dir.ext\file'), 'file');
});
+ test('with a root parameter', () {
+ expect(builder.relative(r'C:\foo\bar\baz', from: r'C:\foo\bar'),
+ equals('baz'));
+ expect(builder.relative('..', from: r'C:\foo\bar'),
+ equals(r'..\..\root'));
+ expect(builder.relative('..', from: r'D:\foo\bar'), equals(r'C:\root'));
+ expect(builder.relative(r'C:\foo\bar\baz', from: r'foo\bar'),
+ equals(r'..\..\..\..\foo\bar\baz'));
+ expect(builder.relative('..', from: r'foo\bar'), equals(r'..\..\..'));
+ });
+
+ test('with a root parameter and a relative root', () {
+ var r = new path.Builder(style: path.Style.windows, root: r'relative\root');
+ expect(r.relative(r'C:\foo\bar\baz', from: r'C:\foo\bar'), equals('baz'));
+ expect(() => r.relative('..', from: r'C:\foo\bar'), throwsArgumentError);
+ expect(r.relative(r'C:\foo\bar\baz', from: r'foo\bar'),
+ equals(r'C:\foo\bar\baz'));
+ expect(r.relative('..', from: r'foo\bar'), equals(r'..\..\..'));
+ });
+
test('given absolute with different root prefix', () {
expect(builder.relative(r'D:\a\b'), r'D:\a\b');
expect(builder.relative(r'\\a\b'), r'\\a\b');
diff --git a/utils/tests/pub/pub.status b/utils/tests/pub/pub.status
index de0fba4..4e6f9e3 100644
--- a/utils/tests/pub/pub.status
+++ b/utils/tests/pub/pub.status
@@ -9,7 +9,3 @@
# Pub only runs on the standalone VM, not the browser.
[ $runtime == drt || $runtime == dartium || $runtime == opera ]
*: Skip
-
-[ $system == macos ]
-validator_test: Fail # Issue 7330
-
diff --git a/utils/tests/pub/pub_lish_test.dart b/utils/tests/pub/pub_lish_test.dart
index a947601..b787ec9 100644
--- a/utils/tests/pub/pub_lish_test.dart
+++ b/utils/tests/pub/pub_lish_test.dart
@@ -53,6 +53,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
handleUpload(server);
@@ -63,7 +65,12 @@
response.outputStream.close();
});
- expectLater(pub.nextLine(), equals('Package test_pkg 1.0.0 uploaded!'));
+ // TODO(rnystrom): The confirm line is run together with this one because
+ // in normal usage, the user will have entered a newline on stdin which
+ // gets echoed to the terminal. Do something better here?
+ expectLater(pub.nextLine(), equals(
+ 'Looks great! Are you ready to upload your package (y/n)?'
+ ' Package test_pkg 1.0.0 uploaded!'));
pub.shouldExit(0);
run();
@@ -77,6 +84,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
server.handle('GET', '/packages/versions/new.json', (request, response) {
response.statusCode = 401;
response.headers.set('www-authenticate', 'Bearer error="invalid_token",'
@@ -89,10 +98,13 @@
expectLater(pub.nextErrLine(), equals('OAuth2 authorization failed (your '
'token sucks).'));
- expectLater(pub.nextLine(), equals('Pub needs your authorization to upload '
- 'packages on your behalf.'));
+ // TODO(rnystrom): The confirm line is run together with this one because
+ // in normal usage, the user will have entered a newline on stdin which
+ // gets echoed to the terminal. Do something better here?
+ expectLater(pub.nextLine(), equals(
+ 'Looks great! Are you ready to upload your package (y/n)? '
+ 'Pub needs your authorization to upload packages on your behalf.'));
pub.kill();
-
run();
});
@@ -102,12 +114,12 @@
dir(appPath, [pubspec(package)]).scheduleCreate();
var server = new ScheduledServer();
- credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
- server.ignore('GET', '/packages/versions/new.json');
pub.shouldExit(1);
- expectLater(pub.remainingStderr(), contains("Package validation failed."));
+ expectLater(pub.remainingStderr(),
+ contains("Sorry, your package is missing a requirement and can't be "
+ "published yet."));
run();
});
@@ -118,9 +130,7 @@
dir(appPath, [pubspec(package)]).scheduleCreate();
var server = new ScheduledServer();
- credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
- server.ignore('GET', '/packages/versions/new.json');
pub.writeLine("n");
pub.shouldExit(1);
@@ -160,6 +170,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
server.handle('GET', '/packages/versions/new.json', (request, response) {
response.statusCode = 400;
response.outputStream.writeString(JSON.stringify({
@@ -179,6 +191,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
server.handle('GET', '/packages/versions/new.json', (request, response) {
response.outputStream.writeString('{not json');
response.outputStream.close();
@@ -196,6 +210,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
var body = {
'fields': {
'field1': 'value1',
@@ -216,6 +232,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
var body = {
'url': 12,
'fields': {
@@ -237,6 +255,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
var body = {'url': 'http://example.com/upload'};
handleUploadForm(server, body);
expectLater(pub.nextErrLine(), equals('Invalid server response:'));
@@ -251,6 +271,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
var body = {'url': 'http://example.com/upload', 'fields': 12};
handleUploadForm(server, body);
expectLater(pub.nextErrLine(), equals('Invalid server response:'));
@@ -265,6 +287,8 @@
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+ confirmPublish(pub);
+
var body = {
'url': 'http://example.com/upload',
'fields': {'field': 12}
@@ -281,6 +305,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
server.handle('POST', '/upload', (request, response) {
@@ -303,6 +329,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
server.handle('POST', '/upload', (request, response) {
@@ -320,6 +348,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
handleUpload(server);
@@ -341,6 +371,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
handleUpload(server);
@@ -360,6 +392,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
handleUpload(server);
@@ -381,6 +415,8 @@
var server = new ScheduledServer();
credentialsFile(server, 'access token').scheduleCreate();
var pub = startPubLish(server);
+
+ confirmPublish(pub);
handleUploadForm(server);
handleUpload(server);
diff --git a/utils/tests/pub/test_pub.dart b/utils/tests/pub/test_pub.dart
index aa8e879..80137a0 100644
--- a/utils/tests/pub/test_pub.dart
+++ b/utils/tests/pub/test_pub.dart
@@ -629,6 +629,23 @@
return new ScheduledProcess("pub lish", process);
}
+/// Handles the beginning confirmation process for uploading a packages.
+/// Ensures that the right output is shown and then enters "y" to confirm the
+/// upload.
+void confirmPublish(ScheduledProcess pub) {
+ // TODO(rnystrom): This is overly specific and inflexible regarding different
+ // test packages. Should validate this a little more loosely.
+ expectLater(pub.nextLine(), equals('Publishing "test_pkg" 1.0.0:'));
+ expectLater(pub.nextLine(), equals("|-- LICENSE"));
+ expectLater(pub.nextLine(), equals("|-- lib"));
+ expectLater(pub.nextLine(), equals("| '-- test_pkg.dart"));
+ expectLater(pub.nextLine(), equals("'-- pubspec.yaml"));
+ expectLater(pub.nextLine(), equals(""));
+
+ pub.writeLine("y");
+}
+
+
/// Calls [fn] with appropriately modified arguments to run a pub process. [fn]
/// should have the same signature as [startProcess], except that the returned
/// [Future] may have a type other than [Process].