OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library resoure.resource; | |
6 | |
7 import "dart:async" show Future, Stream; | 5 import "dart:async" show Future, Stream; |
8 import "dart:convert" show Encoding; | 6 import "dart:convert" show Encoding; |
9 import "dart:isolate" show Isolate; | 7 import "dart:isolate" show Isolate; |
10 import "package_resolver.dart"; | 8 import "loader.dart"; |
11 import "io.dart" as io; // Loading strategy. TODO: Be configuration dependent. | |
12 | |
13 /// A strategy for resolving package URIs | |
14 abstract class PackageResolver { | |
15 /// Cache of the current resolver, accessible directly after it has first | |
16 /// been found asynchronously. | |
17 static PackageResolver _current; | |
18 | |
19 /// The package resolution strategy used by the current isolate. | |
20 static final Future<PackageResolver> current = _findIsolateResolution(); | |
21 | |
22 PackageResolver(); | |
23 | |
24 /// Creates a resolver using a map from package name to package location. | |
25 factory PackageResolver.fromMap(Map<String, Uri> packages) = MapResolver; | |
26 | |
27 /// Creates a resolver using a package root. | |
28 factory PackageResolver.fromRoot(Uri packageRoot) = RootResolver; | |
29 | |
30 /// Resolves a package URI to its location. | |
31 /// | |
32 /// If [uri] does not have `package` as scheme, it is returned again. | |
33 /// Otherwise the package name is looked up, and if found, a location | |
34 /// for the package file is returned. | |
35 Future<Uri> resolve(Uri uri); | |
36 | |
37 /// Returns a [Resource] for the [uri] as resolved by this resolver. | |
38 Resource resource(Uri uri) { | |
39 return new _UriResource(this, uri); | |
40 } | |
41 | |
42 /// Finds the way the current isolate resolves package URIs. | |
43 /// | |
44 /// Is only called once, and when it has been called, the [_current] | |
45 /// resolver is initialized, so [UriResource] will be initialized | |
46 /// with the resolver directly. | |
47 static Future<PackageResolver> _findIsolateResolution() async { | |
48 var pair = await Future.wait([Isolate.packageRoot, Isolate.packageMap]); | |
49 Uri root = pair[0]; | |
50 if (root != null) { | |
51 _current = new RootResolver(root); | |
52 } else { | |
53 Map<String, Uri> map = pair[1]; | |
54 _current = new MapResolver(map); | |
55 } | |
56 return _current; | |
57 } | |
58 } | |
59 | 9 |
60 /// A resource that can be read into the program. | 10 /// A resource that can be read into the program. |
61 /// | 11 /// |
62 /// A resource is data that can be located using a URI and read into | 12 /// A resource is data that can be located using a URI and read into |
63 /// the program at runtime. | 13 /// the program at runtime. |
64 /// The URI may use the `package` scheme to read resources provided | 14 /// The URI may use the `package` scheme to read resources provided |
65 /// along with package sources. | 15 /// along with package sources. |
66 abstract class Resource { | 16 abstract class Resource { |
67 /// Creates a resource object with the given [uri] as location. | 17 /// Creates a resource object with the given [uri] as location. |
68 /// | 18 /// |
69 /// The `uri` is a string containing a valid URI. | 19 /// The [uri] must be either a [Uri] or a string containing a valid URI. |
70 /// If the string is not a valid URI, using any of the functions on | 20 /// If the string is not a valid URI, using any of the functions on |
71 /// the resource object will fail. | 21 /// the resource object will fail. |
72 /// | 22 /// |
73 /// The URI may be relative, in which case it will be resolved | 23 /// The URI may be relative, in which case it will be resolved |
74 /// against [Uri.base] before being used. | 24 /// against [Uri.base] before being used. |
75 /// | 25 /// |
76 /// The URI may use the `package` scheme, which is always supported. | 26 /// The URI may use the `package` scheme, which is always supported. |
77 /// Other schemes may also be supported where possible. | 27 /// Other schemes may also be supported where possible. |
78 const factory Resource(String uri) = _StringResource; | |
79 | |
80 /// Creates a resource object with the given [uri] as location. | |
81 /// | 28 /// |
82 /// The URI may be relative, in which case it will be resolved | 29 /// If [loader] is provided, it is used to load absolute non-package URIs. |
83 /// against [Uri.base] before being used. | 30 /// Package: URIs are resolved to a non-package URI before being loaded, so |
84 /// | 31 /// the loader doesn't have to support package: URIs, nor does it need to |
85 /// The URI may use the `package` scheme, which is always supported. | 32 /// support relative URI references. |
86 /// Other schemes may also be supported where possible. | 33 /// If [loader] is omitted, a default implementation is used which supports |
87 factory Resource.forUri(Uri uri) => | 34 /// as many of `http`, `https`, `file` and `data` as are available on the |
88 new _UriResource(PackageResolver._current, uri); | 35 /// current platform. |
36 const factory Resource(uri, {ResourceLoader loader}) = _Resource; | |
89 | 37 |
90 /// The location `uri` of this resource. | 38 /// The location `uri` of this resource. |
91 /// | 39 /// |
92 /// This is a [Uri] of the `uri` parameter given to the constructor. | 40 /// This is a [Uri] of the `uri` parameter given to the constructor. |
93 /// If the parameter was not a valid URI, reading `uri` may fail. | 41 /// If the parameter was not a valid URI, reading `uri` may fail. |
94 Uri get uri; | 42 Uri get uri; |
95 | 43 |
44 /// Read the resource content as a stream of bytes. | |
floitsch
2016/01/14 15:11:04
Reads
Lasse Reichstein Nielsen
2016/01/15 09:40:02
Done.
| |
96 Stream<List<int>> openRead(); | 45 Stream<List<int>> openRead(); |
97 | 46 |
47 /// Read the resource content as a single list of bytes. | |
floitsch
2016/01/14 15:11:04
ditto.
Lasse Reichstein Nielsen
2016/01/15 09:40:02
Done.
| |
98 Future<List<int>> readAsBytes(); | 48 Future<List<int>> readAsBytes(); |
99 | 49 |
100 /// Read the resource content as a string. | 50 /// Read the resource content as a string. |
101 /// | 51 /// |
102 /// The content is decoded into a string using an [Encoding]. | 52 /// The content is decoded into a string using an [Encoding]. |
103 /// If no other encoding is provided, it defaults to UTF-8. | 53 /// If no other encoding is provided, it defaults to UTF-8. |
104 Future<String> readAsString({Encoding encoding}); | 54 Future<String> readAsString({Encoding encoding}); |
105 } | 55 } |
106 | 56 |
107 class _StringResource implements Resource { | 57 class _Resource implements Resource { |
108 final String _uri; | 58 /// Loading strategy for the resource. |
109 | 59 final ResourceLoader _loader; |
110 const _StringResource(String uri) : _uri = uri; | |
111 | |
112 Uri get uri => Uri.parse(_uri); | |
113 | |
114 Stream<List<int>> openRead() { | |
115 return new _UriResource(PackageResolver._current, uri).openRead(); | |
116 } | |
117 Future<List<int>> readAsBytes() { | |
118 return new _UriResource(PackageResolver._current, uri).readAsBytes(); | |
119 } | |
120 Future<String> readAsString({Encoding encoding}) { | |
121 return new _UriResource(PackageResolver._current, uri) | |
122 .readAsString(encoding: encoding); | |
123 } | |
124 } | |
125 | |
126 class _UriResource implements Resource { | |
127 /// The strategy for resolving package: URIs. | |
128 /// | |
129 /// May be null intially. If so, the [PackageResolver.current] resolver is | |
130 /// used (and cached for later use). | |
131 PackageResolver _resolver; | |
132 | 60 |
133 /// The URI of the resource. | 61 /// The URI of the resource. |
134 final Uri uri; | 62 final _uri; |
135 | 63 |
136 _UriResource(this.resolver, Uri uri); | 64 const _Resource(uri, {ResourceLoader loader}) |
65 : _uri = uri, _loader = (loader != null) ? loader : const DefaultLoader(); | |
66 // TODO: Make this `loader ?? const DefaultLoader()` when ?? is const. | |
67 | |
68 Uri get uri => (_uri is String) ? Uri.parse(_uri) : (_uri as Uri); | |
137 | 69 |
138 Stream<List<int>> openRead() async* { | 70 Stream<List<int>> openRead() async* { |
139 Uri uri = await _resolve(this.uri); | 71 Uri uri = await _resolvedUri; |
140 return io.readAsStream(uri); | 72 yield* _loader.openRead(uri); |
141 } | 73 } |
142 | 74 |
143 Future<List<int>> readAsBytes() async { | 75 Future<List<int>> readAsBytes() async { |
144 Uri uri = await _resolve(this.uri); | 76 Uri uri = await _resolvedUri; |
145 return io.readAsBytes(uri); | 77 return _loader.readAsBytes(uri); |
146 } | 78 } |
147 | 79 |
148 Future<String> readAsString({Encoding encoding}) async { | 80 Future<String> readAsString({Encoding encoding}) async { |
149 Uri uri = await _resolve(this.uri); | 81 Uri uri = await _resolvedUri; |
150 return io.readAsString(uri, encoding); | 82 return _loader.readAsString(uri, encoding: encoding); |
151 } | 83 } |
152 | 84 |
153 static void _checkPackageUri(Uri uri) { | 85 Future<Uri> get _resolvedUri => resolveUri(uri); |
154 if (uri.scheme != "package") { | |
155 throw new ArgumentError.value(uri, "Not a package: URI"); | |
156 } | |
157 if (uri.hasAuthority) { | |
158 throw new ArgumentError.value(uri, | |
159 "A package: URI must not have an authority part"); | |
160 } | |
161 if (uri.path.isEmpty || uri.path.startsWith('/')) { | |
162 throw new ArgumentError.value(uri, | |
163 "A package: URI must have the form " | |
164 "'package:packageName/packagePath'"); | |
165 } | |
166 if (uri.hasQuery) { | |
167 throw new ArgumentError.value(uri, | |
168 "A package: URI must not have a query part"); | |
169 } | |
170 if (uri.hasFragment) { | |
171 throw new ArgumentError.value(uri, | |
172 "A package: URI must not have a fragment part"); | |
173 } | |
174 } | |
175 | |
176 Future<Uri> _resolve(Uri uri) async { | |
177 if (uri.scheme != "package") { | |
178 return Uri.base.resolveUri(uri); | |
179 } | |
180 _checkPackageUri(uri); | |
181 _resolver ??= await PackageResolver._current; | |
182 return _resolver.resolve(uri); | |
183 } | |
184 } | 86 } |
185 | 87 |
186 /// A [PackageResolver] based on a packags map. | 88 /// Validates that a package URI has a valid format. |
187 class MapResolver extends PackageResolver { | 89 /// |
188 Map<String, Uri> _mapping; | 90 /// Returns the length of the packageName. |
189 | 91 /// This is the index of the first `/` in `uri.path`. |
190 MapResolver(this._mapping); | 92 int validatePackageUri(Uri uri) { |
191 | 93 if (uri.scheme != "package") { |
192 Uri resolve(Uri uri) { | 94 throw new ArgumentError.value(uri, "Not a package: URI"); |
193 if (uri.scheme != "package") return uri; | |
194 var path = uri.path; | |
195 int slashIndex = path.indexOf('/'); | |
196 if (slashIndex <= 0) { | |
197 throw new ArgumentError.value(uri, "Invalid package URI"); | |
198 } | |
199 int packageName = path.substring(0, slashIndex); | |
200 var base = _mapping[packageName]; | |
201 if (base != null) { | |
202 int packagePath = path.substring(slashIndex + 1); | |
203 return base.resolveUri(new Uri(path: packagePath)); | |
204 } | |
205 throw new UnsupportedError("No package named '$packageName' found"); | |
206 } | 95 } |
96 if (uri.hasAuthority) { | |
97 throw new ArgumentError.value(uri, | |
98 "A package: URI must not have an authority part"); | |
99 } | |
100 int index = uri.path.indexOf('/'); | |
101 if (index < 1) { | |
102 throw new ArgumentError.value(uri, | |
103 "A package: URI must have the form " | |
104 "'package:packageName/packagePath'"); | |
105 } | |
106 if (uri.hasQuery) { | |
107 throw new ArgumentError.value(uri, | |
108 "A package: URI must not have a query part"); | |
109 } | |
110 if (uri.hasFragment) { | |
111 throw new ArgumentError.value(uri, | |
112 "A package: URI must not have a fragment part"); | |
113 } | |
114 return index; | |
207 } | 115 } |
208 | |
209 /// A [PackageResolver] based on a package root. | |
210 class RootResolver extends PackageResolver { | |
211 Uri _root; | |
212 RootResolver(this._root); | |
213 | |
214 Uri resolve(Uri uri) { | |
215 if (uri.scheme != "package") return uri; | |
216 return _root.resolve(uri.path); | |
217 } | |
218 } | |
OLD | NEW |