ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::fs
//!
//! Built-in functions about filesystem operations.
//!
//! - Cd
//! - Pwd
//! - Mkdir
//! - Rm

use crate::{Dict, Environment, FALSE_SYMBOL, TRUE_SYMBOL, eval::apply::eval_apply, expand_tilde, value::Value};

pub(crate) fn cd(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if args.len() != 1 {
        return Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Cd]: Expected 1 parameter, but {} were passed.",
            args.len()
        )));
    }
    match eval_apply(&args[0], env)? {
        Value::String(path) => {
            let expaned = expand_tilde(path)?;
            std::env::set_current_dir(&expaned)
                .map_err(|e| {
                    std::sync::Arc::from(format!(
                        "Error[ksl::builtin::Cd]: Expected a valid path, but got: `{}` with `{e}`.",
                        expaned.display()
                    ))
                })
                .and(Ok(Value::Unit))
        }
        other => Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Cd]: Expected a string as path, but got: `{other}`.",
        ))),
    }
}

pub(crate) fn pwd(args: &[Value], _env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if !args.is_empty() {
        return Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Pwd]: Expected 0 parameters, but {} were passed.",
            args.len()
        )));
    }

    std::env::current_dir()
        .map_err(|e| {
            std::sync::Arc::from(format!(
                "Error[ksl::builtin::Pwd]: Get current path failed with: `{e}`.",
            ))
        })
        .map(|path| Value::String(std::sync::Arc::from(path.display().to_string())))
}

pub(crate) fn mkdir(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [path] = args {
        match eval_apply(path, env)? {
            Value::String(p) => {
                let expaned = expand_tilde(p)?;
                std::fs::create_dir(expaned)
                    .map_err(|e| {
                        std::sync::Arc::from(format!(
                            "Error[ksl::builtin::Mkdir]: Failed to create directory with: `{e}`"
                        ))
                    })
                    .and(Ok(Value::Unit))
            }
            e => Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Mkdir]: Expected a string as path, but got: `{e}`."
            ))),
        }
    } else if let [path, is_recursive] = args {
        match (
            eval_apply(path, env.clone())?,
            eval_apply(is_recursive, env)?,
        ) {
            (Value::String(p), r @ Value::Atom(_)) if r == *TRUE_SYMBOL => {
                let expaned = expand_tilde(p)?;
                std::fs::create_dir_all(expaned)
                    .map_err(|e| {
                        std::sync::Arc::from(format!(
                            "Error[ksl::builtin::Mkdir]: Failed to create directory with: `{e}`"
                        ))
                    })
                    .and(Ok(Value::Unit))
            }
            (Value::String(p), r @ Value::Atom(_)) if r == *FALSE_SYMBOL => {
                let expaned = expand_tilde(p)?;
                std::fs::create_dir(expaned)
                    .map_err(|e| {
                        std::sync::Arc::from(format!(
                            "Error[ksl::builtin::Mkdir]: Failed to create directory with: `{e}`"
                        ))
                    })
                    .and(Ok(Value::Unit))
            }
            (Value::String(_), e) => Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Mkdir]: Expected a boolean, but got: `{e}`."
            ))),
            (e, _) => Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Mkdir]: Expected a string as path, but got: `{e}`."
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Mkdir]: Expected 1 or 2 parameters, but {} were passed.",
            args.len()
        )))
    }
}

pub(crate) fn rm(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [file_path] = args {
        match eval_apply(file_path, env)? {
            Value::String(path) => {
                let expanded = expand_tilde(path)?;
                match std::fs::metadata(&expanded)
                    .map_err(|e| {
                        std::sync::Arc::from(format!(
                            "Error[ksl::builtin::Rm]: Failed to get the metadata of `{}` with: `{e}`",
                            expanded.display()
                        ))
                    })?
                    .is_dir()
                {
                    true => std::fs::remove_dir_all(expanded),
                    false => std::fs::remove_file(expanded),
                }
                .map_err(|e| {
                    std::sync::Arc::from(format!(
                        "Error[ksl::builtin::Rm]: Failed to remove file with: `{e}`"
                    ))
                })
                .and(Ok(Value::Unit))
            }
            e => Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Rm]: Expected a string as path, but got: `{e}`."
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Rm]: Expected 1 parameter, but {} were passed.",
            args.len()
        )))
    }
}

pub(crate) fn glob(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [file_pattern] = args {
        match eval_apply(file_pattern, env)? {
            Value::String(pat) => Ok(Value::List(
                nu_glob::glob(
                    expand_tilde(pat)?.display().to_string().as_str(),
                    nu_glob::Uninterruptible,
                )
                .map_err(|e| {
                    std::sync::Arc::from(format!(
                        "Error[ksl::builtin::Glob]: Failed to glob files with: `{e}`",
                    ))
                })?
                .filter_map(|e| {
                    e.ok()
                        .map(|p| Value::String(std::sync::Arc::from(p.display().to_string())))
                })
                .collect::<std::sync::Arc<[Value]>>(),
            )),
            e => Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::Glob]: Expected a string as pattern, but got: `{e}`."
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::Glob]: Expected 1 parameter, but {} were passed.",
            args.len()
        )))
    }
}

pub(crate) fn file_meta(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    static TYPE_TAGS: std::sync::LazyLock<[std::sync::Arc<str>; 12]> = std::sync::LazyLock::new(|| {
        [
            std::sync::Arc::from("MetaData"),
            std::sync::Arc::from("kind"),
            std::sync::Arc::from("size"),
            std::sync::Arc::from("modified"),
            std::sync::Arc::from("created"),
            std::sync::Arc::from("readonly"),
            std::sync::Arc::from("extension"),
            std::sync::Arc::from("name"),
            std::sync::Arc::from("file"),
            std::sync::Arc::from("directory"),
            std::sync::Arc::from("symlink"),
            std::sync::Arc::from("unknown"),
        ]
    });
    if let [file_path] = args {
        match eval_apply(file_path, env)? {
            Value::String(path) => {
                let expanded = expand_tilde(path)?;
                let metadata = std::fs::metadata(&expanded).map_err(|e| {
                    std::sync::Arc::from(format!(
                        "Error[ksl::builtin::FileMeta]: Failed to get file metadata with: `{e}`",
                    ))
                })?;
                let mut data = Dict::with_capacity_and_hasher(9, rustc_hash::FxBuildHasher);
                data.insert(
                    TYPE_TAGS[1].clone(),
                    std::sync::Arc::new(Value::Atom(if metadata.is_symlink() {
                        TYPE_TAGS[10].clone()
                    } else if metadata.is_dir() {
                        TYPE_TAGS[9].clone()
                    } else if metadata.is_file() {
                        TYPE_TAGS[8].clone()
                    } else {
                        TYPE_TAGS[11].clone()
                    })),
                );
                data.insert(
                    TYPE_TAGS[2].clone(),
                    std::sync::Arc::new(Value::Number(metadata.len() as f64)),
                );
                data.insert(
                    TYPE_TAGS[3].clone(),
                    std::sync::Arc::new(match metadata.modified() {
                        Ok(mt) => match mt.duration_since(std::time::UNIX_EPOCH) {
                            Ok(t) => Ok(Value::Number(t.as_millis() as f64)),
                            Err(e) => Err(std::sync::Arc::from(format!(
                                "Error[ksl::builtin::FileMeta]: Failed to get the timestamp with: `{e}`"
                            ))),
                        },
                        Err(e) => Err(std::sync::Arc::from(format!(
                            concat!(
                                "Error[ksl::builtin::FileMeta]: ",
                                "Failed to get the last modification time with: {}"
                            ),
                            e
                        ))),
                    }?),
                );
                data.insert(
                    TYPE_TAGS[4].clone(),
                    std::sync::Arc::new(match metadata.created() {
                        Ok(mt) => match mt.duration_since(std::time::UNIX_EPOCH) {
                            Ok(t) => Ok(Value::Number(t.as_millis() as f64)),
                            Err(e) => Err(std::sync::Arc::from(format!(
                                "Error[ksl::builtin::FileMeta]: Failed to get the timestamp with: `{e}`"
                            ))),
                        },
                        Err(e) => Err(std::sync::Arc::from(format!(
                            "Error[ksl::builtin::FileMeta]: Failed to get the creation time with: `{e}`"
                        ))),
                    }?),
                );
                data.insert(
                    TYPE_TAGS[5].clone(),
                    std::sync::Arc::new(match metadata.permissions().readonly() {
                        true => TRUE_SYMBOL.clone(),
                        false => FALSE_SYMBOL.clone(),
                    }),
                );
                data.insert(
                    TYPE_TAGS[6].clone(),
                    std::sync::Arc::new(Value::String({
                        match expanded.extension() {
                            Some(ex) => std::sync::Arc::from(ex.to_string_lossy()),
                            None => std::sync::Arc::from(""),
                        }
                    })),
                );
                data.insert(
                    TYPE_TAGS[7].clone(),
                    std::sync::Arc::new(Value::String(match expanded.file_name() {
                        Some(name) => std::sync::Arc::from(name.to_string_lossy()),
                        None => std::sync::Arc::from(""),
                    })),
                );
                Ok(Value::Object(TYPE_TAGS[0].clone(), Box::new(data)))
            }
            e => Err(std::sync::Arc::from(format!(
                "Error[ksl::builtin::FileMeta]: Expected a string as file path, but got: `{e}`."
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            "Error[ksl::builtin::FileMeta]: Expected 1 parameter, but {} were passed.",
            args.len()
        )))
    }
}