sas_run/
lib.rs

1use std::path::{Path, PathBuf};
2use std::{os::windows::process::CommandExt, process::Command, fmt::Debug};
3use thiserror::Error;
4use winreg::enums::*;
5use winreg::RegKey;
6
7
8#[derive(Error)]
9pub enum SasError {
10    #[error(transparent)]
11    IoError(#[from] std::io::Error),
12}
13
14impl Debug for SasError {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "{}", self)
17    }
18}
19
20pub enum Encoding {
21    UTF8,
22    EucCn,
23}
24
25pub struct Sas{
26    cmd: Command,
27    config_path: PathBuf,
28    pub sas_path: PathBuf,
29    pub log_path: Option<PathBuf>,
30}
31
32impl Sas {
33    pub fn new<T: AsRef<Path>>(encoding: Encoding, sas_path: T, log_path: Option<T>) -> Self{
34        let config_path = {
35            match encoding {
36                Encoding::UTF8 => Path::join(Path::new(&Sas::get_sas_path()), r"nls\u8\sasv9.cfg"),
37                Encoding::EucCn => Path::join(Path::new(&Sas::get_sas_path()), r"nls\zh\sasv9.cfg"),
38            }
39        };
40        let log_path = {
41            match log_path {
42                Some(path) => Some(path.as_ref().to_path_buf()),
43                None => {
44                    None
45                }
46                
47            }
48        };
49        Self { cmd: Command::new("cmd.exe"), config_path: config_path, sas_path: sas_path.as_ref().to_path_buf(), log_path }
50    }
51    pub fn run(&mut self) -> Result<(), SasError> {
52        let config_path = self.config_path.to_str().unwrap();
53        let sas_path = self.sas_path.to_str().unwrap();
54
55        if !std::fs::exists(config_path)? {
56            return Err(SasError::IoError(std::io::Error::new(std::io::ErrorKind::NotFound, format!("SAS config file not found: {}", config_path))));
57        }
58
59        if !std::fs::exists(sas_path)? {
60            return Err(SasError::IoError(std::io::Error::new(std::io::ErrorKind::NotFound, format!("SAS file not found: {}", sas_path))));
61        }
62
63        let log_args = {
64            if self.log_path.is_none(){
65                format!("-NOLOG")
66            }else{
67                let path = self.log_path.clone().unwrap();
68                if !std::fs::exists(&path)? {
69                    std::fs::create_dir(&path)?;
70                }
71                format!("-LOG {}", path.to_str().unwrap())
72            }
73        };
74        
75        self.cmd
76            .raw_arg("/c")
77            .raw_arg("start")
78            .raw_arg("/w")
79            .arg("sas batch")
80            .raw_arg("sas")
81            .raw_arg("-CONFIG")
82            .arg(config_path)
83            .raw_arg("-SYSIN")
84            .arg(sas_path)
85            .raw_arg(log_args)
86            .raw_arg("-nosplash")
87            .raw_arg("-nologo")
88            .raw_arg("-icon");
89        self.cmd.output()?;
90        Ok(())
91    }
92    pub fn get_sas_path() -> String {
93        let hklm = RegKey::predef(HKEY_CLASSES_ROOT);
94        let cur_sas = hklm.open_subkey("SAS.Application\\shell\\Open\\Command").unwrap();
95        let sas_path: String = cur_sas.get_value("").unwrap();
96        let sas_cfg = sas_path.rsplit('"').collect::<Vec<_>>()[1].to_string();
97        sas_cfg.split('\\').filter(|x| !x.to_lowercase().contains(".cfg")).collect::<Vec<_>>().join("\\")
98    }
99}
100
101
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    #[test]
107    fn test_u8() -> Result<(), SasError> {
108        let mut sas_dm = Sas::new(Encoding::UTF8, r"sample.sas", Some("."));
109        sas_dm.run()?;
110        Ok(())
111    }
112    #[test]
113    fn test_zh() -> Result<(), SasError> {
114        let mut sas_dm = Sas::new(Encoding::EucCn, r"sample.sas", Some("."));
115        sas_dm.run()?;
116        Ok(())
117    }
118}