Skip to main content

pygmalion_utils/
cli.rs

1use std::path::Path;
2
3use super::file_io::read_from_file;
4use clap::Parser;
5use tokio::io;
6
7/// Pygmalion, yet another solidity lexer and stuff!
8#[derive(Parser, Debug)]
9#[command(version, about, long_about = None)]
10pub struct Cli {
11    /// Path of Solidity file
12    #[arg(short, long)]
13    path: Option<String>,
14
15    /// Short input to analyze  
16    #[arg(short, long)]
17    code: Option<String>,
18
19    /// Lexical analysis output destination
20    #[arg(short, long)]
21    out: Option<String>,
22}
23
24pub async fn get_app_args(cli: Cli) -> io::Result<(String, String)> {
25    let input: String;
26
27    if let Some(code) = cli.code {
28        input = code;
29    } else if let Some(path) = cli.path {
30        let file_path = Path::new(&path).canonicalize()?;
31        input = read_from_file(&file_path).await?;
32    } else {
33        return Err(io::Error::new(
34            io::ErrorKind::NotFound,
35            "No input or destination provided",
36        ));
37    }
38
39    return Ok((input, cli.out.unwrap_or_else(|| "".to_string())));
40}
41
42#[cfg(test)]
43mod tests {
44    use std::ffi::OsString;
45
46    use tokio::fs;
47
48    use super::*;
49
50    const PSEUDO_PATH: &str = "./temp/Test.sol";
51    const PSEUDO_CODE: &str = "contract Test {\
52            bool test = true;
53        }";
54    const PSEUDO_OUT: &str = "./temp/test_out.sol";
55
56    async fn create_temp_file(dir_name: &str, file_name: &str) -> io::Result<()> {
57        let file_path = Path::new(dir_name).join(file_name);
58
59        if fs::try_exists(dir_name).await? || fs::try_exists(&file_path).await? {
60            return Ok(());
61        }
62
63        fs::create_dir(dir_name).await?;
64
65        {
66            fs::File::create(&file_path).await?;
67            fs::write(file_path, PSEUDO_CODE).await?;
68        }
69
70        Ok(())
71    }
72
73    async fn remove_temp_file(dir_name: &str, file_name: &str) -> io::Result<()> {
74        let file_path = Path::new(dir_name).join(file_name);
75
76        fs::remove_file(file_path).await?;
77        fs::remove_dir(dir_name).await?;
78
79        Ok(())
80    }
81
82    pub fn create_mock_cli<I, T>(itr: I) -> Result<Cli, Box<dyn std::error::Error>>
83    where
84        I: IntoIterator<Item = T>,
85        T: Into<OsString> + Clone,
86    {
87        return Ok(Cli::try_parse_from(itr)?);
88    }
89
90    #[test]
91    fn should_get_args() -> Result<(), Box<dyn std::error::Error>> {
92        let cli = create_mock_cli(&["pygmalion", "--path", PSEUDO_PATH])?;
93        assert_eq!(cli.path, Some(PSEUDO_PATH.to_string()));
94
95        Ok(())
96    }
97
98    #[tokio::test]
99    async fn should_get_app_args_code_only() -> Result<(), Box<dyn std::error::Error>> {
100        let cli = create_mock_cli(&["pygmalion", "--code", PSEUDO_CODE, "--out", PSEUDO_OUT])?;
101        let (input, out) = get_app_args(cli).await?;
102
103        assert_eq!(input, PSEUDO_CODE);
104        assert_eq!(out, PSEUDO_OUT);
105
106        Ok(())
107    }
108
109    #[tokio::test]
110    async fn should_get_app_args_path_only() -> Result<(), Box<dyn std::error::Error>> {
111        let dir_name = "temp";
112        let file_name = "Test.sol";
113        create_temp_file(dir_name, file_name).await?;
114        let cli = create_mock_cli(&["pygmalion", "--path", PSEUDO_PATH])?;
115        let (input, _out) = get_app_args(cli).await?;
116
117        assert_eq!(input, PSEUDO_CODE);
118
119        remove_temp_file(dir_name, file_name).await?;
120
121        Ok(())
122    }
123
124    #[tokio::test]
125    async fn should_panic_for_code_and_path() -> Result<(), Box<dyn std::error::Error>> {
126        let cli = create_mock_cli(&["pygmalion"])?;
127        let e = get_app_args(cli).await.unwrap_err();
128
129        assert_eq!(e.kind(), io::ErrorKind::NotFound);
130
131        Ok(())
132    }
133}