]> git.lizzy.rs Git - rust.git/blob - src/libstd/fs.rs
cf7173f09576e7561670d1e7b89c428bfd526bb3
[rust.git] / src / libstd / fs.rs
1 /*
2 Module: fs
3
4 File system manipulation
5 */
6
7 import core::ctypes;
8 import core::vec;
9 import core::option;
10 import os;
11 import os::getcwd;
12 import os_fs;
13
14 #[abi = "cdecl"]
15 native mod rustrt {
16     fn rust_path_is_dir(path: str::sbuf) -> int;
17     fn rust_path_exists(path: str::sbuf) -> int;
18 }
19
20 /*
21 Function: path_sep
22
23 Get the default path separator for the host platform
24 */
25 fn path_sep() -> str { ret str::from_char(os_fs::path_sep); }
26
27 // FIXME: This type should probably be constrained
28 /*
29 Type: path
30
31 A path or fragment of a filesystem path
32 */
33 type path = str;
34
35 /*
36 Function: dirname
37
38 Get the directory portion of a path
39
40 Returns all of the path up to, but excluding, the final path separator.
41 The dirname of "/usr/share" will be "/usr", but the dirname of
42 "/usr/share/" is "/usr/share".
43
44 If the path is not prefixed with a directory, then "." is returned.
45 */
46 fn dirname(p: path) -> path {
47     let i: int = str::rindex(p, os_fs::path_sep as u8);
48     if i == -1 {
49         i = str::rindex(p, os_fs::alt_path_sep as u8);
50         if i == -1 { ret "."; }
51     }
52     ret str::substr(p, 0u, i as uint);
53 }
54
55 /*
56 Function: basename
57
58 Get the file name portion of a path
59
60 Returns the portion of the path after the final path separator.
61 The basename of "/usr/share" will be "share". If there are no
62 path separators in the path then the returned path is identical to
63 the provided path. If an empty path is provided or the path ends
64 with a path separator then an empty path is returned.
65 */
66 fn basename(p: path) -> path {
67     let i: int = str::rindex(p, os_fs::path_sep as u8);
68     if i == -1 {
69         i = str::rindex(p, os_fs::alt_path_sep as u8);
70         if i == -1 { ret p; }
71     }
72     let len = str::byte_len(p);
73     if i + 1 as uint >= len { ret p; }
74     ret str::slice(p, i + 1 as uint, len);
75 }
76
77
78 // FIXME: Need some typestate to avoid bounds check when len(pre) == 0
79 /*
80 Function: connect
81
82 Connects to path segments
83
84 Given paths `pre` and `post` this function will return a path
85 that is equal to `post` appended to `pre`, inserting a path separator
86 between the two as needed.
87 */
88 fn connect(pre: path, post: path) -> path {
89     let len = str::byte_len(pre);
90     ret if pre[len - 1u] == os_fs::path_sep as u8 {
91
92             // Trailing '/'?
93             pre + post
94         } else { pre + path_sep() + post };
95 }
96
97 /*
98 Function: connect_many
99
100 Connects a vector of path segments into a single path.
101
102 Inserts path separators as needed.
103 */
104 fn connect_many(paths: [path]) : vec::is_not_empty(paths) -> path {
105     ret if vec::len(paths) == 1u {
106         paths[0]
107     } else {
108         let rest = vec::slice(paths, 1u, vec::len(paths));
109         check vec::is_not_empty(rest);
110         connect(paths[0], connect_many(rest))
111     }
112 }
113
114 /*
115 Function: path_is_dir
116
117 Indicates whether a path represents a directory.
118 */
119 fn path_is_dir(p: path) -> bool {
120     ret str::as_buf(p, {|buf| rustrt::rust_path_is_dir(buf) != 0 });
121 }
122
123 /*
124 Function: path_exists
125
126 Indicates whether a path exists.
127 */
128 fn path_exists(p: path) -> bool {
129     ret str::as_buf(p, {|buf| rustrt::rust_path_exists(buf) != 0 });
130 }
131
132 /*
133 Function: make_dir
134
135 Creates a directory at the specified path.
136 */
137 fn make_dir(p: path, mode: ctypes::c_int) -> bool {
138     ret mkdir(p, mode);
139
140     #[cfg(target_os = "win32")]
141     fn mkdir(_p: path, _mode: ctypes::c_int) -> bool unsafe {
142         // FIXME: turn mode into something useful?
143         ret str::as_buf(_p, {|buf|
144             os::kernel32::CreateDirectoryA(
145                 buf, unsafe::reinterpret_cast(0))
146         });
147     }
148
149     #[cfg(target_os = "linux")]
150     #[cfg(target_os = "macos")]
151     #[cfg(target_os = "freebsd")]
152     fn mkdir(_p: path, _mode: ctypes::c_int) -> bool {
153         ret str::as_buf(_p, {|buf| os::libc::mkdir(buf, _mode) == 0i32 });
154     }
155 }
156
157 /*
158 Function: list_dir
159
160 Lists the contents of a directory.
161 */
162 fn list_dir(p: path) -> [str] {
163     let p = p;
164     let pl = str::byte_len(p);
165     if pl == 0u || p[pl - 1u] as char != os_fs::path_sep { p += path_sep(); }
166     let full_paths: [str] = [];
167     for filename: str in os_fs::list_dir(p) {
168         if !str::eq(filename, ".") {
169             if !str::eq(filename, "..") { full_paths += [p + filename]; }
170         }
171     }
172     ret full_paths;
173 }
174
175 /*
176 Function: remove_dir
177
178 Removes a directory at the specified path.
179 */
180 fn remove_dir(p: path) -> bool {
181    ret rmdir(p);
182
183     #[cfg(target_os = "win32")]
184     fn rmdir(_p: path) -> bool {
185         ret str::as_buf(_p, {|buf| os::kernel32::RemoveDirectoryA(buf)});
186     }
187
188     #[cfg(target_os = "linux")]
189     #[cfg(target_os = "macos")]
190     #[cfg(target_os = "freebsd")]
191     fn rmdir(_p: path) -> bool {
192         ret str::as_buf(_p, {|buf| os::libc::rmdir(buf) == 0i32 });
193     }
194 }
195
196 fn change_dir(p: path) -> bool {
197     ret chdir(p);
198
199     #[cfg(target_os = "win32")]
200     fn chdir(_p: path) -> bool {
201         ret str::as_buf(_p, {|buf| os::kernel32::SetCurrentDirectoryA(buf)});
202     }
203
204     #[cfg(target_os = "linux")]
205     #[cfg(target_os = "macos")]
206     #[cfg(target_os = "freebsd")]
207     fn chdir(_p: path) -> bool {
208         ret str::as_buf(_p, {|buf| os::libc::chdir(buf) == 0i32 });
209     }
210 }
211
212 /*
213 Function: path_is_absolute
214
215 Indicates whether a path is absolute.
216
217 A path is considered absolute if it begins at the filesystem root ("/") or,
218 on Windows, begins with a drive letter.
219 */
220 fn path_is_absolute(p: path) -> bool { ret os_fs::path_is_absolute(p); }
221
222 // FIXME: under Windows, we should prepend the current drive letter to paths
223 // that start with a slash.
224 /*
225 Function: make_absolute
226
227 Convert a relative path to an absolute path
228
229 If the given path is relative, return it prepended with the current working
230 directory. If the given path is already an absolute path, return it
231 as is.
232 */
233 fn make_absolute(p: path) -> path {
234     if path_is_absolute(p) { ret p; } else { ret connect(getcwd(), p); }
235 }
236
237 /*
238 Function: split
239
240 Split a path into it's individual components
241
242 Splits a given path by path separators and returns a vector containing
243 each piece of the path. On Windows, if the path is absolute then
244 the first element of the returned vector will be the drive letter
245 followed by a colon.
246 */
247 fn split(p: path) -> [path] {
248     let split1 = str::split(p, os_fs::path_sep as u8);
249     let split2 = [];
250     for s in split1 {
251         split2 += str::split(s, os_fs::alt_path_sep as u8);
252     }
253     ret split2;
254 }
255
256 /*
257 Function: splitext
258
259 Split a path into a pair of strings with the first element being the filename
260 without the extension and the second being either empty or the file extension
261 including the period. Leading periods in the basename are ignored.  If the
262 path includes directory components then they are included in the filename part
263 of the result pair.
264 */
265 fn splitext(p: path) -> (str, str) {
266     if str::is_empty(p) { ("", "") }
267     else {
268         let parts = str::split(p, '.' as u8);
269         if vec::len(parts) > 1u {
270             let base = str::connect(vec::init(parts), ".");
271             let ext = "." + option::get(vec::last(parts));
272
273             fn is_dotfile(base: str) -> bool {
274                 str::is_empty(base)
275                     || str::ends_with(
276                         base, str::from_char(os_fs::path_sep))
277                     || str::ends_with(
278                         base, str::from_char(os_fs::alt_path_sep))
279             }
280
281             fn ext_contains_sep(ext: str) -> bool {
282                 vec::len(split(ext)) > 1u
283             }
284
285             fn no_basename(ext: str) -> bool {
286                 str::ends_with(
287                     ext, str::from_char(os_fs::path_sep))
288                     || str::ends_with(
289                         ext, str::from_char(os_fs::alt_path_sep))
290             }
291
292             if is_dotfile(base)
293                 || ext_contains_sep(ext)
294                 || no_basename(ext) {
295                 (p, "")
296             } else {
297                 (base, ext)
298             }
299         } else {
300             (p, "")
301         }
302     }
303 }
304
305 /*
306 Function: normalize
307
308 Removes extra "." and ".." entries from paths.
309
310 Does not follow symbolic links.
311 */
312 fn normalize(p: path) -> path {
313     let s = split(p);
314     let s = strip_dots(s);
315     let s = rollup_doubledots(s);
316
317     let s = if check vec::is_not_empty(s) {
318         connect_many(s)
319     } else {
320         ""
321     };
322     let s = reabsolute(p, s);
323     let s = reterminate(p, s);
324
325     let s = if str::byte_len(s) == 0u {
326         "."
327     } else {
328         s
329     };
330
331     ret s;
332
333     fn strip_dots(s: [path]) -> [path] {
334         vec::filter_map(s, { |elem|
335             if elem == "." {
336                 option::none
337             } else {
338                 option::some(elem)
339             }
340         })
341     }
342
343     fn rollup_doubledots(s: [path]) -> [path] {
344         if vec::is_empty(s) {
345             ret [];
346         }
347
348         let t = [];
349         let i = vec::len(s);
350         let skip = 0;
351         do {
352             i -= 1u;
353             if s[i] == ".." {
354                 skip += 1;
355             } else {
356                 if skip == 0 {
357                     t += [s[i]];
358                 } else {
359                     skip -= 1;
360                 }
361             }
362         } while i != 0u;
363         let t = vec::reversed(t);
364         while skip > 0 {
365             t += [".."];
366             skip -= 1;
367         }
368         ret t;
369     }
370
371     #[cfg(target_os = "linux")]
372     #[cfg(target_os = "macos")]
373     #[cfg(target_os = "freebsd")]
374     fn reabsolute(orig: path, new: path) -> path {
375         if path_is_absolute(orig) {
376             path_sep() + new
377         } else {
378             new
379         }
380     }
381
382     #[cfg(target_os = "win32")]
383     fn reabsolute(orig: path, new: path) -> path {
384        if path_is_absolute(orig) && orig[0] == os_fs::path_sep as u8 {
385            str::from_char(os_fs::path_sep) + new
386        } else {
387            new
388        }
389     }
390
391     fn reterminate(orig: path, new: path) -> path {
392         let last = orig[str::byte_len(orig) - 1u];
393         if last == os_fs::path_sep as u8
394             || last == os_fs::path_sep as u8 {
395             ret new + path_sep();
396         } else {
397             ret new;
398         }
399     }
400 }
401
402 /*
403 Function: homedir
404
405 Returns the path to the user's home directory, if known.
406
407 On Unix, returns the value of the "HOME" environment variable if it is set and
408 not equal to the empty string.
409
410 On Windows, returns the value of the "HOME" environment variable if it is set
411 and not equal to the empty string. Otherwise, returns the value of the
412 "USERPROFILE" environment variable if it is set and not equal to the empty
413 string.
414
415 Otherwise, homedir returns option::none.
416 */
417 fn homedir() -> option<path> {
418     ret alt generic_os::getenv("HOME") {
419         some(p) {
420             if !str::is_empty(p) {
421                 some(p)
422             } else {
423                 secondary()
424             }
425         }
426         none. {
427             secondary()
428         }
429     };
430
431     #[cfg(target_os = "linux")]
432     #[cfg(target_os = "macos")]
433     #[cfg(target_os = "freebsd")]
434     fn secondary() -> option<path> {
435         none
436     }
437
438     #[cfg(target_os = "win32")]
439     fn secondary() -> option<path> {
440         option::maybe(none, generic_os::getenv("USERPROFILE")) {|p|
441             if !str::is_empty(p) {
442                 some(p)
443             } else {
444                 none
445             }
446         }
447     }
448 }
449
450 // Local Variables:
451 // mode: rust;
452 // fill-column: 78;
453 // indent-tabs-mode: nil
454 // c-basic-offset: 4
455 // buffer-file-coding-system: utf-8-unix
456 // End: