|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /** |
|
6 * Handling native paths. |
|
7 * |
|
8 * This module contains a number of functions destined to simplify |
|
9 * working with native paths through a cross-platform API. Functions |
|
10 * of this module will only work with the following assumptions: |
|
11 * |
|
12 * - paths are valid; |
|
13 * - paths are defined with one of the grammars that this module can |
|
14 * parse (see later); |
|
15 * - all path concatenations go through function |join|. |
|
16 */ |
|
17 |
|
18 "use strict"; |
|
19 |
|
20 // Boilerplate used to be able to import this module both from the main |
|
21 // thread and from worker threads. |
|
22 if (typeof Components != "undefined") { |
|
23 Components.utils.importGlobalProperties(["URL"]); |
|
24 // Global definition of |exports|, to keep everybody happy. |
|
25 // In non-main thread, |exports| is provided by the module |
|
26 // loader. |
|
27 this.exports = {}; |
|
28 } else if (typeof "module" == "undefined" || typeof "exports" == "undefined") { |
|
29 throw new Error("Please load this module using require()"); |
|
30 } |
|
31 |
|
32 let EXPORTED_SYMBOLS = [ |
|
33 "basename", |
|
34 "dirname", |
|
35 "join", |
|
36 "normalize", |
|
37 "split", |
|
38 "toFileURI", |
|
39 "fromFileURI", |
|
40 ]; |
|
41 |
|
42 /** |
|
43 * Return the final part of the path. |
|
44 * The final part of the path is everything after the last "/". |
|
45 */ |
|
46 let basename = function(path) { |
|
47 return path.slice(path.lastIndexOf("/") + 1); |
|
48 }; |
|
49 exports.basename = basename; |
|
50 |
|
51 /** |
|
52 * Return the directory part of the path. |
|
53 * The directory part of the path is everything before the last |
|
54 * "/". If the last few characters of this part are also "/", |
|
55 * they are ignored. |
|
56 * |
|
57 * If the path contains no directory, return ".". |
|
58 */ |
|
59 let dirname = function(path) { |
|
60 let index = path.lastIndexOf("/"); |
|
61 if (index == -1) { |
|
62 return "."; |
|
63 } |
|
64 while (index >= 0 && path[index] == "/") { |
|
65 --index; |
|
66 } |
|
67 return path.slice(0, index + 1); |
|
68 }; |
|
69 exports.dirname = dirname; |
|
70 |
|
71 /** |
|
72 * Join path components. |
|
73 * This is the recommended manner of getting the path of a file/subdirectory |
|
74 * in a directory. |
|
75 * |
|
76 * Example: Obtaining $TMP/foo/bar in an OS-independent manner |
|
77 * var tmpDir = OS.Constants.Path.tmpDir; |
|
78 * var path = OS.Path.join(tmpDir, "foo", "bar"); |
|
79 * |
|
80 * Under Unix, this will return "/tmp/foo/bar". |
|
81 */ |
|
82 let join = function(...path) { |
|
83 // If there is a path that starts with a "/", eliminate everything before |
|
84 let paths = []; |
|
85 for (let subpath of path) { |
|
86 if (subpath == null) { |
|
87 throw new TypeError("invalid path component"); |
|
88 } |
|
89 if (subpath.length != 0 && subpath[0] == "/") { |
|
90 paths = [subpath]; |
|
91 } else { |
|
92 paths.push(subpath); |
|
93 } |
|
94 } |
|
95 return paths.join("/"); |
|
96 }; |
|
97 exports.join = join; |
|
98 |
|
99 /** |
|
100 * Normalize a path by removing any unneeded ".", "..", "//". |
|
101 */ |
|
102 let normalize = function(path) { |
|
103 let stack = []; |
|
104 let absolute; |
|
105 if (path.length >= 0 && path[0] == "/") { |
|
106 absolute = true; |
|
107 } else { |
|
108 absolute = false; |
|
109 } |
|
110 path.split("/").forEach(function(v) { |
|
111 switch (v) { |
|
112 case "": case ".":// fallthrough |
|
113 break; |
|
114 case "..": |
|
115 if (stack.length == 0) { |
|
116 if (absolute) { |
|
117 throw new Error("Path is ill-formed: attempting to go past root"); |
|
118 } else { |
|
119 stack.push(".."); |
|
120 } |
|
121 } else { |
|
122 if (stack[stack.length - 1] == "..") { |
|
123 stack.push(".."); |
|
124 } else { |
|
125 stack.pop(); |
|
126 } |
|
127 } |
|
128 break; |
|
129 default: |
|
130 stack.push(v); |
|
131 } |
|
132 }); |
|
133 let string = stack.join("/"); |
|
134 return absolute ? "/" + string : string; |
|
135 }; |
|
136 exports.normalize = normalize; |
|
137 |
|
138 /** |
|
139 * Return the components of a path. |
|
140 * You should generally apply this function to a normalized path. |
|
141 * |
|
142 * @return {{ |
|
143 * {bool} absolute |true| if the path is absolute, |false| otherwise |
|
144 * {array} components the string components of the path |
|
145 * }} |
|
146 * |
|
147 * Other implementations may add additional OS-specific informations. |
|
148 */ |
|
149 let split = function(path) { |
|
150 return { |
|
151 absolute: path.length && path[0] == "/", |
|
152 components: path.split("/") |
|
153 }; |
|
154 }; |
|
155 exports.split = split; |
|
156 |
|
157 /** |
|
158 * Returns the file:// URI file path of the given local file path. |
|
159 */ |
|
160 // The case of %3b is designed to match Services.io, but fundamentally doesn't matter. |
|
161 let toFileURIExtraEncodings = {';': '%3b', '?': '%3F', "'": '%27', '#': '%23'}; |
|
162 let toFileURI = function toFileURI(path) { |
|
163 let uri = encodeURI(this.normalize(path)); |
|
164 |
|
165 // add a prefix, and encodeURI doesn't escape a few characters that we do |
|
166 // want to escape, so fix that up |
|
167 let prefix = "file://"; |
|
168 uri = prefix + uri.replace(/[;?'#]/g, match => toFileURIExtraEncodings[match]); |
|
169 |
|
170 return uri; |
|
171 }; |
|
172 exports.toFileURI = toFileURI; |
|
173 |
|
174 /** |
|
175 * Returns the local file path from a given file URI. |
|
176 */ |
|
177 let fromFileURI = function fromFileURI(uri) { |
|
178 let url = new URL(uri); |
|
179 if (url.protocol != 'file:') { |
|
180 throw new Error("fromFileURI expects a file URI"); |
|
181 } |
|
182 let path = this.normalize(decodeURIComponent(url.pathname)); |
|
183 return path; |
|
184 }; |
|
185 exports.fromFileURI = fromFileURI; |
|
186 |
|
187 |
|
188 //////////// Boilerplate |
|
189 if (typeof Components != "undefined") { |
|
190 this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS; |
|
191 for (let symbol of EXPORTED_SYMBOLS) { |
|
192 this[symbol] = exports[symbol]; |
|
193 } |
|
194 } |