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