hyper-scripter 0.3.3

The script managing tool for script lovers
Documentation
use crate::error::{Contextable, Error, Result};
use crate::script::{ScriptName, ANONYMOUS};
use crate::script_type::ScriptType;
use crate::util::handle_fs_res;
use std::fs::{canonicalize, create_dir, read_dir};
use std::path::{Path, PathBuf};
use std::sync::Mutex;

pub const HS_EXECUTABLE_INFO_PATH: &'static str = ".hs_exe_path";

lazy_static::lazy_static! {
    static ref PATH: Mutex<Option<PathBuf>> = Mutex::new(None);
}

#[cfg(not(debug_assertions))]
pub fn get_sys_path() -> Result<PathBuf> {
    use crate::error::SysPath;
    const ROOT_PATH: &'static str = "hyper_scripter";
    const HS_HOME_ENV: &'static str = "HYPER_SCRIPTER_HOME";

    let p = match std::env::var(HS_HOME_ENV) {
        Ok(p) => {
            log::debug!("使用環境變數路徑:{}", p);
            p.into()
        }
        Err(std::env::VarError::NotPresent) => dirs::config_dir()
            .ok_or(Error::SysPathNotFound(SysPath::Config))?
            .join(ROOT_PATH),
        Err(e) => return Err(e.into()),
    };
    Ok(p)
}
#[cfg(all(debug_assertions, not(test)))]
pub fn get_sys_path() -> Result<PathBuf> {
    Ok(".hyper_scripter".into())
}
#[cfg(all(debug_assertions, test))]
pub fn get_sys_path() -> Result<PathBuf> {
    Ok(".test_hyper_scripter".into())
}

fn join_path<B: AsRef<Path>, P: AsRef<Path>>(base: B, path: P) -> Result<PathBuf> {
    let here = canonicalize(base)?;
    Ok(here.join(path))
}

pub fn set_path_from_sys() -> Result<()> {
    set_home(get_sys_path()?)
}
pub fn set_home<T: AsRef<Path>>(p: T) -> Result {
    let path = join_path(".", p)?;
    log::debug!("使用路徑:{:?}", path);
    if !path.exists() {
        log::info!("路徑 {:?} 不存在,嘗試創建之", path);
        handle_fs_res(&[&path], create_dir(&path))?;
    }
    *PATH.lock().unwrap() = Some(path);
    Ok(())
}
pub fn get_home() -> PathBuf {
    PATH.lock()
        .unwrap()
        .clone()
        .expect("還沒設定路徑就取路徑,錯誤實作!")
}

fn get_anonymous_ids() -> Result<Vec<u32>> {
    let mut ids = vec![];
    let dir = get_home().join(ANONYMOUS);
    if !dir.exists() {
        log::info!("找不到匿名腳本資料夾,創建之");
        handle_fs_res(&[&dir], create_dir(&dir))?;
    }
    for entry in handle_fs_res(&[&dir], read_dir(&dir))? {
        let name = entry?
            .file_name()
            .to_str()
            .ok_or(Error::msg("檔案實體為空...?"))?
            .to_string();
        let re = regex::Regex::new(r"\.\w+$").unwrap();
        let name = re.replace(&name, "");
        match name.parse::<u32>() {
            Ok(id) => ids.push(id),
            _ => log::warn!("匿名腳本名無法轉為整數:{}", name),
        }
    }

    Ok(ids)
}
pub fn open_new_anonymous(ty: &ScriptType) -> Result<(ScriptName, PathBuf)> {
    let ids = get_anonymous_ids().context("無法取得匿名腳本編號")?;
    let id = ids.into_iter().max().unwrap_or_default() + 1;
    Ok((ScriptName::Anonymous(id), open_anonymous(id, ty)?))
}
pub fn open_anonymous(id: u32, ty: &ScriptType) -> Result<PathBuf> {
    let name = ScriptName::Anonymous(id);
    let path = get_home().join(name.to_file_path(ty)?);
    Ok(path)
}

pub fn open_script(
    name: &ScriptName,
    ty: &ScriptType,
    check_sxist: Option<bool>,
) -> Result<PathBuf> {
    let script_path = match &name {
        ScriptName::Anonymous(id) => open_anonymous(*id, ty)?,
        ScriptName::Named(_) => {
            let path = get_home().join(name.to_file_path(ty)?);
            path
        }
    };
    if let Some(should_exist) = check_sxist {
        if !script_path.exists() && should_exist {
            return Err(
                Error::PathNotFound(vec![script_path]).context("開腳本失敗:應存在卻不存在")
            );
        } else if script_path.exists() && !should_exist {
            return Err(Error::ScriptExist(name.key().as_ref().to_owned())
                .context("開腳本失敗:不應存在卻存在"));
        }
    }
    Ok(script_path)
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::script::IntoScriptName;
    fn setup() {
        set_home(".test_hyper_scripter").unwrap();
    }
    #[test]
    fn test_anonymous_ids() {
        setup();
        let mut ids = get_anonymous_ids().unwrap();
        ids.sort();
        assert_eq!(ids, vec![1, 2, 5]);
    }
    #[test]
    fn test_open_anonymous() {
        setup();
        let (name, p) = open_new_anonymous(&"sh".into()).unwrap();
        assert_eq!(name, ScriptName::Anonymous(6));
        assert_eq!(
            p,
            join_path("./.test_hyper_scripter/.anonymous", "6.sh").unwrap()
        );
        let p = open_anonymous(5, &"js".into()).unwrap();
        assert_eq!(
            p,
            join_path("./.test_hyper_scripter/.anonymous", "5.js").unwrap()
        );
    }
    #[test]
    fn test_open() {
        setup();
        let second = "second".to_owned();
        let second_name = second.into_script_name().unwrap();
        let p = open_script(&second_name, &"rb".into(), Some(false)).unwrap();
        assert_eq!(p, get_home().join("second.rb"));

        let p = open_script(&".1".into_script_name().unwrap(), &"sh".into(), None).unwrap();
        assert_eq!(
            p,
            join_path("./.test_hyper_scripter/.anonymous", "1.sh").unwrap()
        );

        match open_script(
            &"not-exist".into_script_name().unwrap(),
            &"sh".into(),
            Some(true),
        )
        .unwrap_err()
        {
            Error::PathNotFound(name) => assert_eq!(name[0], get_home().join("not-exist.sh")),
            _ => unreachable!(),
        }
    }
}