Update to new syntax.
R=het@google.com, pquitslund@google.com
Review URL: https://codereview.chromium.org//1178213004.
diff --git a/lib/discovery_analysis.dart b/lib/discovery_analysis.dart
index f3bcac6..af4df07 100644
--- a/lib/discovery_analysis.dart
+++ b/lib/discovery_analysis.dart
@@ -18,7 +18,6 @@
import "package:path/path.dart" as path;
import "packages.dart";
-import "discovery.dart";
import "packages_file.dart" as pkgfile;
import "src/packages_impl.dart";
import "src/packages_io_impl.dart";
diff --git a/lib/packages_file.dart b/lib/packages_file.dart
index a736ca0..73e6061 100644
--- a/lib/packages_file.dart
+++ b/lib/packages_file.dart
@@ -5,7 +5,7 @@
library package_config.packages_file;
import "package:charcode/ascii.dart";
-import "src/util.dart" show isIdentifier;
+import "src/util.dart" show isValidPackageName;
/// Parses a `.packages` file into a map from package name to base URI.
///
@@ -28,34 +28,34 @@
while (index < source.length) {
bool isComment = false;
int start = index;
- int eqIndex = -1;
+ int separatorIndex = -1;
int end = source.length;
int char = source[index++];
if (char == $cr || char == $lf) {
continue;
}
- if (char == $equal) {
+ if (char == $colon) {
throw new FormatException("Missing package name", source, index - 1);
}
isComment = char == $hash;
while (index < source.length) {
char = source[index++];
- if (char == $equal && eqIndex < 0) {
- eqIndex = index - 1;
+ if (char == $colon && separatorIndex < 0) {
+ separatorIndex = index - 1;
} else if (char == $cr || char == $lf) {
end = index - 1;
break;
}
}
if (isComment) continue;
- if (eqIndex < 0) {
- throw new FormatException("No '=' on line", source, index - 1);
+ if (separatorIndex < 0) {
+ throw new FormatException("No ':' on line", source, index - 1);
}
- var packageName = new String.fromCharCodes(source, start, eqIndex);
- if (!isIdentifier(packageName)) {
+ var packageName = new String.fromCharCodes(source, start, separatorIndex);
+ if (!isValidPackageName(packageName)) {
throw new FormatException("Not a valid package name", packageName, 0);
}
- var packageUri = new String.fromCharCodes(source, eqIndex + 1, end);
+ var packageUri = new String.fromCharCodes(source, separatorIndex + 1, end);
var packageLocation = Uri.parse(packageUri);
if (!packageLocation.path.endsWith('/')) {
packageLocation =
@@ -101,11 +101,11 @@
packageMapping.forEach((String packageName, Uri uri) {
// Validate packageName.
- if (!isIdentifier(packageName)) {
+ if (!isValidPackageName(packageName)) {
throw new ArgumentError('"$packageName" is not a valid package name');
}
output.write(packageName);
- output.write('=');
+ output.write(':');
// If baseUri provided, make uri relative.
if (baseUri != null) {
uri = _relativize(uri, baseUri);
@@ -124,7 +124,7 @@
/// but may be relative.
/// The `baseUri` must be absolute.
Uri _relativize(Uri uri, Uri baseUri) {
- assert(!baseUri.isAbsolute);
+ assert(baseUri.isAbsolute);
if (uri.hasQuery || uri.hasFragment) {
uri = new Uri(
scheme: uri.scheme,
@@ -158,6 +158,7 @@
}
uri = _normalizePath(uri);
List<String> target = uri.pathSegments.toList();
+ if (target.isNotEmpty && target.last.isEmpty) target.removeLast();
int index = 0;
while (index < base.length && index < target.length) {
if (base[index] != target[index]) {
@@ -166,6 +167,9 @@
index++;
}
if (index == base.length) {
+ if (index == target.length) {
+ return new Uri(path: "./");
+ }
return new Uri(path: target.skip(index).join('/'));
} else if (index > 0) {
return new Uri(
diff --git a/lib/src/util.dart b/lib/src/util.dart
index abf1d52..badf640 100644
--- a/lib/src/util.dart
+++ b/lib/src/util.dart
@@ -7,26 +7,38 @@
import "package:charcode/ascii.dart";
-/// Tests whether something is a valid Dart identifier/package name.
-bool isIdentifier(String string) {
- if (string.isEmpty) return false;
- int firstChar = string.codeUnitAt(0);
- int firstCharLower = firstChar | 0x20;
- if (firstCharLower < $a || firstCharLower > $z) {
- if (firstChar != $_ && firstChar != $$) return false;
- }
- for (int i = 1; i < string.length; i++) {
- int char = string.codeUnitAt(i);
- int charLower = char | 0x20;
- if (charLower < $a || charLower > $z) { // Letters.
- if ((char ^ 0x30) <= 9) continue; // Digits.
- if (char == $_ || char == $$) continue; // $ and _
- if (firstChar != $_ && firstChar != $$) return false;
- }
- }
- return true;
+// All ASCII characters that are valid in a package name, with space
+// for all the invalid ones (including space).
+const String _validPackageNameCharacters =
+ r" ! $ &'()*+,-. 0123456789 ; = "
+ r"@ABCDEFGHIJKLMNOPQRSTUVWXYZ _ abcdefghijklmnopqrstuvwxyz ~ ";
+
+/// Tests whether something is a valid Dart package name.
+bool isValidPackageName(String string) {
+ return _findInvalidCharacter(string) < 0;
}
+/// Check if a string is a valid package name.
+///
+/// Valid package names contain only characters in [_validPackageNameCharacters]
+/// and must contain at least one non-'.' character.
+///
+/// Returns `-1` if the string is valid.
+/// Otherwise returns the index of the first invalid character,
+/// or `string.length` if the string contains no non-'.' character.
+int _findInvalidCharacter(String string) {
+ // Becomes non-zero if any non-'.' character is encountered.
+ int nonDot = 0;
+ for (int i = 0; i < string.length; i++) {
+ var c = string.codeUnitAt(i);
+ if (c > 0x7f || _validPackageNameCharacters.codeUnitAt(c) <= $space) {
+ return i;
+ }
+ nonDot += c ^ $dot;
+ }
+ if (nonDot == 0) return string.length;
+ return -1;
+}
/// Validate that a Uri is a valid package:URI.
String checkValidPackageUri(Uri packageUri) {
@@ -61,9 +73,25 @@
"Package URIs must start with the package name followed by a '/'");
}
String packageName = packageUri.path.substring(0, firstSlash);
- if (!isIdentifier(packageName)) {
+ int badIndex = _findInvalidCharacter(packageName);
+ if (badIndex >= 0) {
+ if (packageName.isEmpty) {
+ throw new ArgumentError.value(packageUri, "packageUri",
+ "Package names mus be non-empty");
+ }
+ if (badIndex == packageName.length) {
+ throw new ArgumentError.value(packageUri, "packageUri",
+ "Package names must contain at least one non-'.' character");
+ }
+ assert(badIndex < packageName.length);
+ int badCharCode = packageName.codeUnitAt(badIndex);
+ var badChar = "U+" + badCharCode.toRadixString(16).padLeft(4, '0');
+ if (badCharCode >= 0x20 && badCharCode <= 0x7e) {
+ // Printable character.
+ badChar = "'${packageName[badIndex]}' ($badChar)";
+ }
throw new ArgumentError.value(packageUri, "packageUri",
- "Package names must be valid identifiers");
+ "Package names must not contain $badChar");
}
return packageName;
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 0b780d9..2d96c90 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: package_config
-version: 0.0.4
+version: 0.1.0
description: Support for working with Package Resolution config files.
author: Dart Team <misc@dartlang.org>
homepage: https://github.com/dart-lang/package_config
diff --git a/test/all.dart b/test/all.dart
index 79f5b35..78e6cff 100644
--- a/test/all.dart
+++ b/test/all.dart
@@ -2,10 +2,16 @@
// 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.
+import "package:test/test.dart";
+
+import "discovery_analysis_test.dart" as discovery_analysis;
import "discovery_test.dart" as discovery;
import "parse_test.dart" as parse;
+import "parse_write_test.dart" as parse_write;
main() {
- parse.main();
- discovery.main();
+ group("parse:", parse.main);
+ group("discovery:", discovery.main);
+ group("discovery-analysis:", discovery_analysis.main);
+ group("parse/write:", parse_write.main);
}
diff --git a/test/discovery_analysis_test.dart b/test/discovery_analysis_test.dart
index 38599f8..f33a4a8 100644
--- a/test/discovery_analysis_test.dart
+++ b/test/discovery_analysis_test.dart
@@ -2,6 +2,8 @@
// 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 package_config.discovery_analysis_test;
+
import "dart:async";
import "dart:io";
@@ -46,9 +48,9 @@
const packagesFile = """
# A comment
-foo=file:///dart/packages/foo/
-bar=http://example.com/dart/packages/bar/
-baz=packages/baz/
+foo:file:///dart/packages/foo/
+bar:http://example.com/dart/packages/bar/
+baz:packages/baz/
""";
void validatePackagesFile(Packages resolver, Uri location) {
diff --git a/test/discovery_test.dart b/test/discovery_test.dart
index d89cc2f..4f780c2 100644
--- a/test/discovery_test.dart
+++ b/test/discovery_test.dart
@@ -2,6 +2,8 @@
// 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 package_config.discovery_test;
+
import "dart:async";
import "dart:io";
import "package:test/test.dart";
@@ -11,9 +13,9 @@
const packagesFile = """
# A comment
-foo=file:///dart/packages/foo/
-bar=http://example.com/dart/packages/bar/
-baz=packages/baz/
+foo:file:///dart/packages/foo/
+bar:http://example.com/dart/packages/bar/
+baz:packages/baz/
""";
void validatePackagesFile(Packages resolver, Uri location) {
diff --git a/test/parse_test.dart b/test/parse_test.dart
index ddd8ff6..902d8ce 100644
--- a/test/parse_test.dart
+++ b/test/parse_test.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 test_all;
+library package_config.parse_test;
import "package:package_config/packages.dart";
import "package:package_config/packages_file.dart" show parse;
@@ -81,27 +81,27 @@
equals(base.resolve("../test/").resolve("bar/baz.dart")));
});
- test("all valid chars", () {
+ test("all valid chars can be used in URI segment", () {
var packages = doParse(allValidCharsSample, base);
expect(packages.packages.toList(), equals([allValidChars]));
expect(packages.resolve(Uri.parse("package:$allValidChars/bar/baz.dart")),
equals(base.resolve("../test/").resolve("bar/baz.dart")));
});
- test("no escapes", () {
- expect(() => doParse("x%41x=x", base), throws);
+ test("no invalid chars accepted", () {
+ var map = {};
+ for (int i = 0; i < allValidChars.length; i++) {
+ map[allValidChars.codeUnitAt(i)] = true;
+ }
+ for (int i = 0; i <= 255; i++) {
+ if (map[i] == true) continue;
+ var char = new String.fromCharCode(i);
+ expect(() => doParse("x${char}x:x"), throws);
+ }
});
- test("not identifiers", () {
- expect(() => doParse("1x=x", base), throws);
- expect(() => doParse(" x=x", base), throws);
- expect(() => doParse("\\x41x=x", base), throws);
- expect(() => doParse("x@x=x", base), throws);
- expect(() => doParse("x[x=x", base), throws);
- expect(() => doParse("x`x=x", base), throws);
- expect(() => doParse("x{x=x", base), throws);
- expect(() => doParse("x/x=x", base), throws);
- expect(() => doParse("x:x=x", base), throws);
+ test("no escapes", () {
+ expect(() => doParse("x%41x:x", base), throws);
});
test("same name twice", () {
@@ -131,26 +131,28 @@
var emptySample = "";
var commentOnlySample = "# comment only\n";
var emptyLinesSample = "\n\n\r\n";
-var singleRelativeSample = "foo=../test/\n";
-var singleRelativeSampleNoSlash = "foo=../test\n";
-var singleRelativeSampleNoNewline = "foo=../test/";
-var singleAbsoluteSample = "foo=http://example.com/some/where/\n";
-var multiRelativeSample = "foo=../test/\nbar=../test2/\n";
+var singleRelativeSample = "foo:../test/\n";
+var singleRelativeSampleNoSlash = "foo:../test\n";
+var singleRelativeSampleNoNewline = "foo:../test/";
+var singleAbsoluteSample = "foo:http://example.com/some/where/\n";
+var multiRelativeSample = "foo:../test/\nbar:../test2/\n";
// All valid path segment characters in an URI.
var allValidChars =
- r"$0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
-var allValidCharsSample = "${allValidChars.replaceAll('=', '%3D')}=../test/\n";
-var allUnreservedChars =
- "-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
+ r"!$&'()*+,-.0123456789;="
+ r"@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
+
+var allValidCharsSample = "${allValidChars}:../test/\n";
// Invalid samples.
var invalid = [
- "foobar:baz.dart", // no equals
- ".=../test/", // dot segment
- "..=../test/", // dot-dot segment
- "foo/bar=../test/", //
- "/foo=../test/", // var multiSegmentSample
- "?=../test/", // invalid characters in path segment.
- "[=../test/", // invalid characters in path segment.
- "x#=../test/", // invalid characters in path segment.
+ ":baz.dart", // empty.
+ "foobar=baz.dart", // no colon (but an equals, which is not the same)
+ ".:../test/", // dot segment
+ "..:../test/", // dot-dot segment
+ "...:../test/", // dot-dot-dot segment
+ "foo/bar:../test/", // slash in name
+ "/foo:../test/", // slash at start of name
+ "?:../test/", // invalid characters.
+ "[:../test/", // invalid characters.
+ "x#:../test/", // invalid characters.
];
diff --git a/test/parse_write_test.dart b/test/parse_write_test.dart
new file mode 100644
index 0000000..fde9616
--- /dev/null
+++ b/test/parse_write_test.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2015, 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 package_config.parse_write_test;
+
+import "package:package_config/packages.dart";
+import "package:package_config/packages_file.dart";
+import "package:test/test.dart";
+
+main() {
+ testBase(baseDirString) {
+ var baseDir = Uri.parse(baseDirString);
+ group("${baseDir.scheme} base", () {
+ Uri packagesFile = baseDir.resolve(".packages");
+
+ roundTripTest(String name, Map<String, Uri> map) {
+ group(name, () {
+ test("write with no baseUri", () {
+ var content = writeToString(map).codeUnits;
+ var resultMap = parse(content, packagesFile);
+ expect(resultMap, map);
+ });
+
+ test("write with base directory", () {
+ var content = writeToString(map, baseUri: baseDir).codeUnits;
+ var resultMap = parse(content, packagesFile);
+ expect(resultMap, map);
+ });
+
+ test("write with base .packages file", () {
+ var content = writeToString(map, baseUri: packagesFile).codeUnits;
+ var resultMap = parse(content, packagesFile);
+ expect(resultMap, map);
+ });
+ });
+ }
+ var lowerDir = baseDir.resolve("path3/path4/");
+ var higherDir = baseDir.resolve("../");
+ var parallelDir = baseDir.resolve("../path3/");
+ var rootDir = baseDir.resolve("/");
+ var fileDir = Uri.parse("file:///path1/part2/");
+ var httpDir = Uri.parse("http://example.com/path1/path2/");
+ var otherDir = Uri.parse("other:/path1/path2/");
+
+ roundTripTest("empty", {});
+ roundTripTest("lower directory", {"foo": lowerDir});
+ roundTripTest("higher directory", {"foo": higherDir});
+ roundTripTest("parallel directory", {"foo": parallelDir});
+ roundTripTest("same directory", {"foo": baseDir});
+ roundTripTest("root directory", {"foo": rootDir});
+ roundTripTest("file directory", {"foo": fileDir});
+ roundTripTest("http directory", {"foo": httpDir});
+ roundTripTest("other scheme directory", {"foo": otherDir});
+ roundTripTest("multiple same-type directories",
+ {"foo": lowerDir, "bar": higherDir, "baz": parallelDir});
+ roundTripTest("multiple scheme directories",
+ {"foo": fileDir, "bar": httpDir, "baz": otherDir});
+ roundTripTest("multiple scheme directories and mutliple same type",
+ {"foo": fileDir, "bar": httpDir, "baz": otherDir,
+ "qux": lowerDir, "hip": higherDir, "dep": parallelDir});
+ });
+ }
+
+ testBase("file:///base1/base2/");
+ testBase("http://example.com/base1/base2/");
+ testBase("other:/base1/base2/");
+
+ // Check that writing adds the comment.
+ test("write preserves comment", () {
+ var comment = "comment line 1\ncomment line 2\ncomment line 3";
+ var result = writeToString({}, comment: comment);
+ // Comment with "# " before each line and "\n" after last.
+ var expectedComment =
+ "# comment line 1\n# comment line 2\n# comment line 3\n";
+ expect(result, startsWith(expectedComment));
+ });
+}
+
+String writeToString(Map map, {Uri baseUri, String comment}) {
+ var buffer = new StringBuffer();
+ write(buffer, map, baseUri: baseUri, comment: comment);
+ return buffer.toString();
+}