1use std::path::{Path, PathBuf};
8
9use ion::flags::PropertyFlags;
10use ion::function::Rest;
11use ion::spec::create_property_spec_string;
12use ion::{Context, Error, Object, Result, function_spec, js_fn};
13use mozjs::jsapi::{JSFunctionSpec, JSPropertySpec};
14use runtime::module::NativeModule;
15
16#[cfg(windows)]
17const SEPARATOR: &str = "\\\0";
18#[cfg(unix)]
19const SEPARATOR: &str = "/\0";
20
21#[cfg(windows)]
22const DELIMITER: &str = ";\0";
23#[cfg(unix)]
24const DELIMITER: &str = ":\0";
25
26#[js_fn]
27fn join(Rest(segments): Rest<String>) -> String {
28 let mut path = PathBuf::new();
29 path.extend(segments.into_vec());
30 String::from(path.to_str().unwrap())
31}
32
33#[js_fn]
34fn strip_prefix(path: String, prefix: String) -> Result<String> {
35 let path = Path::new(&path);
36
37 if let Ok(path) = path.strip_prefix(&prefix) {
38 Ok(String::from(path.to_str().unwrap()))
39 } else {
40 Err(Error::new("Failed to strip prefix from path.", None))
41 }
42}
43
44#[js_fn]
45fn file_stem(path: String) -> Option<String> {
46 let path = Path::new(&path);
47 path.file_stem().map(|s| String::from(s.to_str().unwrap()))
48}
49
50#[js_fn]
51fn parent(path: String) -> Option<String> {
52 let path = Path::new(&path);
53 path.parent().map(|s| String::from(s.to_str().unwrap()))
54}
55
56#[js_fn]
57fn file_name(path: String) -> Option<String> {
58 let path = Path::new(&path);
59 path.file_name().map(|s| String::from(s.to_str().unwrap()))
60}
61
62#[js_fn]
63fn extension(path: String) -> Option<String> {
64 let path = Path::new(&path);
65 path.extension().map(|s| String::from(s.to_str().unwrap()))
66}
67
68#[js_fn]
69fn with_file_name(path: String, file_name: String) -> String {
70 let path = Path::new(&path);
71 String::from(path.with_file_name(file_name).to_str().unwrap())
72}
73
74#[js_fn]
75fn with_externsion(path: String, extension: String) -> String {
76 let path = Path::new(&path);
77 String::from(path.with_extension(extension).to_str().unwrap())
78}
79
80#[js_fn]
81fn is_absolute(path: String) -> bool {
82 Path::new(&path).is_absolute()
83}
84
85#[js_fn]
86fn is_relative(path: String) -> bool {
87 Path::new(&path).is_relative()
88}
89
90#[js_fn]
91fn has_root(path: String) -> bool {
92 Path::new(&path).has_root()
93}
94
95#[js_fn]
96fn starts_with(path: String, prefix: String) -> bool {
97 Path::new(&path).starts_with(prefix)
98}
99
100#[js_fn]
101fn ends_with(path: String, prefix: String) -> bool {
102 Path::new(&path).ends_with(prefix)
103}
104
105const FUNCTIONS: &[JSFunctionSpec] = &[
106 function_spec!(join, 0),
107 function_spec!(strip_prefix, c"stripPrefix", 2),
108 function_spec!(file_stem, c"fileStem", 1),
109 function_spec!(parent, 1),
110 function_spec!(file_name, c"fileName", 1),
111 function_spec!(extension, 1),
112 function_spec!(with_file_name, c"withFileName", 2),
113 function_spec!(with_externsion, c"withExtension", 2),
114 function_spec!(is_absolute, c"isAbsolute", 1),
115 function_spec!(is_relative, c"isRelative", 1),
116 function_spec!(has_root, c"hasRoot", 1),
117 function_spec!(starts_with, c"startsWith", 2),
118 function_spec!(ends_with, c"endsWith", 2),
119 JSFunctionSpec::ZERO,
120];
121
122const PROPERTIES: &[JSPropertySpec] = &[
123 create_property_spec_string("separator", SEPARATOR, PropertyFlags::CONSTANT_ENUMERATED),
124 create_property_spec_string("delimiter", DELIMITER, PropertyFlags::CONSTANT_ENUMERATED),
125 JSPropertySpec::ZERO,
126];
127
128pub struct PathM;
129
130impl<'cx> NativeModule<'cx> for PathM {
131 const NAME: &'static str = "path";
132 const VARIABLE_NAME: &'static str = "path";
133 const SOURCE: &'static str = include_str!("path.js");
134
135 fn module(&self, cx: &'cx Context) -> Option<Object<'cx>> {
136 let path = Object::new(cx);
137 if unsafe { path.define_methods(cx, FUNCTIONS) && path.define_properties(cx, PROPERTIES) } {
138 return Some(path);
139 }
140 None
141 }
142}