Bring the YAML package's style up to modern standards.
R=jmesserly@google.com, rnystrom@google.com
Review URL: https://codereview.chromium.org//274953002
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/yaml@36386 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..695e069
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+## 0.9.0+1
+
+* The `YamlMap` class is deprecated. In a future version, maps returned by
+ `loadYaml` and `loadYamlStream` will be Dart `HashMap`s with a custom equality
+ operation.
diff --git a/README.md b/README.md
index 47a5419..531162f 100644
--- a/README.md
+++ b/README.md
@@ -3,21 +3,27 @@
Use `loadYaml` to load a single document, or `loadYamlStream` to load a
stream of documents. For example:
- import 'package:yaml/yaml.dart';
- main() {
- var doc = loadYaml("YAML: YAML Ain't Markup Language");
- print(doc['YAML']);
- }
+```dart
+import 'package:yaml/yaml.dart';
+
+main() {
+ var doc = loadYaml("YAML: YAML Ain't Markup Language");
+ print(doc['YAML']);
+}
+```
This library currently doesn't support dumping to YAML. You should use
`JSON.encode` from `dart:convert` instead:
- import 'dart:convert';
- import 'package:yaml/yaml.dart';
- main() {
- var doc = loadYaml("YAML: YAML Ain't Markup Language");
- print(JSON.encode(doc));
- }
+```dart
+import 'dart:convert';
+import 'package:yaml/yaml.dart';
+
+main() {
+ var doc = loadYaml("YAML: YAML Ain't Markup Language");
+ print(JSON.encode(doc));
+}
+```
The source code for this package is at <http://code.google.com/p/dart>.
Please file issues at <http://dartbug.com>. Other questions or comments can be
diff --git a/lib/src/composer.dart b/lib/src/composer.dart
index 40b7667..3178096 100644
--- a/lib/src/composer.dart
+++ b/lib/src/composer.dart
@@ -2,7 +2,7 @@
// 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 composer;
+library yaml.composer;
import 'model.dart';
import 'visitor.dart';
@@ -19,10 +19,11 @@
/// that anchor.
final _anchors = <String, Node>{};
- /// The next id to use for the represenation graph's anchors. The spec doesn't
- /// use anchors in the representation graph, but we do so that the constructor
- /// can ensure that the same node in the representation graph produces the
- /// same native object.
+ /// The next id to use for the represenation graph's anchors.
+ ///
+ /// The spec doesn't use anchors in the representation graph, but we do so
+ /// that the constructor can ensure that the same node in the representation
+ /// graph produces the same native object.
var _idCounter = 0;
Composer(this._root);
@@ -33,13 +34,15 @@
/// Returns the anchor to which an alias node refers.
Node visitAlias(AliasNode alias) {
if (!_anchors.containsKey(alias.anchor)) {
- throw new YamlException("no anchor for alias ${alias.anchor}");
+ throw new YamlException("No anchor for alias ${alias.anchor}.");
}
return _anchors[alias.anchor];
}
/// Parses a scalar node according to its tag, or auto-detects the type if no
- /// tag exists. Currently this only supports the YAML core type schema.
+ /// tag exists.
+ ///
+ /// Currently this only supports the YAML core type schema.
Node visitScalar(ScalarNode scalar) {
if (scalar.tag.name == "!") {
return setAnchor(scalar, parseString(scalar.content));
@@ -51,30 +54,31 @@
return setAnchor(scalar, parseString(scalar.content));
}
- // TODO(nweiz): support the full YAML type repository
- var tagParsers = {
- 'null': parseNull, 'bool': parseBool, 'int': parseInt,
- 'float': parseFloat, 'str': parseString
- };
+ var result = _parseByTag(scalar);
+ if (result != null) return setAnchor(scalar, result);
+ throw new YamlException('Invalid literal for ${scalar.tag}: '
+ '"${scalar.content}".');
+ }
- for (var key in tagParsers.keys) {
- if (scalar.tag.name != Tag.yaml(key)) continue;
- var result = tagParsers[key](scalar.content);
- if (result != null) return setAnchor(scalar, result);
- throw new YamlException('invalid literal for $key: "${scalar.content}"');
+ ScalarNode _parseByTag(ScalarNode scalar) {
+ switch (scalar.tag.name) {
+ case "null": return parseNull(scalar.content);
+ case "bool": return parseBool(scalar.content);
+ case "int": return parseInt(scalar.content);
+ case "float": return parseFloat(scalar.content);
+ case "str": return parseString(scalar.content);
}
-
- throw new YamlException('undefined tag: "${scalar.tag.name}"');
+ throw new YamlException('Undefined tag: ${scalar.tag}.');
}
/// Assigns a tag to the sequence and recursively composes its contents.
Node visitSequence(SequenceNode seq) {
var tagName = seq.tag.name;
if (tagName != "!" && tagName != "?" && tagName != Tag.yaml("seq")) {
- throw new YamlException("invalid tag for sequence: ${tagName}");
+ throw new YamlException("Invalid tag for sequence: ${seq.tag}.");
}
- var result = setAnchor(seq, new SequenceNode(Tag.yaml("seq"), null));
+ var result = setAnchor(seq, new SequenceNode(Tag.yaml('seq'), null));
result.content = super.visitSequence(seq);
return result;
}
@@ -83,10 +87,10 @@
Node visitMapping(MappingNode map) {
var tagName = map.tag.name;
if (tagName != "!" && tagName != "?" && tagName != Tag.yaml("map")) {
- throw new YamlException("invalid tag for mapping: ${tagName}");
+ throw new YamlException("Invalid tag for mapping: ${map.tag}.");
}
- var result = setAnchor(map, new MappingNode(Tag.yaml("map"), null));
+ var result = setAnchor(map, new MappingNode(Tag.yaml('map'), null));
result.content = super.visitMapping(map);
return result;
}
diff --git a/lib/src/constructor.dart b/lib/src/constructor.dart
index 428c476..116809d 100644
--- a/lib/src/constructor.dart
+++ b/lib/src/constructor.dart
@@ -2,7 +2,7 @@
// 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 constructor;
+library yaml.constructor;
import 'model.dart';
import 'visitor.dart';
diff --git a/lib/src/deep_equals.dart b/lib/src/deep_equals.dart
index 1e1f7ed..68fb236 100644
--- a/lib/src/deep_equals.dart
+++ b/lib/src/deep_equals.dart
@@ -2,75 +2,80 @@
// 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 deep_equals;
+library yaml.deep_equals;
/// Returns whether two objects are structurally equivalent.
///
/// This considers `NaN` values to be equivalent. It also handles
/// self-referential structures.
-bool deepEquals(obj1, obj2, [List parents1, List parents2]) {
- if (identical(obj1, obj2)) return true;
- if (parents1 == null) {
- parents1 = [];
- parents2 = [];
- }
+bool deepEquals(obj1, obj2) => new _DeepEquals().equals(obj1, obj2);
- // parents1 and parents2 are guaranteed to be the same size.
- for (var i = 0; i < parents1.length; i++) {
- var loop1 = identical(obj1, parents1[i]);
- var loop2 = identical(obj2, parents2[i]);
- // If both structures loop in the same place, they're equal at that point in
- // the structure. If one loops and the other doesn't, they're not equal.
- if (loop1 && loop2) return true;
- if (loop1 || loop2) return false;
- }
+/// A class that provides access to the list of parent objects used for loop
+/// detection.
+class _DeepEquals {
+ final _parents1 = [];
+ final _parents2 = [];
- parents1.add(obj1);
- parents2.add(obj2);
- try {
- if (obj1 is List && obj2 is List) {
- return _listEquals(obj1, obj2, parents1, parents2);
- } else if (obj1 is Map && obj2 is Map) {
- return _mapEquals(obj1, obj2, parents1, parents2);
- } else if (obj1 is num && obj2 is num) {
- return _numEquals(obj1, obj2);
- } else {
- return obj1 == obj2;
+ /// Returns whether [obj1] and [obj2] are structurally equivalent.
+ bool equals(obj1, obj2) {
+ // _parents1 and _parents2 are guaranteed to be the same size.
+ for (var i = 0; i < _parents1.length; i++) {
+ var loop1 = identical(obj1, _parents1[i]);
+ var loop2 = identical(obj2, _parents2[i]);
+ // If both structures loop in the same place, they're equal at that point
+ // in the structure. If one loops and the other doesn't, they're not
+ // equal.
+ if (loop1 && loop2) return true;
+ if (loop1 || loop2) return false;
}
- } finally {
- parents1.removeLast();
- parents2.removeLast();
- }
-}
-/// Returns whether [list1] and [list2] are structurally equal.
-bool _listEquals(List list1, List list2, List parents1, List parents2) {
- if (list1.length != list2.length) return false;
-
- for (var i = 0; i < list1.length; i++) {
- if (!deepEquals(list1[i], list2[i], parents1, parents2)) return false;
+ _parents1.add(obj1);
+ _parents2.add(obj2);
+ try {
+ if (obj1 is List && obj2 is List) {
+ return _listEquals(obj1, obj2);
+ } else if (obj1 is Map && obj2 is Map) {
+ return _mapEquals(obj1, obj2);
+ } else if (obj1 is num && obj2 is num) {
+ return _numEquals(obj1, obj2);
+ } else {
+ return obj1 == obj2;
+ }
+ } finally {
+ _parents1.removeLast();
+ _parents2.removeLast();
+ }
}
- return true;
-}
+ /// Returns whether [list1] and [list2] are structurally equal.
+ bool _listEquals(List list1, List list2) {
+ if (list1.length != list2.length) return false;
-/// Returns whether [map1] and [map2] are structurally equal.
-bool _mapEquals(Map map1, Map map2, List parents1, List parents2) {
- if (map1.length != map2.length) return false;
+ for (var i = 0; i < list1.length; i++) {
+ if (!equals(list1[i], list2[i])) return false;
+ }
- for (var key in map1.keys) {
- if (!map2.containsKey(key)) return false;
- if (!deepEquals(map1[key], map2[key], parents1, parents2)) return false;
+ return true;
}
- return true;
-}
+ /// Returns whether [map1] and [map2] are structurally equal.
+ bool _mapEquals(Map map1, Map map2) {
+ if (map1.length != map2.length) return false;
-/// Returns whether two numbers are equivalent.
-///
-/// This differs from `n1 == n2` in that it considers `NaN` to be equal to
-/// itself.
-bool _numEquals(num n1, num n2) {
- if (n1.isNaN && n2.isNaN) return true;
- return n1 == n2;
+ for (var key in map1.keys) {
+ if (!map2.containsKey(key)) return false;
+ if (!equals(map1[key], map2[key])) return false;
+ }
+
+ return true;
+ }
+
+ /// Returns whether two numbers are equivalent.
+ ///
+ /// This differs from `n1 == n2` in that it considers `NaN` to be equal to
+ /// itself.
+ bool _numEquals(num n1, num n2) {
+ if (n1.isNaN && n2.isNaN) return true;
+ return n1 == n2;
+ }
}
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 37247f0..564cac6 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -5,39 +5,38 @@
/// This file contains the node classes for the internal representations of YAML
/// documents. These nodes are used for both the serialization tree and the
/// representation graph.
-library model;
+library yaml.model;
import 'parser.dart';
import 'utils.dart';
import 'visitor.dart';
import 'yaml_exception.dart';
+/// The prefix for tag types defined by the YAML spec.
+const _YAML_URI_PREFIX = "tag:yaml.org,2002:";
+
/// A tag that indicates the type of a YAML node.
class Tag {
- // TODO(nweiz): it would better match the semantics of the spec if there were
- // a singleton instance of this class for each tag.
-
- static const SCALAR_KIND = 0;
- static const SEQUENCE_KIND = 1;
- static const MAPPING_KIND = 2;
-
- static const String YAML_URI_PREFIX = 'tag:yaml.org,2002:';
-
/// The name of the tag, either a URI or a local tag beginning with "!".
final String name;
- /// The kind of the tag: SCALAR_KIND, SEQUENCE_KIND, or MAPPING_KIND.
- final int kind;
-
- Tag(this.name, this.kind);
-
- Tag.scalar(String name) : this(name, SCALAR_KIND);
- Tag.sequence(String name) : this(name, SEQUENCE_KIND);
- Tag.mapping(String name) : this(name, MAPPING_KIND);
+ /// The kind of the tag.
+ final TagKind kind;
/// Returns the standard YAML tag URI for [type].
static String yaml(String type) => "tag:yaml.org,2002:$type";
+ const Tag(this.name, this.kind);
+
+ const Tag.scalar(String name)
+ : this(name, TagKind.SCALAR);
+
+ const Tag.sequence(String name)
+ : this(name, TagKind.SEQUENCE);
+
+ const Tag.mapping(String name)
+ : this(name, TagKind.MAPPING);
+
/// Two tags are equal if their URIs are equal.
operator ==(other) {
if (other is! Tag) return false;
@@ -45,8 +44,8 @@
}
String toString() {
- if (name.startsWith(YAML_URI_PREFIX)) {
- return '!!${name.substring(YAML_URI_PREFIX.length)}';
+ if (name.startsWith(_YAML_URI_PREFIX)) {
+ return '!!${name.substring(_YAML_URI_PREFIX.length)}';
} else {
return '!<$name>';
}
@@ -55,6 +54,24 @@
int get hashCode => name.hashCode;
}
+/// An enum for kinds of tags.
+class TagKind {
+ /// A tag indicating that the value is a scalar.
+ static const SCALAR = const TagKind._("scalar");
+
+ /// A tag indicating that the value is a sequence.
+ static const SEQUENCE = const TagKind._("sequence");
+
+ /// A tag indicating that the value is a mapping.
+ static const MAPPING = const TagKind._("mapping");
+
+ final String name;
+
+ const TagKind._(this.name);
+
+ String toString() => name;
+}
+
/// The abstract class for YAML nodes.
abstract class Node {
/// Every YAML node has a tag that describes its type.
@@ -185,7 +202,7 @@
return '"${escapedValue.join()}"';
}
- throw new YamlException("unknown scalar value: $value");
+ throw new YamlException('Unknown scalar value: "$value".');
}
String toString() => '$tag "$content"';
diff --git a/lib/src/parser.dart b/lib/src/parser.dart
index 162fef4..d1e20ff 100644
--- a/lib/src/parser.dart
+++ b/lib/src/parser.dart
@@ -2,7 +2,7 @@
// 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 parser;
+library yaml.parser;
import 'dart:collection';
@@ -407,23 +407,24 @@
error(String message) {
// Line and column should be one-based.
throw new SyntaxError(_line + 1, _column + 1,
- "$message (in $_farthestContext)");
+ "$message (in $_farthestContext).");
}
/// If [result] is falsey, throws an error saying that [expected] was
/// expected.
expect(result, String expected) {
if (truth(result)) return result;
- error("expected $expected");
+ error("Expected $expected");
}
/// Throws an error saying that the parse failed. Uses [_farthestLine],
- /// [_farthestColumn], and [_farthestContext] to provide additional information.
+ /// [_farthestColumn], and [_farthestContext] to provide additional
+ /// information.
parseFailed() {
- var message = "invalid YAML in $_farthestContext";
+ var message = "Invalid YAML in $_farthestContext";
var extraError = _errorAnnotations[_farthestPos];
if (extraError != null) message = "$message ($extraError)";
- throw new SyntaxError(_farthestLine + 1, _farthestColumn + 1, message);
+ throw new SyntaxError(_farthestLine + 1, _farthestColumn + 1, "$message.");
}
/// Returns the number of spaces after the current position.
@@ -788,7 +789,7 @@
case BLOCK_KEY:
case FLOW_KEY:
return s_separateInLine();
- default: throw 'invalid context "$ctx"';
+ default: throw 'Invalid context "$ctx".';
}
}
@@ -1014,7 +1015,7 @@
var char = peek();
var indicator = indicatorType(char);
if (indicator == C_RESERVED) {
- error("reserved indicators can't start a plain scalar");
+ error("Reserved indicators can't start a plain scalar");
}
var match = (isNonSpace(char) && indicator == null) ||
((indicator == C_MAPPING_KEY ||
@@ -1037,7 +1038,7 @@
case FLOW_KEY:
// 129
return isNonSpace(char) && !isFlowIndicator(char);
- default: throw 'invalid context "$ctx"';
+ default: throw 'Invalid context "$ctx".';
}
}
@@ -1066,7 +1067,7 @@
case BLOCK_KEY:
case FLOW_KEY:
return ns_plainOneLine(ctx);
- default: throw 'invalid context "$ctx"';
+ default: throw 'Invalid context "$ctx".';
}
});
});
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index a87555d..64cad47 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -2,38 +2,33 @@
// 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 utils;
+library yaml.utils;
-/// Returns the hash code for [obj]. This includes null, true, false, maps, and
-/// lists. Also handles self-referential structures.
-int hashCodeFor(obj, [List parents]) {
- if (parents == null) {
- parents = [];
- } else if (parents.any((p) => identical(p, obj))) {
- return -1;
- }
+import 'package:collection/collection.dart';
- parents.add(obj);
- try {
- if (obj == null) return 0;
- if (obj == true) return 1;
- if (obj == false) return 2;
- if (obj is Map) {
- return hashCodeFor(obj.keys, parents) ^
- hashCodeFor(obj.values, parents);
- }
- if (obj is Iterable) {
- // This is probably a really bad hash function, but presumably we'll get
- // this in the standard library before it actually matters.
- int hash = 0;
- for (var e in obj) {
- hash ^= hashCodeFor(e, parents);
+/// Returns a hash code for [obj] such that structurally equivalent objects
+/// will have the same hash code.
+///
+/// This supports deep equality for maps and lists, including those with
+/// self-referential structures.
+int hashCodeFor(obj) {
+ var parents = [];
+
+ _hashCodeFor(value) {
+ if (parents.any((parent) => identical(parent, value))) return -1;
+
+ parents.add(value);
+ try {
+ if (value is Map) {
+ return _hashCodeFor(value.keys) ^ _hashCodeFor(value.values);
+ } else if (value is Iterable) {
+ return const IterableEquality().hash(value.map(hashCodeFor));
}
- return hash;
+ return value.hashCode;
+ } finally {
+ parents.removeLast();
}
- return obj.hashCode;
- } finally {
- parents.removeLast();
}
-}
+ return _hashCodeFor(obj);
+}
diff --git a/lib/src/visitor.dart b/lib/src/visitor.dart
index 4a9c54f..7095268 100644
--- a/lib/src/visitor.dart
+++ b/lib/src/visitor.dart
@@ -2,7 +2,7 @@
// 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 visitor;
+library yaml.visitor;
import 'model.dart';
import 'yaml_map.dart';
diff --git a/lib/src/yaml_exception.dart b/lib/src/yaml_exception.dart
index 66697a1..a863274 100644
--- a/lib/src/yaml_exception.dart
+++ b/lib/src/yaml_exception.dart
@@ -2,7 +2,7 @@
// 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 yaml_exception;
+library yaml.exception;
/// An error thrown by the YAML processor.
class YamlException implements Exception {
diff --git a/lib/src/yaml_map.dart b/lib/src/yaml_map.dart
index 5e3e20a..ff3da36 100644
--- a/lib/src/yaml_map.dart
+++ b/lib/src/yaml_map.dart
@@ -2,86 +2,39 @@
// 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 yaml_map;
+library yaml.map;
+
+import 'dart:collection';
+
+import 'package:collection/collection.dart';
import 'deep_equals.dart';
import 'utils.dart';
-/// This class wraps behaves almost identically to the normal Dart Map
+/// This class behaves almost identically to the normal Dart [Map]
/// implementation, with the following differences:
///
-/// * It allows null, NaN, boolean, list, and map keys.
+/// * It allows NaN, list, and map keys.
/// * It defines `==` structurally. That is, `yamlMap1 == yamlMap2` if they
/// have the same contents.
/// * It has a compatible [hashCode] method.
-class YamlMap implements Map {
- final Map _map;
+///
+/// This class is deprecated. In future releases, this package will use
+/// a [HashMap] with a custom equality operation rather than a custom class.
+@Deprecated('1.0.0')
+class YamlMap extends DelegatingMap {
+ YamlMap()
+ : super(new HashMap(equals: deepEquals, hashCode: hashCodeFor));
- YamlMap() : _map = new Map();
-
- YamlMap.from(Map map) : _map = new Map.from(map);
-
- YamlMap._wrap(this._map);
-
- void addAll(Map other) {
- other.forEach((key, value) {
- this[key] = value;
- });
+ YamlMap.from(Map map)
+ : super(new HashMap(equals: deepEquals, hashCode: hashCodeFor)) {
+ addAll(map);
}
- bool containsValue(value) => _map.containsValue(value);
- bool containsKey(key) => _map.containsKey(_wrapKey(key));
- operator [](key) => _map[_wrapKey(key)];
- operator []=(key, value) { _map[_wrapKey(key)] = value; }
- putIfAbsent(key, ifAbsent()) => _map.putIfAbsent(_wrapKey(key), ifAbsent);
- remove(key) => _map.remove(_wrapKey(key));
- void clear() => _map.clear();
- void forEach(void f(key, value)) =>
- _map.forEach((k, v) => f(_unwrapKey(k), v));
- Iterable get keys => _map.keys.map(_unwrapKey);
- Iterable get values => _map.values;
- int get length => _map.length;
- bool get isEmpty => _map.isEmpty;
- bool get isNotEmpty => _map.isNotEmpty;
- String toString() => _map.toString();
-
- int get hashCode => hashCodeFor(_map);
+ int get hashCode => hashCodeFor(this);
bool operator ==(other) {
if (other is! YamlMap) return false;
return deepEquals(this, other);
}
-
- /// Wraps an object for use as a key in the map.
- _wrapKey(obj) {
- if (obj != null && obj is! bool && obj is! List &&
- (obj is! num || !obj.isNaN) &&
- (obj is! Map || obj is YamlMap)) {
- return obj;
- } else if (obj is Map) {
- return new YamlMap._wrap(obj);
- }
- return new _WrappedHashKey(obj);
- }
-
- /// Unwraps an object that was used as a key in the map.
- _unwrapKey(obj) => obj is _WrappedHashKey ? obj.value : obj;
-}
-
-/// A class for wrapping normally-unhashable objects that are being used as keys
-/// in a YamlMap.
-class _WrappedHashKey {
- final value;
-
- _WrappedHashKey(this.value);
-
- int get hashCode => hashCodeFor(value);
-
- String toString() => value.toString();
-
- /// This is defined as both values being structurally equal.
- bool operator ==(other) {
- if (other is! _WrappedHashKey) return false;
- return deepEquals(this.value, other.value);
- }
}
diff --git a/lib/yaml.dart b/lib/yaml.dart
index fa52861..dc895e6 100644
--- a/lib/yaml.dart
+++ b/lib/yaml.dart
@@ -2,44 +2,6 @@
// 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 parser for [YAML](http://www.yaml.org/).
-///
-/// ## Installing ##
-///
-/// Use [pub][] to install this package. Add the following to your
-/// `pubspec.yaml` file.
-///
-/// dependencies:
-/// yaml: any
-///
-/// Then run `pub install`.
-///
-/// For more information, see the
-/// [yaml package on pub.dartlang.org][pkg].
-///
-/// ## Using ##
-///
-/// Use [loadYaml] to load a single document, or [loadYamlStream] to load a
-/// stream of documents. For example:
-///
-/// import 'package:yaml/yaml.dart';
-/// main() {
-/// var doc = loadYaml("YAML: YAML Ain't Markup Language");
-/// print(doc['YAML']);
-/// }
-///
-/// This library currently doesn't support dumping to YAML. You should use
-/// `JSON.encode` from `dart:convert` instead:
-///
-/// import 'dart:convert';
-/// import 'package:yaml/yaml.dart';
-/// main() {
-/// var doc = loadYaml("YAML: YAML Ain't Markup Language");
-/// print(JSON.encode(doc));
-/// }
-///
-/// [pub]: http://pub.dartlang.org
-/// [pkg]: http://pub.dartlang.org/packages/yaml
library yaml;
import 'src/composer.dart';
@@ -50,18 +12,23 @@
export 'src/yaml_exception.dart';
export 'src/yaml_map.dart';
-/// Loads a single document from a YAML string. If the string contains more than
-/// one document, this throws an error.
+/// Loads a single document from a YAML string.
+///
+/// If the string contains more than one document, this throws a
+/// [YamlException]. In future releases, this will become an [ArgumentError].
///
/// The return value is mostly normal Dart objects. However, since YAML mappings
/// support some key types that the default Dart map implementation doesn't
-/// (null, NaN, booleans, lists, and maps), all maps in the returned document
-/// are [YamlMap]s. These have a few small behavioral differences from the
-/// default Map implementation; for details, see the [YamlMap] class.
+/// (NaN, lists, and maps), all maps in the returned document are [YamlMap]s.
+/// These have a few small behavioral differences from the default Map
+/// implementation; for details, see the [YamlMap] class.
+///
+/// In future versions, maps will instead be [HashMap]s with a custom equality
+/// operation.
loadYaml(String yaml) {
var stream = loadYamlStream(yaml);
if (stream.length != 1) {
- throw new YamlException("Expected 1 document, were ${stream.length}");
+ throw new YamlException("Expected 1 document, were ${stream.length}.");
}
return stream[0];
}
@@ -70,9 +37,12 @@
///
/// The return value is mostly normal Dart objects. However, since YAML mappings
/// support some key types that the default Dart map implementation doesn't
-/// (null, NaN, booleans, lists, and maps), all maps in the returned document
-/// are [YamlMap]s. These have a few small behavioral differences from the
-/// default Map implementation; for details, see the [YamlMap] class.
+/// (NaN, lists, and maps), all maps in the returned document are [YamlMap]s.
+/// These have a few small behavioral differences from the default Map
+/// implementation; for details, see the [YamlMap] class.
+///
+/// In future versions, maps will instead be [HashMap]s with a custom equality
+/// operation.
List loadYamlStream(String yaml) {
return new Parser(yaml).l_yamlStream()
.map((doc) => new Constructor(new Composer(doc).compose()).construct())
diff --git a/pubspec.yaml b/pubspec.yaml
index 1fb4c8d..c3eacd7 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,8 +1,10 @@
name: yaml
-version: 0.9.0
+version: 0.9.0+1
author: "Dart Team <misc@dartlang.org>"
homepage: http://www.dartlang.org
description: A parser for YAML.
+dependencies:
+ collection: ">=0.9.2 <0.10.0"
dev_dependencies:
unittest: ">=0.9.0 <0.10.0"
environment:
diff --git a/test/utils.dart b/test/utils.dart
new file mode 100644
index 0000000..0bac9f9
--- /dev/null
+++ b/test/utils.dart
@@ -0,0 +1,83 @@
+// Copyright (c) 2014, 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 yaml.test.utils;
+
+import 'package:unittest/unittest.dart';
+import 'package:yaml/src/deep_equals.dart' as de;
+import 'package:yaml/yaml.dart';
+
+/// A matcher that validates that a closure or Future throws a [YamlException].
+final Matcher throwsYamlException = throwsA(new isInstanceOf<YamlException>());
+
+/// Returns a matcher that asserts that the value equals [expected].
+///
+/// This handles recursive loops and considers `NaN` to equal itself.
+Matcher deepEquals(expected) =>
+ predicate((actual) => de.deepEquals(actual, expected), "equals $expected");
+
+/// Constructs a new yaml.YamlMap, optionally from a normal Map.
+Map yamlMap([Map from]) =>
+ from == null ? new YamlMap() : new YamlMap.from(from);
+
+/// Asserts that a string containing a single YAML document produces a given
+/// value when loaded.
+void expectYamlLoads(expected, String source) {
+ var actual = loadYaml(cleanUpLiteral(source));
+ expect(expected, deepEquals(actual));
+}
+
+/// Asserts that a string containing a stream of YAML documents produces a given
+/// list of values when loaded.
+void expectYamlStreamLoads(List expected, String source) {
+ var actual = loadYamlStream(cleanUpLiteral(source));
+ expect(expected, deepEquals(actual));
+}
+
+/// Asserts that a string containing a single YAML document throws a
+/// [YamlException].
+void expectYamlFails(String source) {
+ expect(() => loadYaml(cleanUpLiteral(source)), throwsYamlException);
+}
+
+/// Removes eight spaces of leading indentation from a multiline string.
+///
+/// Note that this is very sensitive to how the literals are styled. They should
+/// be:
+/// '''
+/// Text starts on own line. Lines up with subsequent lines.
+/// Lines are indented exactly 8 characters from the left margin.
+/// Close is on the same line.'''
+///
+/// This does nothing if text is only a single line.
+String cleanUpLiteral(String text) {
+ var lines = text.split('\n');
+ if (lines.length <= 1) return text;
+
+ for (var j = 0; j < lines.length; j++) {
+ if (lines[j].length > 8) {
+ lines[j] = lines[j].substring(8, lines[j].length);
+ } else {
+ lines[j] = '';
+ }
+ }
+
+ return lines.join('\n');
+}
+
+/// Indents each line of [text] so that, when passed to [cleanUpLiteral], it
+/// will produce output identical to [text].
+///
+/// This is useful for literals that need to include newlines but can't be
+/// conveniently represented as multi-line strings.
+String indentLiteral(String text) {
+ var lines = text.split('\n');
+ if (lines.length <= 1) return text;
+
+ for (var i = 0; i < lines.length; i++) {
+ lines[i] = " ${lines[i]}";
+ }
+
+ return lines.join("\n");
+}
diff --git a/test/yaml_test.dart b/test/yaml_test.dart
index 42cb613..fcfc12e 100644
--- a/test/yaml_test.dart
+++ b/test/yaml_test.dart
@@ -2,36 +2,13 @@
// 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 yaml_test;
+library yaml.test;
// TODO(rnystrom): rewrite tests so that they don't need "Expect".
-import "package:expect/expect.dart";
import 'package:unittest/unittest.dart';
import 'package:yaml/yaml.dart';
-import 'package:yaml/src/deep_equals.dart';
-// TODO(jmesserly): we should not be reaching outside the YAML package
-// The http package has a similar problem.
-import '../../../tests/utils/test_utils.dart';
-/// Constructs a new yaml.YamlMap, optionally from a normal Map.
-Map yamlMap([Map from]) =>
- from == null ? new YamlMap() : new YamlMap.from(from);
-
-/// Asserts that a string containing a single YAML document produces a given
-/// value when loaded.
-expectYamlLoads(expected, String source) {
- var actual = loadYaml(cleanUpLiteral(source));
- Expect.isTrue(deepEquals(expected, actual),
- 'expectYamlLoads(expected: <$expected>, actual: <$actual>)');
-}
-
-/// Asserts that a string containing a stream of YAML documents produces a given
-/// list of values when loaded.
-expectYamlStreamLoads(List expected, String source) {
- var actual = loadYamlStream(cleanUpLiteral(source));
- Expect.isTrue(deepEquals(expected, actual),
- 'expectYamlStreamLoads(expected: <$expected>, actual: <$actual>)');
-}
+import 'utils.dart';
main() {
var infinity = double.parse("Infinity");
@@ -500,7 +477,7 @@
expectDisallowsCharacter(int charCode) {
var char = new String.fromCharCodes([charCode]);
- Expect.throws(() => loadYaml('The character "$char" is disallowed'));
+ expectYamlFails('The character "$char" is disallowed');
}
test("doesn't include C0 control characters", () {
@@ -598,8 +575,8 @@
// });
test('[Example 5.10]', () {
- Expect.throws(() => loadYaml("commercial-at: @text"));
- Expect.throws(() => loadYaml("commercial-at: `text"));
+ expectYamlFails("commercial-at: @text");
+ expectYamlFails("commercial-at: `text");
});
});
@@ -610,7 +587,7 @@
});
group('do not include', () {
- test('form feed', () => Expect.throws(() => loadYaml("- 1\x0C- 2")));
+ test('form feed', () => expectYamlFails("- 1\x0C- 2"));
test('NEL', () => expectYamlLoads(["1\x85- 2"], "- 1\x85- 2"));
test('0x2028', () => expectYamlLoads(["1\u2028- 2"], "- 1\u2028- 2"));
test('0x2029', () => expectYamlLoads(["1\u2029- 2"], "- 1\u2029- 2"));
@@ -669,27 +646,27 @@
});
test('[Example 5.14]', () {
- Expect.throws(() => loadYaml('Bad escape: "\\c"'));
- Expect.throws(() => loadYaml('Bad escape: "\\xq-"'));
+ expectYamlFails('Bad escape: "\\c"');
+ expectYamlFails('Bad escape: "\\xq-"');
});
});
// Chapter 6: Basic Structures
group('6.1: Indentation Spaces', () {
test('may not include TAB characters', () {
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
-
\t- foo
- \t- bar""")));
+ \t- bar""");
});
test('must be the same for all sibling nodes', () {
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
-
- foo
- - bar""")));
+ - bar""");
});
test('may be different for the children of sibling nodes', () {
@@ -916,10 +893,10 @@
group('6.7: Separation Lines', () {
test('may not be used within implicit keys', () {
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
[1,
- 2]: 3""")));
+ 2]: 3""");
});
test('[Example 6.12]', () {
@@ -960,11 +937,11 @@
// });
// test('[Example 6.15]', () {
- // Expect.throws(() => loadYaml(cleanUpLiteral(
+ // expectYamlFails(
// """
// %YAML 1.2
// %YAML 1.1
- // foo""")));
+ // foo""");
// });
// test('[Example 6.16]', () {
@@ -976,11 +953,11 @@
// });
// test('[Example 6.17]', () {
- // Expect.throws(() => loadYaml(cleanUpLiteral(
+ // ExpectYamlFails(
// """
// %TAG ! !foo
// %TAG ! !foo
- // bar""")));
+ // bar""");
// });
// Examples 6.18 through 6.22 test custom tag URIs, which this
@@ -1010,8 +987,8 @@
// // doesn't plan to support.
// test('[Example 6.25]', () {
- // Expect.throws(() => loadYaml("- !<!> foo"));
- // Expect.throws(() => loadYaml("- !<\$:?> foo"));
+ // expectYamlFails("- !<!> foo");
+ // expectYamlFails("- !<\$:?> foo");
// });
// // Examples 6.26 and 6.27 test custom tag URIs, which this implementation
@@ -1040,10 +1017,10 @@
// Chapter 7: Flow Styles
group('7.1: Alias Nodes', () {
// test("must not use an anchor that doesn't previously occur", () {
- // Expect.throws(() => loadYaml(cleanUpLiteral(
+ // expectYamlFails(
// """
// - *anchor
- // - &anchor foo"""));
+ // - &anchor foo""");
// });
// test("don't have to exist for a given anchor node", () {
@@ -1051,18 +1028,17 @@
// });
// group('must not specify', () {
- // test('tag properties', () => Expect.throws(() => loadYaml(cleanUpLiteral(
+ // test('tag properties', () => expectYamlFails(
// """
// - &anchor foo
- // - !str *anchor""")));
+ // - !str *anchor""");
- // test('anchor properties', () => Expect.throws(
- // () => loadYaml(cleanUpLiteral(
+ // test('anchor properties', () => expectYamlFails(
// """
// - &anchor foo
- // - &anchor2 *anchor""")));
+ // - &anchor2 *anchor""");
- // test('content', () => Expect.throws(() => loadYaml(cleanUpLiteral(
+ // test('content', () => expectYamlFails(
// """
// - &anchor foo
// - *anchor bar""")));
@@ -1075,9 +1051,7 @@
// alias: *anchor""");
// var anchorList = doc['anchor'];
// var aliasList = doc['alias'];
- // Expect.isTrue(anchorList === aliasList);
- // anchorList.add('d');
- // Expect.listEquals(['a', 'b', 'c', 'd'], aliasList);
+ // expect(anchorList, same(aliasList));
// doc = loadYaml(cleanUpLiteral(
// """
@@ -1086,9 +1060,7 @@
// : bar""");
// anchorList = doc.keys[0];
// aliasList = doc[['a', 'b', 'c']].keys[0];
- // Expect.isTrue(anchorList === aliasList);
- // anchorList.add('d');
- // Expect.listEquals(['a', 'b', 'c', 'd'], aliasList);
+ // expect(anchorList, same(aliasList));
// });
// test('[Example 7.1]', () {
@@ -1344,15 +1316,15 @@
});
test('[Example 7.22]', () {
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
[ foo
- bar: invalid ]""")));
+ bar: invalid ]""");
// TODO(nweiz): enable this when we throw an error for long keys
// var dotList = new List.filled(1024, ' ');
// var dots = dotList.join();
- // Expect.throws(() => loadYaml('[ "foo...$dots...bar": invalid ]'));
+ // expectYamlFails('[ "foo...$dots...bar": invalid ]');
});
});
@@ -1421,22 +1393,22 @@
});
test('[Example 8.3]', () {
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
- |
- text""")));
+ text""");
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
- >
text
- text""")));
+ text""");
- Expect.throws(() => loadYaml(cleanUpLiteral(
+ expectYamlFails(
"""
- |2
- text""")));
+ text""");
});
test('[Example 8.4]', () {