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; | 5 library resoure.resource; |
6 | 6 |
7 import "dart:async" show Future, Stream; | 7 import "dart:async" show Future, Stream; |
8 import "dart:convert" show Encoding; | 8 import "dart:convert" show Encoding; |
9 import "dart:isolate" show Isolate; | 9 import "dart:isolate" show Isolate; |
10 import "package_resolver.dart"; | 10 import "loader.dart"; |
11 import "io.dart" as io; // Loading strategy. TODO: Be configuration dependent. | |
12 | 11 |
13 /// A strategy for resolving package URIs | 12 /// A strategy for resolving package URIs |
14 abstract class PackageResolver { | 13 abstract class PackageResolver { |
15 /// Cache of the current resolver, accessible directly after it has first | 14 /// Cache of the current isolate resolver, accessible directly after it has |
16 /// been found asynchronously. | 15 /// first been found asynchronously by reading [current]. |
17 static PackageResolver _current; | 16 static PackageResolver _current; |
18 | 17 |
19 /// The package resolution strategy used by the current isolate. | 18 /// The package resolution strategy used by the current isolate. |
20 static final Future<PackageResolver> current = _findIsolateResolution(); | 19 static final Future<PackageResolver> current = _findIsolateResolution(); |
21 | 20 |
22 PackageResolver(); | 21 PackageResolver(); |
23 | 22 |
24 /// Creates a resolver using a map from package name to package location. | 23 /// Creates a resolver using a map from package name to package location. |
25 factory PackageResolver.fromMap(Map<String, Uri> packages) = MapResolver; | 24 factory PackageResolver.fromMap(Map<String, Uri> packages) = MapResolver; |
26 | 25 |
27 /// Creates a resolver using a package root. | 26 /// Creates a resolver using a package root. |
28 factory PackageResolver.fromRoot(Uri packageRoot) = RootResolver; | 27 factory PackageResolver.fromRoot(Uri packageRoot) = RootResolver; |
29 | 28 |
30 /// Resolves a package URI to its location. | 29 /// Resolves a package URI to its location. |
31 /// | 30 /// |
32 /// If [uri] does not have `package` as scheme, it is returned again. | 31 /// If [uri] does not have `package` as scheme, it is returned as-is. |
33 /// Otherwise the package name is looked up, and if found, a location | 32 /// Otherwise the package name of the package URI is looked up, |
34 /// for the package file is returned. | 33 /// and if found, a location for the package file is returned. |
35 Future<Uri> resolve(Uri uri); | 34 /// |
35 /// Returns `null` if the package name is not recognized. | |
36 /// | |
37 /// Fails if the [uri] is a `package:` URI but has an invalid format. | |
38 /// Package URIs must have the format: | |
39 /// | |
40 /// package:packageName/someValidPath | |
41 /// | |
42 /// with no authority, no query, no fragment, and a non-empty package name. | |
43 Uri resolve(Uri uri); | |
36 | 44 |
37 /// Returns a [Resource] for the [uri] as resolved by this resolver. | 45 /// Returns a [Resource] for the [uri] as resolved by this resolver. |
38 Resource resource(Uri uri) { | 46 /// |
39 return new _UriResource(this, uri); | 47 /// If [loader] is provided, it will be used to load the resource. |
48 /// If omitted, it defaults to a default system loader. | |
49 Resource resource(Uri uri, { ResourceLoader loader }) { | |
Bob Nystrom
2015/10/08 16:48:33
Can this have a verb phrase name? Maybe "loadResou
Lasse Reichstein Nielsen
2015/10/08 17:20:54
createResource?
Bob Nystrom
2015/10/08 17:32:20
"create" feels like the wrong word to me because t
floitsch
2015/10/13 12:47:50
I would go with "get".
| |
50 return new UriResource(this, loader ?? const DefaultLoader(), uri); | |
40 } | 51 } |
41 | 52 |
42 /// Finds the way the current isolate resolves package URIs. | 53 /// Finds the way the current isolate resolves package URIs. |
43 /// | 54 /// |
44 /// Is only called once, and when it has been called, the [_current] | 55 /// Is only called once, and when it has been called, the [_current] |
45 /// resolver is initialized, so [UriResource] will be initialized | 56 /// resolver is initialized, so [UriResource] will be initialized |
46 /// with the resolver directly. | 57 /// with the resolver directly. |
47 static Future<PackageResolver> _findIsolateResolution() async { | 58 static Future<PackageResolver> _findIsolateResolution() async { |
48 var pair = await Future.wait([Isolate.packageRoot, Isolate.packageMap]); | 59 var pair = await Future.wait([Isolate.packageRoot, Isolate.packageMap]); |
49 Uri root = pair[0]; | 60 Uri root = pair[0]; |
50 if (root != null) { | 61 if (root != null) { |
51 _current = new RootResolver(root); | 62 _current = new RootResolver(root); |
52 } else { | 63 } else { |
53 Map<String, Uri> map = pair[1]; | 64 Map<String, Uri> map = pair[1]; |
54 _current = new MapResolver(map); | 65 _current = new MapResolver(map); |
55 } | 66 } |
56 return _current; | 67 return _current; |
57 } | 68 } |
58 } | 69 } |
59 | 70 |
71 | |
60 /// A resource that can be read into the program. | 72 /// A resource that can be read into the program. |
61 /// | 73 /// |
62 /// A resource is data that can be located using a URI and read into | 74 /// A resource is data that can be located using a URI and read into |
63 /// the program at runtime. | 75 /// the program at runtime. |
64 /// The URI may use the `package` scheme to read resources provided | 76 /// The URI may use the `package` scheme to read resources provided |
65 /// along with package sources. | 77 /// along with package sources. |
66 abstract class Resource { | 78 abstract class Resource { |
67 /// Creates a resource object with the given [uri] as location. | 79 /// Creates a resource object with the given [uri] as location. |
68 /// | 80 /// |
69 /// The `uri` is a string containing a valid URI. | 81 /// The `uri` is a string containing a valid URI. |
70 /// If the string is not a valid URI, using any of the functions on | 82 /// If the string is not a valid URI, using any of the functions on |
71 /// the resource object will fail. | 83 /// the resource object will fail. |
72 /// | 84 /// |
73 /// The URI may be relative, in which case it will be resolved | 85 /// The URI may be relative, in which case it will be resolved |
74 /// against [Uri.base] before being used. | 86 /// against [Uri.base] before being used. |
75 /// | 87 /// |
76 /// The URI may use the `package` scheme, which is always supported. | 88 /// The URI may use the `package` scheme, which is always supported. |
77 /// Other schemes may also be supported where possible. | 89 /// Other schemes may also be supported where possible. |
78 const factory Resource(String uri) = _StringResource; | 90 const factory Resource(String uri) = StringResource; |
79 | 91 |
80 /// Creates a resource object with the given [uri] as location. | 92 /// Creates a resource object with the given [uri] as location. |
81 /// | 93 /// |
82 /// The URI may be relative, in which case it will be resolved | 94 /// The URI may be relative, in which case it will be resolved |
83 /// against [Uri.base] before being used. | 95 /// against [Uri.base] before being used. |
84 /// | 96 /// |
85 /// The URI may use the `package` scheme, which is always supported. | 97 /// The URI may use the `package` scheme, which is always supported. |
86 /// Other schemes may also be supported where possible. | 98 /// Other schemes may also be supported where possible. |
87 factory Resource.forUri(Uri uri) => | 99 factory Resource.forUri(Uri uri, {ResourceLoader loader}) => |
floitsch
2015/10/13 12:47:50
I think we often used "fromX" for this use-case.
"
| |
88 new _UriResource(PackageResolver._current, uri); | 100 new UriResource(PackageResolver._current, loader, uri); |
89 | 101 |
90 /// The location `uri` of this resource. | 102 /// The location `uri` of this resource. |
91 /// | 103 /// |
92 /// This is a [Uri] of the `uri` parameter given to the constructor. | 104 /// 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. | 105 /// If the parameter was not a valid URI, reading `uri` may fail. |
94 Uri get uri; | 106 Uri get uri; |
95 | 107 |
108 /// Read the resource content as a stream of bytes. | |
96 Stream<List<int>> openRead(); | 109 Stream<List<int>> openRead(); |
97 | 110 |
111 /// Read the resource content as a single list of bytes. | |
98 Future<List<int>> readAsBytes(); | 112 Future<List<int>> readAsBytes(); |
99 | 113 |
100 /// Read the resource content as a string. | 114 /// Read the resource content as a string. |
101 /// | 115 /// |
102 /// The content is decoded into a string using an [Encoding]. | 116 /// The content is decoded into a string using an [Encoding]. |
103 /// If no other encoding is provided, it defaults to UTF-8. | 117 /// If no other encoding is provided, it defaults to UTF-8. |
104 Future<String> readAsString({Encoding encoding}); | 118 Future<String> readAsString({Encoding encoding}); |
105 } | 119 } |
106 | 120 |
107 class _StringResource implements Resource { | 121 /// Implementation of [new Resoure] constructor. |
Bob Nystrom
2015/10/08 16:48:33
"Resource".
Lasse Reichstein Nielsen
2015/10/08 17:20:54
Acknowledged.
| |
122 /// | |
123 /// Stores only a plain string, and | |
Bob Nystrom
2015/10/08 16:48:33
...?
Lasse Reichstein Nielsen
2015/10/08 17:20:54
Maybe it's all it does? :)
Will fix.
| |
124 class StringResource implements Resource { | |
108 final String _uri; | 125 final String _uri; |
109 | 126 |
110 const _StringResource(String uri) : _uri = uri; | 127 const StringResource(String uri) : _uri = uri; |
111 | 128 |
112 Uri get uri => Uri.parse(_uri); | 129 Uri get uri => Uri.parse(_uri); |
113 | 130 |
131 UriResource get _asUriResource => | |
132 new UriResource(PackageResolver._current, const DefaultLoader(), uri); | |
133 | |
114 Stream<List<int>> openRead() { | 134 Stream<List<int>> openRead() { |
115 return new _UriResource(PackageResolver._current, uri).openRead(); | 135 return _asUriResource.openRead(); |
116 } | 136 } |
117 Future<List<int>> readAsBytes() { | 137 Future<List<int>> readAsBytes() { |
118 return new _UriResource(PackageResolver._current, uri).readAsBytes(); | 138 return _asUriResource.readAsBytes(); |
119 } | 139 } |
120 Future<String> readAsString({Encoding encoding}) { | 140 Future<String> readAsString({Encoding encoding}) { |
121 return new _UriResource(PackageResolver._current, uri) | 141 return _asUriResource.readAsString(encoding: encoding); |
122 .readAsString(encoding: encoding); | 142 } |
123 } | 143 } |
124 } | 144 |
125 | 145 class UriResource implements Resource { |
126 class _UriResource implements Resource { | |
127 /// The strategy for resolving package: URIs. | 146 /// The strategy for resolving package: URIs. |
128 /// | 147 /// |
129 /// May be null intially. If so, the [PackageResolver.current] resolver is | 148 /// May be null intially. If so, the [PackageResolver.current] resolver is |
130 /// used (and cached for later use). | 149 /// used (and cached for later use). |
131 PackageResolver _resolver; | 150 PackageResolver _resolver; |
132 | 151 |
152 /// Loading strategy for the resource. | |
153 ResourceLoader _loader; | |
154 | |
133 /// The URI of the resource. | 155 /// The URI of the resource. |
134 final Uri uri; | 156 final Uri uri; |
135 | 157 |
136 _UriResource(this.resolver, Uri uri); | 158 UriResource(this._resolver, this._loader, this.uri); |
137 | 159 |
138 Stream<List<int>> openRead() async* { | 160 Stream<List<int>> openRead() async* { |
139 Uri uri = await _resolve(this.uri); | 161 Uri uri = await _resolve(this.uri); |
140 return io.readAsStream(uri); | 162 yield* _loader.openRead(uri); |
141 } | 163 } |
142 | 164 |
143 Future<List<int>> readAsBytes() async { | 165 Future<List<int>> readAsBytes() async { |
144 Uri uri = await _resolve(this.uri); | 166 Uri uri = await _resolve(this.uri); |
145 return io.readAsBytes(uri); | 167 return _loader.readAsBytes(uri); |
146 } | 168 } |
147 | 169 |
148 Future<String> readAsString({Encoding encoding}) async { | 170 Future<String> readAsString({Encoding encoding}) async { |
149 Uri uri = await _resolve(this.uri); | 171 Uri uri = await _resolve(this.uri); |
150 return io.readAsString(uri, encoding); | 172 return _loader.readAsString(uri, encoding: encoding); |
151 } | |
152 | |
153 static void _checkPackageUri(Uri 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 } | 173 } |
175 | 174 |
176 Future<Uri> _resolve(Uri uri) async { | 175 Future<Uri> _resolve(Uri uri) async { |
177 if (uri.scheme != "package") { | 176 if (uri.scheme != "package") { |
178 return Uri.base.resolveUri(uri); | 177 return Uri.base.resolveUri(uri); |
179 } | 178 } |
180 _checkPackageUri(uri); | 179 if (_resolver == null) { |
181 _resolver ??= await PackageResolver._current; | 180 _resolver = await PackageResolver.current; |
182 return _resolver.resolve(uri); | 181 } |
182 var result = _resolver.resolve(uri); | |
183 return result; | |
183 } | 184 } |
184 } | 185 } |
185 | 186 |
186 /// A [PackageResolver] based on a packags map. | 187 /// A [PackageResolver] based on a packags map. |
187 class MapResolver extends PackageResolver { | 188 class MapResolver extends PackageResolver { |
188 Map<String, Uri> _mapping; | 189 Map<String, Uri> _mapping; |
189 | 190 |
190 MapResolver(this._mapping); | 191 MapResolver(this._mapping); |
191 | 192 |
192 Uri resolve(Uri uri) { | 193 Uri resolve(Uri uri) { |
193 if (uri.scheme != "package") return uri; | 194 if (uri.scheme != "package") return uri; |
194 var path = uri.path; | 195 int slashIndex = validatePackageUri(uri); |
195 int slashIndex = path.indexOf('/'); | 196 String path = uri.path; |
196 if (slashIndex <= 0) { | 197 String packageName = path.substring(0, slashIndex); |
197 throw new ArgumentError.value(uri, "Invalid package URI"); | 198 Uri base = _mapping[packageName]; |
198 } | |
199 int packageName = path.substring(0, slashIndex); | |
200 var base = _mapping[packageName]; | |
201 if (base != null) { | 199 if (base != null) { |
202 int packagePath = path.substring(slashIndex + 1); | 200 String packagePath = path.substring(slashIndex + 1); |
203 return base.resolveUri(new Uri(path: packagePath)); | 201 return base.resolveUri(new Uri(path: packagePath)); |
204 } | 202 } |
205 throw new UnsupportedError("No package named '$packageName' found"); | 203 return null; |
206 } | 204 } |
207 } | 205 } |
208 | 206 |
209 /// A [PackageResolver] based on a package root. | 207 /// A [PackageResolver] based on a package root. |
210 class RootResolver extends PackageResolver { | 208 class RootResolver extends PackageResolver { |
211 Uri _root; | 209 Uri _root; |
212 RootResolver(this._root); | 210 |
211 RootResolver(this._root) { | |
212 if (_root.path.contains("local")) { | |
213 try { throw 0; } catch (e, s) { print(s); } | |
214 throw "WHAT?"; | |
Bob Nystrom
2015/10/08 16:48:33
?
Lasse Reichstein Nielsen
2015/10/08 17:20:54
Whoops, debug code.
It did find the bug!
| |
215 } | |
216 } | |
213 | 217 |
214 Uri resolve(Uri uri) { | 218 Uri resolve(Uri uri) { |
215 if (uri.scheme != "package") return uri; | 219 if (uri.scheme != "package") return uri; |
220 validatePackageUri(uri); | |
216 return _root.resolve(uri.path); | 221 return _root.resolve(uri.path); |
217 } | 222 } |
218 } | 223 } |
224 | |
225 /// Validates that a package URI has a valid format. | |
226 /// | |
227 /// Returns the length of the packageName. | |
228 /// This is the index of the first `/` in `uri.path`. | |
229 int validatePackageUri(Uri uri) { | |
230 if (uri.scheme != "package") { | |
231 throw new ArgumentError.value(uri, "Not a package: URI"); | |
232 } | |
233 if (uri.hasAuthority) { | |
234 throw new ArgumentError.value(uri, | |
235 "A package: URI must not have an authority part"); | |
236 } | |
237 int index = uri.path.indexOf('/'); | |
238 if (index < 1) { | |
239 throw new ArgumentError.value(uri, | |
240 "A package: URI must have the form " | |
241 "'package:packageName/packagePath'"); | |
242 } | |
243 if (uri.hasQuery) { | |
244 throw new ArgumentError.value(uri, | |
245 "A package: URI must not have a query part"); | |
246 } | |
247 if (uri.hasFragment) { | |
248 throw new ArgumentError.value(uri, | |
249 "A package: URI must not have a fragment part"); | |
250 } | |
251 return index; | |
252 } | |
253 | |
OLD | NEW |