txl_rs/
lib.rs

1extern crate reqwest;
2use std::{
3    env,
4    io::{Error, ErrorKind},
5    path::Path,
6    process::{Command, Stdio},
7};
8
9use flate2::read::GzDecoder;
10use regex::Regex;
11use tar::Archive;
12
13#[cfg(target_os = "macos")]
14const FOLDER: &str = "txl10.8b.macosx64";
15#[cfg(target_os = "macos")]
16const URL: &str = "http://txl.ca/download/11206-txl10.8b.macosx64.tar.gz";
17#[cfg(target_os = "macos")]
18const EXE: &str = "";
19#[cfg(target_os = "linux")]
20const FOLDER: &str = "txl10.8b.linux64";
21#[cfg(target_os = "linux")]
22const URL: &str = "http://www.txl.ca/download/13483-txl10.8b.linux64.tar.gz";
23#[cfg(target_os = "linux")]
24const EXE: &str = "";
25#[cfg(target_os = "windows")]
26const FOLDER: &str = "Txl108bwin64";
27#[cfg(target_os = "windows")]
28const URL: &str = "http://txl.ca/download/11888-Txl108bwin64.zip";
29#[cfg(target_os = "windows")]
30const EXE: &str = ".exe";
31
32#[cfg(all())]
33fn get_file_arg_ext(args: Vec<String>) -> (String, String) {
34    let file_args: Vec<&String> = args
35        .iter()
36        .filter(|x| std::path::PathBuf::from(x).exists())
37        .collect();
38    let mut ext = "";
39    if !file_args.is_empty() {
40        ext = file_args[0];
41    }
42    if let Some(ex) = Path::new(ext).extension() {
43        let ex_str = ex.to_string_lossy();
44        let grammar = lang_to_grammar(&ex_str);
45        (ex_str.to_string(), grammar.to_string())
46    } else {
47        ("".to_string(), "".to_string())
48    }
49}
50
51/// .
52///
53/// # Errors
54///
55/// This function will return an error if the txl command is not found.
56pub fn txl(args: Vec<String>) -> Result<String, Error> {
57    use std::path::PathBuf;
58    let mut my_args = args.clone();
59    let cmd = format!("{FOLDER}/bin/txl{EXE}");
60    if Path::new(&cmd).exists() {
61        let (ext, grammar) = get_file_arg_ext(args.clone());
62        if !grammar.is_empty() {
63            let grammar_file = PathBuf::from(format!("{}/lib/{}/{}.txl", FOLDER, grammar, ext));
64            if grammar_file.exists() {
65                my_args.push("-i".to_string());
66                my_args.push(format!("{}/lib/{}/", FOLDER, grammar));
67            } else {
68                let grammar_file =
69                    PathBuf::from(format!("{}/lib/{}/Txl/{}.txl", FOLDER, grammar, ext));
70                if grammar_file.exists() {
71                    my_args.push("-i".to_string());
72                    my_args.push(format!("{}/lib/{}/Txl/", FOLDER, grammar));
73                }
74            }
75        }
76    }
77    if let Ok(command) = Command::new(cmd)
78        .args(&my_args)
79        .stdout(Stdio::piped())
80        .stderr(Stdio::piped())
81        .spawn()
82    {
83        if let Ok(output) = command.wait_with_output() {
84            match String::from_utf8(output.stdout).ok() {
85                Some(s) => {
86                    if let Ok(s0) = String::from_utf8(output.stderr) {
87                        if let Ok(re) = Regex::new(".*: TXL0944E.* file '(.*).txl'") {
88                            if re.is_match(&s0) {
89                                let mut found = true;
90                                re.captures_iter(&s0).for_each(|cap| {
91                                    if let Err(e) = download(&cap[1]) {
92                                        println!("{e}");
93                                        found = false;
94                                    }
95                                });
96                                if !found {
97                                    Err(Error::new(ErrorKind::Other, s0))
98                                } else {
99                                    txl(my_args)
100                                }
101                            } else if s0.is_empty() {
102                                Ok(s)
103                            } else {
104                                if let Ok(re) = Regex::new(".*: TXL(.*)E.*") {
105                                    if re.is_match(&s0) {
106                                        Err(Error::new(ErrorKind::Other, s0))
107                                    } else {
108                                        Ok(s)
109                                    }
110                                } else {
111                                    Ok(s)
112                                }
113                            }
114                        } else {
115                            Ok(s)
116                        }
117                    } else {
118                        Ok(s)
119                    }
120                }
121                None => Err(Error::new(ErrorKind::Other, "output is not UTF8")),
122            }
123        } else {
124            println!("Cannot run txl {:?}", args);
125            Err(Error::new(
126                ErrorKind::Other,
127                format!("Cannot run `txl {:?}`", args),
128            ))
129        }
130    } else {
131        println!("txl not found, downlading...");
132        if let Ok(resp) = reqwest::blocking::get(URL) {
133            if let Ok(bytes) = resp.bytes() {
134                if URL.ends_with(".tar.gz") {
135                    let tar = GzDecoder::new(&bytes[..]);
136                    let mut archive = Archive::new(tar);
137                    archive.unpack(".")?;
138                } else {
139                    let reader = std::io::Cursor::new(bytes);
140                    let mut zip = zip::ZipArchive::new(reader)?;
141                    zip.extract(".").ok();
142                }
143                if let Ok(path) = env::var("PATH") {
144                    env::set_var(
145                        "PATH",
146                        format!("{:?}/{}/bin:{path}", env::current_dir(), FOLDER),
147                    );
148                }
149                download("rs")?;
150                txl(my_args)
151            } else {
152                Err(Error::new(ErrorKind::Other, "Bytes conversion error"))
153            }
154        } else {
155            Err(Error::new(ErrorKind::Other, "Command `txl` not found"))
156        }
157    }
158}
159
160fn lang_to_grammar(lang: &str) -> &str {
161    let mut grammar = lang;
162    match lang {
163        "atl" => {
164            grammar = "ATL";
165        }
166        "ada" => {
167            grammar = "Ada";
168        }
169        "c" => {
170            grammar = "C18";
171        }
172        "cpp" => {
173            grammar = "Cpp";
174        }
175        "cs" => {
176            grammar = "CSharp";
177        }
178        "delphi" => {
179            grammar = "Delphi2006";
180        }
181        "e" => {
182            grammar = "Eiffel";
183        }
184        "f77" => {
185            grammar = "Fortran";
186        }
187        "html" => {
188            grammar = "HTML";
189        }
190        "java" => {
191            grammar = "Java8";
192        }
193        "js" => {
194            grammar = "JavaScript";
195        }
196        "mod" => {
197            grammar = "Modula3";
198        }
199        "php" => {
200            grammar = "PHP";
201        }
202        "py" => {
203            grammar = "Python";
204        }
205        "rb" => {
206            grammar = "Ruby";
207        }
208        "grm" | "txl" => {
209            grammar = "TXL";
210        }
211        "rs" => {
212            grammar = "Rust";
213        }
214        "swift" | "SWIFT" => {
215            grammar = "Swift";
216        }
217        "vb" => {
218            grammar = "VisualBasic";
219        }
220        "xml" => {
221            grammar = "XML";
222        }
223        "y" => {
224            grammar = "Yacc";
225        }
226        _ => {
227            grammar = "unsupported";
228        }
229    };
230    grammar
231}
232
233fn download(lang: &str) -> Result<String, Error> {
234    let grammar = lang_to_grammar(lang);
235    let mut grammar_name;
236    match lang {
237        "ada" => {
238            grammar_name = "Ada_grammar";
239        }
240        "php" => {
241            grammar_name = "PHP345";
242        }
243        "y" => {
244            grammar_name = "YAXX";
245        }
246        _ => {
247            if lang_to_grammar(lang) == "unsupported" {
248                return Err(Error::new(
249                    ErrorKind::Other,
250                    format!("{lang} is not supported"),
251                ))
252            } else {
253                grammar_name = grammar;
254            }
255        }
256    }
257    if grammar_name.is_empty() {
258        grammar_name = grammar;
259    }
260    if let Ok(resp) = reqwest::blocking::get(format!(
261        "http://www.txl.ca/examples/Grammars/{grammar}/{grammar_name}.tar.gz"
262    )) {
263        if let Ok(bytes) = resp.bytes() {
264            let tar = GzDecoder::new(&bytes[..]);
265            let mut archive = Archive::new(tar);
266            archive.unpack(format!("{}/lib", FOLDER))?;
267        }
268    }
269    Ok("success".to_string())
270}