cursython/
lib.rs

1mod backend;
2
3#[cfg(test)]
4mod tests;
5
6use backend::toplevel::Module;
7
8use std::{
9    error::Error,
10    fmt::Display,
11    fs::File,
12    io::{BufReader, Read},
13    path::{Path, PathBuf},
14};
15
16#[derive(Debug)]
17pub struct TranspileError {
18    cause: Box<dyn Error>,
19    desc: String,
20}
21
22impl TranspileError {
23    fn new(cause: Box<dyn Error>, desc: String) -> Self {
24        TranspileError { cause, desc }
25    }
26}
27
28impl Error for TranspileError {
29    fn cause(&self) -> Option<&dyn Error> {
30        Some(&*self.cause)
31    }
32
33    fn source(&self) -> Option<&(dyn Error + 'static)> {
34        None
35    }
36
37    fn description(&self) -> &str {
38        self.desc.as_str()
39    }
40}
41
42impl Display for TranspileError {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        f.write_fmt(format_args!("{}: {}", self.desc, self.cause))
45    }
46}
47
48pub fn transpile_file<P: AsRef<Path>>(
49    in_file: P,
50    out_file: Option<P>,
51) -> Result<(), Box<dyn Error>> {
52    let infile = if let Some(ext) = in_file.as_ref().extension() {
53        if ext != "json" && ext != "json5" {
54            println!(
55                "not transpiling {}: file extension doesn't end in .json/.json5, skipping.",
56                in_file.as_ref().display()
57            );
58            return Ok(());
59        }
60
61        in_file.as_ref()
62    } else {
63        println!(
64            "not transpiling {}: file doesnt have an extension, skipping.",
65            in_file.as_ref().display()
66        );
67        return Ok(());
68    };
69
70    let infile_file = File::open(&infile)?;
71    let mut infile_reader = BufReader::new(infile_file);
72
73    let mut buf: Vec<u8> = Vec::new();
74    infile_reader
75        .read_to_end(&mut buf)
76        .unwrap_or_else(|err| panic!("failed to read the file at {}: {}", infile.display(), err));
77    let data = std::str::from_utf8(&buf).expect("JSON file is not valid UTF-8!");
78
79    let file: Module = match json5::from_str(data) {
80        Ok(data) => data,
81        Err(err) => {
82            return Err(Box::new(TranspileError::new(
83                Box::new(err),
84                format!("While parsing the JSON file at {}", infile.display()),
85            )))
86        }
87    };
88
89    file.write_file(out_file);
90
91    Ok(())
92}
93
94pub fn batch_transpile<P: AsRef<Path>>(in_dir: P, out_dir: P) -> Result<(), Box<dyn Error>> {
95    fn transpile_dir(in_dir: &Path, outdir: &Path) -> Result<(), Box<dyn Error>> {
96        let needed_files_iter = in_dir
97            .iter()
98            .filter(|file_or_dir| !PathBuf::from(file_or_dir).is_dir())
99            .map(|file| PathBuf::from(file));
100
101        for f in needed_files_iter {
102            let target_path = outdir.join(f.file_name().unwrap());
103            transpile_file(&f, Some(&target_path))?
104        }
105
106        Ok(())
107    }
108
109    let needed_dirs_iter = in_dir
110        .as_ref()
111        .iter()
112        .filter(|file_or_dir| PathBuf::from(file_or_dir).is_dir())
113        .map(|dir| PathBuf::from(dir));
114
115    for d in needed_dirs_iter {
116        transpile_dir(&d, &out_dir.as_ref().join(&d.file_name().unwrap()))?
117    }
118
119    transpile_dir(Path::new("./"), out_dir.as_ref())?;
120
121    Ok(())
122}