Initial implementation.
diff --git a/lib/resource.dart b/lib/resource.dart
new file mode 100644
index 0000000..dbed97a
--- /dev/null
+++ b/lib/resource.dart
@@ -0,0 +1,12 @@
+// 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.
+
+/// A [Resource] is data that can be loaded into a Dart program.
+///
+/// This library provides an implementation of [Resource] and a
+/// [PackageResolver] that controls how package: URIs are converted to
+/// URIs that can be loaded.
+library resource;
+
+export "src/resource.dart" show Resource, PackageResolver;
diff --git a/lib/src/io.dart b/lib/src/io.dart
new file mode 100644
index 0000000..8237c01
--- /dev/null
+++ b/lib/src/io.dart
@@ -0,0 +1,59 @@
+// 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.
+
+// dart:io based strategy for loading resources.
+
+import "dart:io" show File, HttpClient, HttpClientRequest, HttpClientResponse;
+import "dart:async" show Future, Stream;
+import "dart:convert" show Encoding, UTF8;
+import "package:typed_data/typed_buffers.dart" show Uint8Buffer;
+
+Stream<List<int>> readAsStream(Uri uri) async* {
+ if (uri.scheme == "file") {
+ yield* new File.fromUri(uri).openRead();
+ return;
+ }
+ if (uri.scheme == "http" || uri.scheme == "https") {
+ HttpClientResponse response = await _httpGet(uri);
+ yield* response;
+ return;
+ }
+ throw new UnsupportedError("Unsupported scheme: $uri");
+}
+
+Future<List<int>> readAsBytes(Uri uri) async {
+ if (uri.scheme == "file") {
+ return new File.fromUri(uri).readAsBytes();
+ }
+ if (uri.scheme == "http" || uri.scheme == "https") {
+ HttpClientResponse response = await _httpGet(uri);
+ Uint8Buffer buffer = new Uint8Buffer();
+ await for (var bytes in response) {
+ buffer.addAll(bytes);
+ }
+ return buffer.toList();
+ }
+ throw new UnsupportedError("Unsupported scheme: $uri");
+}
+
+Future<String> readAsString(Uri uri, Encoding encoding) async {
+ if (encoding == null) encoding = UTF8;
+ if (uri.scheme == "file") {
+ return new File.fromUri(uri).readAsString(encoding: encoding);
+ }
+ if (uri.scheme == "http" || uri.scheme == "https") {
+ HttpClientResponse response = await _httpGet(uri);
+ Uint8Buffer buffer = new Uint8Buffer();
+ await for (var bytes in response) {
+ buffer.addAll(bytes);
+ }
+ new String.fromCharCodes(buffer.toList());
+ }
+ throw new UnsupportedError("Unsupported scheme: $uri");
+}
+
+Future<HttpClientResponse> _httpGet(Uri uri) async {
+ HttpClientRequest request = await new HttpClient().getUrl(uri);
+ return await request.close();
+}
diff --git a/lib/src/resource.dart b/lib/src/resource.dart
new file mode 100644
index 0000000..2fef5f6
--- /dev/null
+++ b/lib/src/resource.dart
@@ -0,0 +1,218 @@
+// 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 resoure.resource;
+
+import "dart:async" show Future, Stream;
+import "dart:convert" show Encoding;
+import "dart:isolate" show Isolate;
+import "package_resolver.dart";
+import "io.dart" as io; // Loading strategy. TODO: Be configuration dependent.
+
+/// A strategy for resolving package URIs
+abstract class PackageResolver {
+ /// Cache of the current resolver, accessible directly after it has first
+ /// been found asynchronously.
+ static PackageResolver _current;
+
+ /// The package resolution strategy used by the current isolate.
+ static final Future<PackageResolver> current = _findIsolateResolution();
+
+ PackageResolver();
+
+ /// Creates a resolver using a map from package name to package location.
+ factory PackageResolver.fromMap(Map<String, Uri> packages) = MapResolver;
+
+ /// Creates a resolver using a package root.
+ factory PackageResolver.fromRoot(Uri packageRoot) = RootResolver;
+
+ /// Resolves a package URI to its location.
+ ///
+ /// If [uri] does not have `package` as scheme, it is returned again.
+ /// Otherwise the package name is looked up, and if found, a location
+ /// for the package file is returned.
+ Future<Uri> resolve(Uri uri);
+
+ /// Returns a [Resource] for the [uri] as resolved by this resolver.
+ Resource resource(Uri uri) {
+ return new _UriResource(this, uri);
+ }
+
+ /// Finds the way the current isolate resolves package URIs.
+ ///
+ /// Is only called once, and when it has been called, the [_current]
+ /// resolver is initialized, so [UriResource] will be initialized
+ /// with the resolver directly.
+ static Future<PackageResolver> _findIsolateResolution() async {
+ var pair = await Future.wait([Isolate.packageRoot, Isolate.packageMap]);
+ Uri root = pair[0];
+ if (root != null) {
+ _current = new RootResolver(root);
+ } else {
+ Map<String, Uri> map = pair[1];
+ _current = new MapResolver(map);
+ }
+ return _current;
+ }
+}
+
+/// A resource that can be read into the program.
+///
+/// A resource is data that can be located using a URI and read into
+/// the program at runtime.
+/// The URI may use the `package` scheme to read resources provided
+/// along with package sources.
+abstract class Resource {
+ /// Creates a resource object with the given [uri] as location.
+ ///
+ /// The `uri` is a string containing a valid URI.
+ /// If the string is not a valid URI, using any of the functions on
+ /// the resource object will fail.
+ ///
+ /// The URI may be relative, in which case it will be resolved
+ /// against [Uri.base] before being used.
+ ///
+ /// The URI may use the `package` scheme, which is always supported.
+ /// Other schemes may also be supported where possible.
+ const factory Resource(String uri) = _StringResource;
+
+ /// Creates a resource object with the given [uri] as location.
+ ///
+ /// The URI may be relative, in which case it will be resolved
+ /// against [Uri.base] before being used.
+ ///
+ /// The URI may use the `package` scheme, which is always supported.
+ /// Other schemes may also be supported where possible.
+ factory Resource.forUri(Uri uri) =>
+ new _UriResource(PackageResolver._current, uri);
+
+ /// The location `uri` of this resource.
+ ///
+ /// This is a [Uri] of the `uri` parameter given to the constructor.
+ /// If the parameter was not a valid URI, reading `uri` may fail.
+ Uri get uri;
+
+ Stream<List<int>> openRead();
+
+ Future<List<int>> readAsBytes();
+
+ /// Read the resource content as a string.
+ ///
+ /// The content is decoded into a string using an [Encoding].
+ /// If no other encoding is provided, it defaults to UTF-8.
+ Future<String> readAsString({Encoding encoding});
+}
+
+class _StringResource implements Resource {
+ final String _uri;
+
+ const _StringResource(String uri) : _uri = uri;
+
+ Uri get uri => Uri.parse(_uri);
+
+ Stream<List<int>> openRead() {
+ return new _UriResource(PackageResolver._current, uri).openRead();
+ }
+ Future<List<int>> readAsBytes() {
+ return new _UriResource(PackageResolver._current, uri).readAsBytes();
+ }
+ Future<String> readAsString({Encoding encoding}) {
+ return new _UriResource(PackageResolver._current, uri)
+ .readAsString(encoding: encoding);
+ }
+}
+
+class _UriResource implements Resource {
+ /// The strategy for resolving package: URIs.
+ ///
+ /// May be null intially. If so, the [PackageResolver.current] resolver is
+ /// used (and cached for later use).
+ PackageResolver _resolver;
+
+ /// The URI of the resource.
+ final Uri uri;
+
+ _UriResource(this.resolver, Uri uri);
+
+ Stream<List<int>> openRead() async* {
+ Uri uri = await _resolve(this.uri);
+ return io.readAsStream(uri);
+ }
+
+ Future<List<int>> readAsBytes() async {
+ Uri uri = await _resolve(this.uri);
+ return io.readAsBytes(uri);
+ }
+
+ Future<String> readAsString({Encoding encoding}) async {
+ Uri uri = await _resolve(this.uri);
+ return io.readAsString(uri, encoding);
+ }
+
+ static void _checkPackageUri(Uri uri) {
+ if (uri.scheme != "package") {
+ throw new ArgumentError.value(uri, "Not a package: URI");
+ }
+ if (uri.hasAuthority) {
+ throw new ArgumentError.value(uri,
+ "A package: URI must not have an authority part");
+ }
+ if (uri.path.isEmpty || uri.path.startsWith('/')) {
+ throw new ArgumentError.value(uri,
+ "A package: URI must have the form "
+ "'package:packageName/packagePath'");
+ }
+ if (uri.hasQuery) {
+ throw new ArgumentError.value(uri,
+ "A package: URI must not have a query part");
+ }
+ if (uri.hasFragment) {
+ throw new ArgumentError.value(uri,
+ "A package: URI must not have a fragment part");
+ }
+ }
+
+ Future<Uri> _resolve(Uri uri) async {
+ if (uri.scheme != "package") {
+ return Uri.base.resolveUri(uri);
+ }
+ _checkPackageUri(uri);
+ _resolver ??= await PackageResolver._current;
+ return _resolver.resolve(uri);
+ }
+}
+
+/// A [PackageResolver] based on a packags map.
+class MapResolver extends PackageResolver {
+ Map<String, Uri> _mapping;
+
+ MapResolver(this._mapping);
+
+ Uri resolve(Uri uri) {
+ if (uri.scheme != "package") return uri;
+ var path = uri.path;
+ int slashIndex = path.indexOf('/');
+ if (slashIndex <= 0) {
+ throw new ArgumentError.value(uri, "Invalid package URI");
+ }
+ int packageName = path.substring(0, slashIndex);
+ var base = _mapping[packageName];
+ if (base != null) {
+ int packagePath = path.substring(slashIndex + 1);
+ return base.resolveUri(new Uri(path: packagePath));
+ }
+ throw new UnsupportedError("No package named '$packageName' found");
+ }
+}
+
+/// A [PackageResolver] based on a package root.
+class RootResolver extends PackageResolver {
+ Uri _root;
+ RootResolver(this._root);
+
+ Uri resolve(Uri uri) {
+ if (uri.scheme != "package") return uri;
+ return _root.resolve(uri.path);
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..6770e7f
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,7 @@
+name: resource
+version: 0.1.0
+dependencies:
+ http: "any"
+ typed_data: "any"
+dev_dependencies:
+ test: "any"