use hyper_scripter::{
config::{Config, PromptLevel},
path::normalize_path,
};
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
use std::sync::{Mutex, MutexGuard, Once};
pub const HOME_RELATIVE: &str = "./.hyper_scripter";
lazy_static::lazy_static! {
static ref LOCK: Mutex<()> = Mutex::new(());
static ref HOME: PathBuf = normalize_path(HOME_RELATIVE).unwrap();
}
fn get_exe() -> String {
#[cfg(not(debug_assertions))]
let mode = "release";
#[cfg(debug_assertions)]
let mode = "debug";
format!("{}/../target/{}/hs", env!("CARGO_MANIFEST_DIR"), mode)
}
#[derive(Debug, Default)]
pub struct RunEnv {
pub home: Option<PathBuf>,
pub dir: Option<PathBuf>,
pub silent: Option<bool>,
}
#[macro_export]
macro_rules! run {
($($key:ident: $val:expr,)* $lit:literal) => ({
run!($($key: $val,)* $lit,)
});
($($key:ident: $val:expr,)* $lit:literal, $($arg:tt)*) => ({
let env = RunEnv{
$($key: Some($val.into()),)*
..Default::default()
};
run_with_env(env, format!($lit, $($arg)*))
});
}
#[derive(Debug)]
enum ErrorInner {
Other,
ExitStatus(ExitStatus),
}
#[derive(Debug)]
pub struct Error {
msg: Vec<String>,
inner: ErrorInner,
}
impl Error {
pub fn exit_status(e: ExitStatus) -> Error {
Error {
msg: vec![],
inner: ErrorInner::ExitStatus(e),
}
}
pub fn other<T: ToString>(s: T) -> Error {
Error {
msg: vec![s.to_string()],
inner: ErrorInner::Other,
}
}
pub fn context<T: ToString>(mut self, s: T) -> Error {
self.msg.push(s.to_string());
self
}
}
type Result<T = ()> = std::result::Result<T, Error>;
pub fn get_home() -> PathBuf {
HOME.clone()
}
pub fn load_conf() -> Config {
Config::load(hyper_scripter::path::get_home()).unwrap()
}
pub fn setup<'a>() -> MutexGuard<'a, ()> {
let g = setup_with_utils();
run!("rm --purge * -f all").unwrap();
g
}
pub fn setup_with_utils<'a>() -> MutexGuard<'a, ()> {
let guard = LOCK.lock().unwrap_or_else(|err| err.into_inner());
let _ = env_logger::try_init();
let home: PathBuf = get_home();
match std::fs::remove_dir_all(&home) {
Ok(_) => (),
Err(e) => {
if e.kind() != std::io::ErrorKind::NotFound {
panic!("重整測試用資料夾 {:?} 失敗了……", home);
}
}
}
static ONCE: Once = Once::new();
ONCE.call_once(|| {
hyper_scripter::path::set_home(Some(&home), true).unwrap();
Config::init().unwrap();
Config::set_prompt_level(Some(PromptLevel::Never));
});
run!("alias e edit --fast").unwrap();
guard
}
fn join_path(p: &[&str]) -> PathBuf {
let mut file = get_home();
for p in p.iter() {
file = file.join(p);
}
file
}
pub fn read(p: &[&str]) -> String {
let file = join_path(p);
let s = std::fs::read(file).unwrap();
let s: &str = std::str::from_utf8(&s).unwrap();
s.to_owned()
}
pub fn check_exist(p: &[&str]) -> bool {
let file = join_path(p);
file.exists()
}
pub fn run_with_env<T: ToString>(env: RunEnv, args: T) -> Result<String> {
let home = match env.home {
Some(h) => {
log::info!("使用腳本之家 {:?}", h);
h
}
None => get_home(),
};
let home = home.to_string_lossy();
let mut full_args = vec!["-H", home.as_ref(), "--prompt-level", "never"];
let args = args.to_string();
let args_vec: Vec<&str> = if args.find('|').is_some() {
let (first, second) = args.split_once("|").unwrap();
let mut v: Vec<_> = first.split(' ').filter(|s| !s.is_empty()).collect();
let second = second.trim();
if second.len() > 0 {
v.push(second);
}
v
} else {
args.split_whitespace().collect()
};
full_args.extend(&args_vec);
log::info!("開始執行 {:?}", args_vec);
let mut cmd = Command::new(normalize_path(get_exe()).unwrap());
if let Some(dir) = env.dir {
log::info!("使用路徑 {}", dir.to_string_lossy());
cmd.current_dir(&dir);
cmd.env("PWD", dir);
}
let mut child = cmd
.args(&full_args)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
let stdout = child.stdout.as_mut().unwrap();
let mut out_str = vec![];
let reader = BufReader::new(stdout);
let silent = env.silent;
reader
.lines()
.filter_map(|line| line.ok())
.for_each(|line| {
if silent != Some(true) {
println!("{}", line);
}
out_str.push(line);
});
let status = child.wait().unwrap();
let res = if status.success() {
Ok(out_str.join("\n"))
} else {
Err(Error::exit_status(status).context(format!("執行 {:?} 失敗", args_vec)))
};
log::info!("執行 {:?} 完畢,結果為 {:?}", args_vec, res);
res
}
fn get_ls(filter: Option<&str>, query: Option<&str>) -> Vec<String> {
let ls_res = run!(
"ls {} --grouping none --plain --name {}",
filter.map(|f| format!("-f {}", f)).unwrap_or_default(),
query.unwrap_or_default()
)
.unwrap();
ls_res
.split_whitespace()
.filter_map(|s| {
if !s.is_empty() {
Some(s.to_owned())
} else {
None
}
})
.collect::<Vec<_>>()
}
pub fn assert_ls_len(expect: usize, filter: Option<&str>, query: Option<&str>) {
let res = get_ls(filter, query);
assert_eq!(expect, res.len(), "ls {:?} 結果為 {:?}", filter, res);
}
pub fn assert_ls(mut expect: Vec<&str>, filter: Option<&str>, query: Option<&str>) {
expect.sort_unstable();
let mut res = get_ls(filter, query);
res.sort();
assert_eq!(expect, res, "ls {:?} 結果為 {:?}", filter, res);
}
#[derive(Debug)]
pub struct ScriptTest {
name: String,
}
impl ScriptTest {
pub fn get_name(&self) -> &str {
&self.name
}
pub fn new(name: &str, tags: Option<&str>, content: Option<&str>) -> Self {
let tags_str = tags.map(|s| format!("-t {}", s)).unwrap_or_default();
let content = content.unwrap_or("echo $NAME");
run!("e {} ={} | {}", tags_str, name, content).unwrap();
ScriptTest {
name: name.to_owned(),
}
}
pub fn assert_not_exist(&self, args: Option<&str>, msg: Option<&str>) {
let s = format!("cat {} ={}", args.unwrap_or_default(), self.name);
let msg = msg.map(|s| format!("\n{}", s)).unwrap_or_default();
run!("{}", s).expect_err(&format!("{} 找到東西{}", s, msg));
}
pub fn archaeology<'a>(&'a self) -> ScriptTestWithFilter<'a> {
ScriptTestWithFilter {
script: self,
filter: "-A",
}
}
pub fn filter<'a>(&'a self, filter: &'a str) -> ScriptTestWithFilter<'a> {
ScriptTestWithFilter {
script: self,
filter,
}
}
pub fn run(&self, args: &str) -> Result<String> {
self.filter("").run(args)
}
pub fn can_find(&self, command: &str) -> Result {
self.filter("").can_find(command)
}
pub fn can_find_by_name(&self) -> Result {
self.filter("").can_find_by_name()
}
}
pub struct ScriptTestWithFilter<'a> {
script: &'a ScriptTest,
filter: &'a str,
}
impl<'a> ScriptTestWithFilter<'a> {
pub fn run(&self, args: &str) -> Result<String> {
run!("{} ={} {}", self.filter, self.script.name, args)
}
pub fn can_find(&self, command: &str) -> Result {
let res = run!(
"{} ls --plain --grouping=none --name {}",
self.filter,
command
)?;
if res == self.script.name {
Ok(())
} else {
Err(Error::other(format!(
"想找 {} 卻找到 {}",
self.script.name, res
)))
}
}
pub fn can_find_by_name(&self) -> Result {
self.can_find(&format!("={}", self.script.name))
}
}