Add Resource package.

R=floitsch@google.com

Review URL: https://codereview.chromium.org//1387163002 .
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..25a1df3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+.buildlog
+.DS_Store
+.idea
+.pub/
+.settings/
+build/
+packages
+.packages
+pubspec.lock
diff --git a/.test_config b/.test_config
new file mode 100644
index 0000000..352d2fe
--- /dev/null
+++ b/.test_config
@@ -0,0 +1,3 @@
+{ 
+  "test_package": true
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b35c9f6
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+language: dart
+
+script: ./tool/travis.sh
+
+# Speed up builds by using containerization. Disable this if you need to use
+# sudo in your scripts.
+sudo: false
+
+branches:
+  only:
+    - master
+
+cache:
+  directories:
+  - $HOME/.pub-cache
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e8063a8
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,6 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..dd213b1
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## 1.0.0
+
+- Initial version
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..6f5e0ea
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+    // 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.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..de31e1a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2015, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..24e0287
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+# Resource
+
+Reading data from package contents and files.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/resource/issues
diff --git a/lib/resource.dart b/lib/resource.dart
index dbed97a..b23d220 100644
--- a/lib/resource.dart
+++ b/lib/resource.dart
@@ -1,12 +1,11 @@
-// 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;

+// 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.
+///
+/// A resource is identified by a URI.
+library resource;
+
+export "src/resource.dart" show Resource;
+export "src/loader.dart" show ResourceLoader;
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 8237c01..ba13796 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -1,59 +1,90 @@
-// 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();

-}

+// 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:async" show Future, Stream;
+import "dart:convert" show Encoding, LATIN1, UTF8;
+import "dart:io" show
+    File, HttpClient, HttpClientResponse, HttpClientRequest, HttpHeaders;
+import "dart:typed_data" show Uint8List;
+import "package:typed_data/typed_buffers.dart" show Uint8Buffer;
+
+/// Read the bytes of a URI as a stream of bytes.
+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;
+  }
+  if (uri.scheme == "data") {
+    yield uri.data.contentAsBytes();
+    return;
+  }
+  throw new UnsupportedError("Unsupported scheme: $uri");
+}
+
+/// Read the bytes of a URI as a list of bytes.
+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();
+  }
+  if (uri.scheme == "data") {
+    return uri.data.contentAsBytes();
+  }
+  throw new UnsupportedError("Unsupported scheme: $uri");
+}
+
+/// Read the bytes of a URI as a string.
+Future<String> readAsString(Uri uri, Encoding encoding) async {
+  if (uri.scheme == "file") {
+    if (encoding == null) encoding = UTF8;
+    return new File.fromUri(uri).readAsString(encoding: encoding);
+  }
+  if (uri.scheme == "http" || uri.scheme == "https") {
+    HttpClientRequest request = await new HttpClient().getUrl(uri);
+    // Prefer text/plain, text/* if possible, otherwise take whatever is there.
+    request.headers.set(HttpHeaders.ACCEPT, "text/plain, text/*, */*");
+    if (encoding != null) {
+      request.headers.set(HttpHeaders.ACCEPT_CHARSET, encoding.name);
+    }
+    HttpClientResponse response = await request.close();
+    encoding ??= Encoding.getByName(response.headers.contentType?.charset);
+    if (encoding == null || encoding == LATIN1) {
+      // Default to LATIN-1 if no encoding found.
+      // Special case LATIN-1 since it is common and doesn't need decoding.
+      int length = response.contentLength;
+      if (length < 0) length = 0;
+      Uint8Buffer buffer = new Uint8Buffer(length);
+      await for (var bytes in response) {
+        buffer.addAll(bytes);
+      }
+      var byteList = new Uint8List.view(buffer.buffer, 0, buffer.length);
+      return new String.fromCharCodes(byteList);
+    }
+    return response.transform(encoding.decoder).join();
+  }
+  if (uri.scheme == "data") {
+    return uri.data.contentAsString(encoding: encoding);
+  }
+  throw new UnsupportedError("Unsupported scheme: $uri");
+}
+
+Future<HttpClientResponse> _httpGet(Uri uri) async {
+  HttpClientRequest request = await new HttpClient().getUrl(uri);
+    request.headers.set(HttpHeaders.ACCEPT, "application/octet-stream, */*");
+  return request.close();
+}
diff --git a/lib/src/loader.dart b/lib/src/loader.dart
new file mode 100644
index 0000000..b6b4e67
--- /dev/null
+++ b/lib/src/loader.dart
@@ -0,0 +1,92 @@
+// 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.
+
+import "dart:async" show Future, Stream;
+import "dart:convert" show Encoding;
+import "dart:isolate" show Isolate;
+import "io.dart" as io;  // TODO: make this import configuration dependent.
+
+/// Resource loading strategy.
+///
+/// An abstraction of the functionality needed to load resources.
+///
+/// Implementations of this interface decide which URI schemes they support.
+abstract class ResourceLoader {
+  /// A resource loader that can load as many of the following URI
+  /// schemes as are supported by the platform:
+  /// * file
+  /// * http
+  /// * https
+  /// * data
+  /// * package
+  ///
+  /// (For example, file: URIs are not supported in the browser).
+  /// Relative URI references are accepted - they are resolved against
+  /// [Uri.base] before being loaded.
+  static const ResourceLoader defaultLoader = const PackageLoader();
+
+  /// Reads the file located by [uri] as a stream of bytes.
+  Stream<List<int>> openRead(Uri uri);
+
+  /// Reads the file located by [uri] as a list of bytes.
+  Future<List<int>> readAsBytes(Uri uri);
+
+  /// Reads the file located by [uri] as a [String].
+  ///
+  /// The file bytes are decoded using [encoding], if provided.
+  ///
+  /// If [encoding] is omitted, the default for the `file:` scheme is UTF-8.
+  /// For `http`, `https` and `data` URIs, the Content-Type header's charset
+  /// is used, if available and recognized by [Encoding.getByName],
+  /// otherwise it defaults to Latin-1 for `http` and `https`
+  /// and to ASCII for `data` URIs.
+  Future<String> readAsString(Uri uri, { Encoding encoding });
+}
+
+/// Default implementation of [ResourceLoader].
+///
+/// Uses the system's available loading functionality to implement the
+/// loading functions.
+///
+/// Supports `http:`, `https:`, `file:` and `data:` URIs.
+class DefaultLoader implements ResourceLoader {
+  const DefaultLoader();
+
+  Stream<List<int>> openRead(Uri uri) => io.readAsStream(uri);
+
+  Future<List<int>> readAsBytes(Uri uri) => io.readAsBytes(uri);
+
+  Future<String> readAsString(Uri uri, { Encoding encoding }) =>
+      io.readAsString(uri, encoding);
+}
+
+
+/// Implementation of [ResourceLoader] that accepts relative and package: URIs.
+///
+/// Like [DefaultLoader] except that it resolves package URIs and relative
+/// URI references as well.
+///
+/// This class may be useful when you don't want to bother creating a [Resource]
+/// object, and just want to load a resource directly.
+class PackageLoader implements ResourceLoader {
+  const PackageLoader();
+
+  Stream<List<int>> openRead(Uri uri) async* {
+    yield* io.readAsStream(await resolveUri(uri));
+  }
+
+  Future<List<int>> readAsBytes(Uri uri) async =>
+      io.readAsBytes(await resolveUri(uri));
+
+  Future<String> readAsString(Uri uri, { Encoding encoding }) async =>
+      io.readAsString(await resolveUri(uri), encoding);
+}
+
+/// Helper function for resolving to a non-relative, non-package URI.
+Future<Uri> resolveUri(Uri uri) async {
+  if (uri.scheme == "package") {
+    return Isolate.resolvePackageUri(uri);
+  }
+  return Uri.base.resolveUri(uri);
+}
diff --git a/lib/src/resource.dart b/lib/src/resource.dart
index 2fef5f6..924ec05 100644
--- a/lib/src/resource.dart
+++ b/lib/src/resource.dart
@@ -1,218 +1,87 @@
-// 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);

-  }

-}

+// 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.
+
+import "dart:async" show Future, Stream;
+import "dart:convert" show Encoding;
+import "dart:isolate" show Isolate;
+import "loader.dart";
+
+/// 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] must be either a [Uri] or 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.
+  ///
+  /// If [loader] is provided, it is used to load absolute non-package URIs.
+  /// Package: URIs are resolved to a non-package URI before being loaded, so
+  /// the loader doesn't have to support package: URIs, nor does it need to
+  /// support relative URI references.
+  /// If [loader] is omitted, a default implementation is used which supports
+  /// as many of `http`, `https`, `file` and `data` as are available on the
+  /// current platform.
+  const factory Resource(uri, {ResourceLoader loader}) = _Resource;
+
+  /// The location URI of this resource.
+  ///
+  /// This is a [Uri] of the `uri` parameter given to the constructor.
+  /// If the parameter was a string that did not contain a valid URI,
+  /// reading `uri` will fail.
+  Uri get uri;
+
+  /// Reads the resource content as a stream of bytes.
+  Stream<List<int>> openRead();
+
+  /// Reads the resource content as a single list of bytes.
+  Future<List<int>> readAsBytes();
+
+  /// Reads 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 _Resource implements Resource {
+  /// Loading strategy for the resource.
+  final ResourceLoader _loader;
+
+  /// The URI of the resource.
+  final _uri;
+
+  const _Resource(uri, {ResourceLoader loader})
+      : _uri = uri, _loader = (loader != null) ? loader : const DefaultLoader();
+  // TODO: Make this `loader ?? const DefaultLoader()` when ?? is const.
+
+  Uri get uri => (_uri is String) ? Uri.parse(_uri) : (_uri as Uri);
+
+  Stream<List<int>> openRead() async* {
+    Uri uri = await _resolvedUri;
+    yield* _loader.openRead(uri);
+  }
+
+  Future<List<int>> readAsBytes() async {
+    Uri uri = await _resolvedUri;
+    return _loader.readAsBytes(uri);
+  }
+
+  Future<String> readAsString({Encoding encoding}) async {
+    Uri uri = await _resolvedUri;
+    return _loader.readAsString(uri, encoding: encoding);
+  }
+
+  Future<Uri> get _resolvedUri => resolveUri(uri);
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 6770e7f..a76584e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,7 +1,14 @@
-name: resource

-version: 0.1.0

-dependencies:

-  http: "any"

-  typed_data: "any"

-dev_dependencies:

-  test: "any"

+name: resource
+version: 1.0.0
+description: Reading resource data from (package and other) files.
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/resource
+
+environment:
+  sdk: '>=1.14.0-any <2.0.0'
+
+dependencies:
+  package_config: "^0.0.4"
+  typed_data: "^1.0.0"
+dev_dependencies:
+  test: "^0.12.0"
diff --git a/test/loader_data_test.dart b/test/loader_data_test.dart
new file mode 100644
index 0000000..8c84f7d
--- /dev/null
+++ b/test/loader_data_test.dart
@@ -0,0 +1,51 @@
+// 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.
+
+import "dart:async";
+import "dart:convert";
+import "dart:io";
+
+import "package:resource/resource.dart";
+import "package:test/test.dart";
+
+const content = "Rødgrød med fløde";
+
+main() {
+  var dir;
+  testFile(Encoding encoding, bool base64) {
+    group("${encoding.name}${base64 ? " base64" : ""}", () {
+      var uri;
+      setUp(() {
+        var dataUri = new UriData.fromString(content,
+                                             encoding: encoding,
+                                             base64: base64);
+        uri = dataUri.uri;
+      });
+
+      test("read string", () async {
+        var loader = ResourceLoader.defaultLoader;
+        String string = await loader.readAsString(uri, encoding: encoding);
+        expect(string, content);
+      });
+
+      test("read bytes", () async {
+        var loader = ResourceLoader.defaultLoader;
+        List<int> bytes = await loader.readAsBytes(uri);
+        expect(bytes, encoding.encode(content));
+      });
+
+      test("read byte stream", () async {
+        var loader = ResourceLoader.defaultLoader;
+        Stream<int> bytes = loader.openRead(uri);
+        var buffer = [];
+        await bytes.forEach(buffer.addAll);
+        expect(buffer, encoding.encode(content));
+      });
+    });
+  }
+  testFile(LATIN1, true);
+  testFile(LATIN1, false);
+  testFile(UTF8, true);
+  testFile(UTF8, false);
+}
diff --git a/test/loader_file_test.dart b/test/loader_file_test.dart
new file mode 100644
index 0000000..aa4c238
--- /dev/null
+++ b/test/loader_file_test.dart
@@ -0,0 +1,65 @@
+// 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.
+
+import "dart:async";
+import "dart:convert";
+import "dart:io";
+
+import "package:resource/resource.dart";
+import "package:test/test.dart";
+
+const content = "Rødgrød med fløde";
+
+
+main() {
+  var dir;
+  setUp(() {
+    dir = Directory.systemTemp.createTempSync('testdir');
+  });
+  testFile(Encoding encoding) {
+    group("${encoding.name}", () {
+      var file;
+      var uri;
+      setUp(() {
+        var dirUri = dir.uri;
+        uri = dirUri.resolve("file.txt");
+        file = new File.fromUri(uri);
+        file.createSync();
+        var sink = file.openWrite(encoding: encoding);
+        sink.write(content);
+        sink.close();
+      });
+
+      test("read string", () async {
+        var loader = ResourceLoader.defaultLoader;
+        String string = await loader.readAsString(uri, encoding: encoding);
+        expect(string, content);
+      });
+
+      test("read bytes", () async {
+        var loader = ResourceLoader.defaultLoader;
+        List<int> bytes = await loader.readAsBytes(uri);
+        expect(bytes, encoding.encode(content));
+      });
+
+      test("read byte stream", () async {
+        var loader = ResourceLoader.defaultLoader;
+        Stream<int> bytes = loader.openRead(uri);
+        var buffer = [];
+        await bytes.forEach(buffer.addAll);
+        expect(buffer, encoding.encode(content));
+      });
+
+      tearDown(() {
+        file.deleteSync();
+      });
+    });
+  }
+  testFile(LATIN1);
+  testFile(UTF8);
+
+  tearDown(() {
+    dir.delete(recursive: true);
+  });
+}
diff --git a/test/loader_http_test.dart b/test/loader_http_test.dart
new file mode 100644
index 0000000..c47fa27
--- /dev/null
+++ b/test/loader_http_test.dart
@@ -0,0 +1,92 @@
+// 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.
+
+import "dart:async";
+import "dart:convert";
+import "dart:io";
+
+import "package:resource/resource.dart";
+import "package:test/test.dart";
+
+const content = "Rødgrød med fløde";
+
+main() {
+  var server;
+  var uri;
+  setUp(() async {
+    server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0);
+    int port = server.port;
+    uri = Uri.parse("http://localhost:$port/default.html");
+    server.forEach((HttpRequest request) {
+      var accept = request.headers[HttpHeaders.ACCEPT];
+      var encodings = request.headers[HttpHeaders.ACCEPT_CHARSET];
+      var encoding = parseAcceptCharset(encodings);
+      request.response.headers.contentType =
+          new ContentType("text", "plain", charset: encoding.name);
+      request.response..write(content)
+                      ..close();
+    }).catchError(print);
+  });
+
+  test("Default encoding", () async {
+    var loader = ResourceLoader.defaultLoader;
+    String string = await loader.readAsString(uri);
+    expect(string, content);
+  });
+
+  test("Latin-1 encoding", () async {
+    var loader = ResourceLoader.defaultLoader;
+    String string = await loader.readAsString(uri, encoding: LATIN1);
+    expect(string, content);
+  });
+
+  test("UTF-8 encoding", () async {
+    var loader = ResourceLoader.defaultLoader;
+    String string = await loader.readAsString(uri, encoding: UTF8);
+    expect(string, content);
+  });
+
+  test("bytes", () async {
+    var loader = ResourceLoader.defaultLoader;
+    List<int> bytes = await loader.readAsBytes(uri);  // Sender uses Latin-1.
+    expect(bytes, content.codeUnits);
+  });
+
+  test("byte stream", () async {
+    var loader = ResourceLoader.defaultLoader;
+    Stream<int> bytes = loader.openRead(uri);  // Sender uses Latin-1.
+    var buffer = [];
+    await bytes.forEach(buffer.addAll);
+    expect(buffer, content.codeUnits);
+  });
+
+  tearDown(() {
+    server.close();
+    server = null;
+  });
+}
+
+
+Encoding parseAcceptCharset(List<String> headers) {
+  var encoding = LATIN1;
+  if (headers != null) {
+    var weight = 0.0;
+    var pattern = new RegExp(r"([\w-]+)(;\s*q=[\d.]+)?");
+    for (var acceptCharset in headers) {
+      for (var match in pattern.allMatches(acceptCharset)) {
+        var e = Encoding.getByName(match[1]);
+        if (e == null) continue;
+        var w = 1.0;
+        if (match[2] != null) {
+          w = double.parse(match[2]);
+        }
+        if (w > weight) {
+          weight = w;
+          encoding = e;
+        }
+      }
+    }
+  }
+  return encoding;
+}
diff --git a/test/resource_test.dart b/test/resource_test.dart
new file mode 100644
index 0000000..f5f2345
--- /dev/null
+++ b/test/resource_test.dart
@@ -0,0 +1,70 @@
+// 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.
+
+import "dart:async" show Future, Stream;
+import "dart:isolate" show Isolate;
+import "dart:convert" show Encoding, ASCII;
+import "package:package_config/packages.dart";
+import "package:package_config/discovery.dart";
+import "package:resource/resource.dart";
+import "package:test/test.dart";
+
+main() {
+  pkguri(path) => new Uri(scheme: "package", path: path);
+
+  Future<Uri> resolve(Uri source) async {
+    if (source.scheme == "package") {
+      return Isolate.resolvePackageUri(source);
+    }
+    return Uri.base.resolveUri(source);
+  }
+
+  group("loading", () {
+    testLoad(Uri uri) async {
+      LogLoader loader = new LogLoader();
+      var resource = new Resource(uri, loader: loader);
+      var res = await resource.openRead().toList();
+      var resolved = await resolve(uri);
+      expect(res, [[0, 0, 0]]);
+      res = await resource.readAsBytes();
+      expect(res, [0, 0, 0]);
+      res = await resource.readAsString(encoding: ASCII);
+      expect(res, "\x00\x00\x00");
+
+      expect(loader.requests, [["Stream", resolved],
+                               ["Bytes", resolved],
+                               ["String", resolved, ASCII]]);
+    }
+
+    test("load package: URIs", () async {
+      await testLoad(pkguri("foo/bar/baz"));
+      await testLoad(pkguri("bar/foo/baz"));
+    });
+    test("load non-pkgUri", () async {
+      await testLoad(Uri.parse("file://localhost/something?x#y"));
+      await testLoad(Uri.parse("http://auth/something?x#y"));
+      await testLoad(Uri.parse("https://auth/something?x#y"));
+      await testLoad(Uri.parse("data:,something?x"));
+      await testLoad(Uri.parse("unknown:/something"));
+    });
+  });
+}
+
+
+class LogLoader implements ResourceLoader {
+  final List requests = [];
+  void reset() { requests.clear(); }
+  Stream<List<int>> openRead(Uri uri) async* {
+    requests.add(["Stream", uri]);
+    yield [0x00, 0x00, 0x00];
+  }
+  Future<List<int>> readAsBytes(Uri uri) async {
+    requests.add(["Bytes", uri]);
+    return [0x00, 0x00, 0x00];
+  }
+  Future<String> readAsString(Uri uri, {Encoding encoding}) async {
+    requests.add(["String", uri, encoding]);
+    return "\x00\x00\x00";
+  }
+}
diff --git a/tool/travis.sh b/tool/travis.sh
new file mode 100755
index 0000000..5afae3c
--- /dev/null
+++ b/tool/travis.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# 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.
+
+# Fast fail the script on failures.
+set -e
+
+# Verify that the libraries are error free.
+dartanalyzer --fatal-warnings \
+  lib/sample.dart \
+  test/all_test.dart
+
+# Run the tests.
+pub run test