4
4
5
5
import 'package:path/path.dart' as p;
6
6
7
- /// Identifies an asset within a package .
7
+ /// A file in a `build_runner` build .
8
8
class AssetId implements Comparable <AssetId > {
9
- /// The name of the package containing this asset .
9
+ /// The package containing the file .
10
10
final String package;
11
11
12
- /// The path to the asset relative to the root directory of [package] .
12
+ /// The relative path of the file under the root of [package] .
13
13
///
14
- /// Source (i.e. read from disk) and generated (i.e. the output of a
15
- /// `Builder` ) assets all have paths. Even intermediate assets that are
16
- /// generated and then consumed by later transformations will still have a
17
- /// path used to identify it.
14
+ /// The path is relative and contains no parent references `..` , guaranteeing
15
+ /// that it is under [package] .
18
16
///
19
- /// Asset paths always use forward slashes as path separators, regardless of
20
- /// the host platform. Asset paths will always be within their package, that
21
- /// is they will never contain "../".
17
+ /// The path segment separator is `/` on all platforms.
22
18
final String path;
23
19
24
- /// Splits [path] into its components .
20
+ /// The segments of [path] .
25
21
List <String > get pathSegments => p.url.split (path);
26
22
27
- /// The file extension of the asset, if it has one, including the ".".
23
+ /// The file extension of [path] : the portion of its "basename" from the last
24
+ /// `.` to the end, including the `.` itself.
28
25
String get extension => p.extension (path);
29
26
30
- /// Creates a new [AssetId] at [path] within [package] .
27
+ /// An [AssetId] with the specified [path] under [package] .
31
28
///
32
- /// The [path] will be normalized: any backslashes will be replaced with
33
- /// forward slashes (regardless of host OS) and "." and ".." will be removed
34
- /// where possible.
29
+ /// The [path] must be relative and under [package] , or an [ArgumentError] is
30
+ /// thrown.
31
+ ///
32
+ /// The [path] is normalized: `\` is replaced with `/` , then `.` and `..` are removed.
35
33
AssetId (this .package, String path) : path = _normalizePath (path);
36
34
37
- /// Creates a new [AssetId] from an [uri] String.
35
+ /// Creates an [AssetId] from a [uri] .
36
+ ///
37
+ /// The [uri] can be relative, in which case it will be resolved relative to
38
+ /// [from] ; if [uri] is relative and [from] is not specified, an
39
+ /// [ArgumentError] is thrown.
38
40
///
39
- /// This gracefully handles `package: ` or `asset:` URIs .
41
+ /// Or, [uri] can have the scheme `package` or `asset` .
40
42
///
41
- /// Resolve a `package:` URI when creating an [AssetId] from an `import` or
42
- /// `export` directive pointing to a package's _lib_ directory:
43
- /// ```dart
44
- /// AssetId assetOfDirective(UriReferencedElement element) {
45
- /// return new AssetId.resolve(element.uri);
46
- /// }
47
- /// ```
43
+ /// A `package` [uri] has the form `package:$package/$path` and references
44
+ /// the specified path within the `lib/` directory of the specified package, just like
45
+ /// a `package` URI in Dart source code.
48
46
///
49
- /// When resolving a relative URI with no scheme, specifyg the origin asset
50
- /// ([from] ) - otherwise an [ArgumentError] will be thrown.
51
- /// ```dart
52
- /// AssetId assetOfDirective(AssetId origin, UriReferencedElement element) {
53
- /// return new AssetId.resolve(element.uri, from: origin);
54
- /// }
55
- /// ```
47
+ /// An `asset` [uri] has the form `asset:$package/$path` and references the
48
+ /// specified path within the root of the specified package.
56
49
///
57
- /// `asset:` uris have the format '$package/$path', including the top level
58
- /// directory.
50
+ /// If [uri] has any other scheme then [UnsupportedError] is thrown.
59
51
factory AssetId .resolve (Uri uri, {AssetId ? from}) {
60
- final resolved =
61
- uri.hasScheme
62
- ? uri
63
- : from != null
64
- ? from.uri.resolveUri (uri)
65
- : (throw ArgumentError .value (
66
- from,
67
- 'from' ,
68
- 'An AssetId "from" must be specified to resolve a relative '
69
- 'URI' ,
70
- ));
52
+ if (from == null && ! uri.hasScheme) {
53
+ throw ArgumentError .value (
54
+ from,
55
+ 'from' ,
56
+ 'An AssetId `from` must be specified to resolve a relative URI.' ,
57
+ );
58
+ }
59
+ final resolved = uri.hasScheme ? uri : from! .uri.resolveUri (uri);
71
60
if (resolved.scheme == 'package' ) {
72
61
return AssetId (
73
62
resolved.pathSegments.first,
@@ -80,46 +69,48 @@ class AssetId implements Comparable<AssetId> {
80
69
);
81
70
}
82
71
throw UnsupportedError (
83
- 'Cannot resolve $uri ; only "package" and "asset" schemes supported' ,
72
+ 'Cannot resolve $uri ; only "package" and "asset" schemes supported. ' ,
84
73
);
85
74
}
86
75
87
- /// Parses an [AssetId] string of the form " package|path/to/asset.txt" .
76
+ /// Parses an [AssetId] string of the form `$ package|$ path` .
88
77
///
89
- /// The [path] will be normalized: any backslashes will be replaced with
90
- /// forward slashes (regardless of host OS) and "." and ".." will be removed
91
- /// where possible.
92
- factory AssetId .parse (String description) {
93
- final parts = description.split ('|' );
78
+ /// If [id] does not match that form, a [FormatException] is thrown.
79
+ ///
80
+ /// See [AssetId.new] for restrictions on `path` and how it will be
81
+ /// normalized.
82
+ factory AssetId .parse (String id) {
83
+ final parts = id.split ('|' );
94
84
if (parts.length != 2 ) {
95
- throw FormatException ('Could not parse "$description ".' );
96
- }
97
-
98
- if (parts[0 ].isEmpty) {
99
- throw FormatException (
100
- 'Cannot have empty package name in "$description ".' ,
101
- );
102
- }
103
-
104
- if (parts[1 ].isEmpty) {
105
- throw FormatException ('Cannot have empty path in "$description ".' );
85
+ throw FormatException ('Could not parse "$id ".' );
106
86
}
107
87
108
88
return AssetId (parts[0 ], parts[1 ]);
109
89
}
110
90
111
- /// A `package:` URI suitable for use directly with other systems if this
112
- /// asset is under it's package's `lib/` directory, else an `asset:` URI
113
- /// suitable for use within build tools .
91
+ /// If [path] starts with `lib/` , the URI starting `package:` that refers to this asset.
92
+ ///
93
+ /// If not, the URI `asset:$package/$path` .
114
94
late final Uri uri = _constructUri (this );
115
95
116
- /// Deserializes an [AssetId] from [data] , which must be the result of
117
- /// calling [serialize] on an existing [AssetId] .
118
- AssetId .deserialize (List <dynamic > data)
119
- : package = data[0 ] as String ,
120
- path = data[1 ] as String ;
96
+ /// Returns an [AssetId] in [package] with path `$path$exension` .
97
+ AssetId addExtension (String extension ) => AssetId (package, '$path $extension ' );
98
+
99
+ /// Returns an [AssetId] in [package] with [extension] removed and
100
+ /// [newExtension] appended.
101
+ AssetId changeExtension (String newExtension) =>
102
+ AssetId (package, p.withoutExtension (path) + newExtension);
103
+
104
+ /// Deserializes a `List<dynamic>` from [serialize] .
105
+ AssetId .deserialize (List <dynamic > serialized)
106
+ : package = serialized[0 ] as String ,
107
+ path = serialized[1 ] as String ;
108
+
109
+ /// Serializes this [AssetId] to an `Object` that can be sent across isolates.
110
+ ///
111
+ /// See [AssetId.deserialize] .
112
+ Object serialize () => [package, path];
121
113
122
- /// Returns `true` if [other] is an [AssetId] with the same package and path.
123
114
@override
124
115
bool operator == (Object other) =>
125
116
other is AssetId && package == other.package && path == other.path;
@@ -134,40 +125,27 @@ class AssetId implements Comparable<AssetId> {
134
125
return path.compareTo (other.path);
135
126
}
136
127
137
- /// Returns a new [AssetId] with the same [package] as this one and with the
138
- /// [path] extended to include [extension] .
139
- AssetId addExtension (String extension ) => AssetId (package, '$path $extension ' );
140
-
141
- /// Returns a new [AssetId] with the same [package] and [path] as this one
142
- /// but with file extension [newExtension] .
143
- AssetId changeExtension (String newExtension) =>
144
- AssetId (package, p.withoutExtension (path) + newExtension);
145
-
128
+ /// Returns `$package|$path` , which can be converted back to an `AssetId`
129
+ /// using [AssetId.parse] .
146
130
@override
147
131
String toString () => '$package |$path ' ;
148
-
149
- /// Serializes this [AssetId] to an object that can be sent across isolates
150
- /// and passed to [AssetId.deserialize] .
151
- Object serialize () => [package, path];
152
132
}
153
133
154
134
String _normalizePath (String path) {
155
135
if (p.isAbsolute (path)) {
156
136
throw ArgumentError .value (path, 'Asset paths must be relative.' );
157
137
}
158
-
159
- // Normalize path separators so that they are always "/" in the AssetID.
160
138
path = path.replaceAll (r'\' , '/' );
161
139
162
140
// Collapse "." and "..".
163
- final collapsed = p.posix.normalize (path);
164
- if (collapsed .startsWith ('../' )) {
141
+ final result = p.posix.normalize (path);
142
+ if (result .startsWith ('../' )) {
165
143
throw ArgumentError .value (
166
144
path,
167
- 'Asset paths may not reach outside the package.' ,
145
+ 'Asset paths must be within the specified the package.' ,
168
146
);
169
147
}
170
- return collapsed ;
148
+ return result ;
171
149
}
172
150
173
151
Uri _constructUri (AssetId id) {
0 commit comments