rsw/
utils.rs

1use anyhow::Result;
2use regex::Regex;
3use std::{
4    env,
5    fs::{self, File},
6    io::{Read, Write},
7    path::{Path, PathBuf},
8    process::Command,
9};
10use toml::Value;
11use which::which;
12
13use crate::config;
14use crate::core::RswErr;
15
16pub fn check_env_cmd(program: &str) -> bool {
17    let result = which(program);
18    match result {
19        Err(e) => {
20            if is_program_in_path(program) {
21                true
22            } else {
23                eprint!("{}\n", e);
24                false
25            }
26        }
27        _ => true,
28    }
29}
30
31pub fn is_program_in_path(program: &str) -> bool {
32    if let Ok(path) = env::var("PATH") {
33        for p in path.split(":") {
34            let p_str = format!("{}/{}", p, program);
35            if fs::metadata(p_str).is_ok() {
36                return true;
37            }
38        }
39    }
40    false
41}
42
43// get fields from `Cargo.toml`
44pub fn get_crate_metadata(name: &str, root: PathBuf) -> Value {
45    let crate_root = root.join("Cargo.toml");
46    let content = fs::read_to_string(crate_root).unwrap_or_else(|e| {
47        // TODO: create crate
48        print(RswErr::Crate(name.into(), e));
49        std::process::exit(1);
50    });
51    let value = content.parse::<Value>().unwrap();
52    value
53}
54
55pub fn path_exists(path: &Path) -> bool {
56    fs::metadata(path).is_ok()
57}
58
59// https://docs.npmjs.com/creating-a-package-json-file#required-name-and-version-fields
60pub fn get_pkg(name: &str) -> (String, String) {
61    // example: @rsw/test | rsw-test | wasm123
62    let re = Regex::new(r"(?x)(@([\w\d_-]+)/)?((?P<pkg_name>[\w\d_-]+))").unwrap();
63    let caps = re.captures(name).unwrap();
64    let mut scope = "".into();
65    if caps.get(2) != None {
66        scope = caps.get(2).unwrap().as_str().into();
67    }
68
69    (caps["pkg_name"].to_owned(), scope)
70}
71
72// Checks if a file exists, if so, the destination buffer will be filled with
73// its contents.
74pub fn load_file_contents<P: AsRef<Path>>(filename: P, dest: &mut Vec<u8>) -> Result<()> {
75    let filename = filename.as_ref();
76
77    let mut buffer = Vec::new();
78    File::open(filename)?.read_to_end(&mut buffer)?;
79
80    // We needed the buffer so we'd only overwrite the existing content if we
81    // could successfully load the file into memory.
82    dest.clear();
83    dest.append(&mut buffer);
84
85    Ok(())
86}
87
88// @see: https://github.com/rust-lang/mdBook/blob/2213312938/src/utils/fs.rs#L61-L72
89// This function creates a file and returns it. But before creating the file
90// it checks every directory in the path to see if it exists,
91// and if it does not it will be created.
92pub fn create_file(path: &Path) -> Result<File> {
93    debug!("Creating {}", path.display());
94
95    // Construct path
96    if let Some(p) = path.parent() {
97        trace!("Parent directory is: {:?}", p);
98        fs::create_dir_all(p)?;
99    }
100
101    File::create(path).map_err(Into::into)
102}
103
104// @see: https://github.com/rust-lang/mdBook/blob/2213312938/src/utils/fs.rs#L16-L20
105// Write the given data to a file, creating it first if necessary
106pub fn write_file<P: AsRef<Path>>(build_dir: &Path, filename: P, content: &[u8]) -> Result<()> {
107    let path = build_dir.join(filename);
108    create_file(&path)?.write_all(content).map_err(Into::into)
109}
110
111// recursive copy folder
112pub fn copy_dirs<P: AsRef<Path>>(source: P, target: P) -> std::io::Result<()> {
113    fs::create_dir_all(&target)?;
114    for entry in fs::read_dir(source)? {
115        let entry = entry?;
116        let entry_target = target.as_ref().join(entry.file_name());
117        trace!(
118            "Copy {} to {}",
119            entry.path().display(),
120            entry_target.display()
121        );
122        if entry.file_type()?.is_dir() {
123            copy_dirs(entry.path(), entry_target)?;
124        } else {
125            fs::copy(entry.path(), entry_target)?;
126        }
127    }
128    Ok(())
129}
130
131// rsw log
132pub fn init_logger() {
133    use colored::Colorize;
134    use env_logger::Builder;
135    use log::Level;
136    use log::LevelFilter;
137
138    let mut builder = Builder::new();
139
140    builder.format(|formatter, record| {
141        let level = record.level().as_str();
142        let log_level = match record.level() {
143            Level::Info => level.blue(),
144            Level::Debug => level.magenta(),
145            Level::Trace => level.green(),
146            Level::Error => level.red(),
147            Level::Warn => level.yellow(),
148        };
149
150        let log_target = match record.level() {
151            Level::Info | Level::Error => format!(""),
152            _ => format!(" {}", record.target().yellow()),
153        };
154
155        let rsw_log = format!("[rsw::{}]", log_level);
156        writeln!(
157            formatter,
158            "{}{} {}",
159            rsw_log.bold().on_black(),
160            log_target,
161            record.args()
162        )
163    });
164
165    if let Ok(var) = env::var("RUST_LOG") {
166        builder.parse_filters(&var);
167    } else {
168        // if no RUST_LOG provided, default to logging at the Info level
169        builder.filter(None, LevelFilter::Info);
170    }
171
172    builder.init();
173}
174
175pub fn print<T: std::fmt::Display>(a: T) {
176    println!("{}", a)
177}
178
179pub fn get_root() -> PathBuf {
180    std::env::current_dir().unwrap()
181}
182
183pub fn dot_rsw_dir() -> PathBuf {
184    get_root().join(config::RSW_DIR)
185}
186
187pub fn init_rsw_crates(content: &[u8]) -> Result<()> {
188    let rsw_root = dot_rsw_dir();
189    if !path_exists(rsw_root.as_path()) {
190        std::fs::create_dir(&rsw_root)?;
191    }
192
193    let rsw_crates = rsw_root.join(config::RSW_CRATES);
194    if !path_exists(rsw_crates.as_path()) {
195        File::create(&rsw_crates)?;
196    }
197    fs::write(rsw_crates, content)?;
198
199    Ok(())
200}
201
202pub fn rsw_watch_file(info_content: &[u8], err_content: &[u8], file_type: String) -> Result<()> {
203    let rsw_root = dot_rsw_dir();
204    let rsw_info = rsw_root.join(config::RSW_INFO);
205    let rsw_err = rsw_root.join(config::RSW_ERR);
206    if !path_exists(rsw_info.as_path()) {
207        File::create(&rsw_info)?;
208    }
209    if !path_exists(rsw_err.as_path()) {
210        File::create(&rsw_err)?;
211    }
212    fs::write(&rsw_info, info_content)?;
213    if file_type == "info" {
214        fs::write(&rsw_err, "".as_bytes())?;
215    }
216    if file_type == "err" {
217        fs::write(rsw_err, err_content)?;
218    }
219
220    Ok(())
221}
222
223pub fn os_cli<P: AsRef<Path>>(cli: String, args: Vec<String>, path: P) {
224    if cfg!(target_os = "windows") {
225        Command::new("cmd")
226            .arg("/C")
227            .arg(cli)
228            .args(args)
229            .current_dir(path)
230            .status()
231            .unwrap();
232    } else {
233        Command::new(cli)
234            .args(args)
235            .current_dir(path)
236            .status()
237            .unwrap();
238    }
239}
240
241// https://www.reddit.com/r/learnrust/comments/h82em8/best_way_to_create_a_vecstring_from_str/
242pub fn vec_of_str(v: &[&str]) -> Vec<String> {
243    v.iter().map(|&x| x.into()).collect()
244}
245
246#[cfg(test)]
247mod pkg_name_tests {
248    use super::*;
249
250    #[test]
251    fn pkg_word() {
252        assert_eq!(get_pkg("@rsw/test".into()), ("test".into(), "rsw".into()));
253    }
254
255    #[test]
256    fn pkg_word_num() {
257        assert_eq!(get_pkg("wasm123".into()), ("wasm123".into(), "".into()));
258    }
259
260    #[test]
261    fn pkg_word_num_line() {
262        assert_eq!(
263            get_pkg("@rsw-org/my_wasm".into()),
264            ("my_wasm".into(), "rsw-org".into())
265        );
266    }
267}