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}