aurora-modules 0.1.0

Git, filesystem, system, network, archive, docker, unix, crypto, calculator, QR, color, timer, notes, clipboard, and text processing modules
Documentation
use aurora_core::{AuroraResult, Pipeline, Value};
use std::fs;
use std::io::{BufRead, BufReader};
use regex::Regex;

fn is_binary(data: &[u8]) -> bool {
    data[..data.len().min(1024)].contains(&0)
}

pub fn text_grep(pattern: &str, path: &str) -> AuroraResult<Pipeline> {
    let re = Regex::new(pattern)
        .map_err(|e| aurora_core::AuroraError::InvalidInput(
            format!("invalid regex: {e}")
        ))?;

    let path = std::path::Path::new(path);
    let mut rows: Vec<Vec<Value>> = Vec::new();

    if path.is_file() {
        let data = fs::read(path)
            .map_err(|e| aurora_core::AuroraError::Io(e))?;
        if is_binary(&data) {
            return Err(aurora_core::AuroraError::InvalidInput(
                format!("binary file: {}", path.display())
            ));
        }
        let file = fs::File::open(path)
            .map_err(|e| aurora_core::AuroraError::Io(e))?;
        let reader = BufReader::new(file);
        for (i, line) in reader.lines().enumerate() {
            let line = line.map_err(|e| aurora_core::AuroraError::Io(e))?;
            if re.find(&line).is_some() {
                rows.push(vec![
                    Value::Int((i + 1) as i64),
                    Value::String(line),
                ]);
            }
        }
    } else if path.is_dir() {
        for entry in walkdir::WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
            if !entry.file_type().is_file() {
                continue;
            }
            let data = match fs::read(entry.path()) {
                Ok(d) => d,
                Err(_) => continue,
            };
            if is_binary(&data) {
                continue;
            }
            let file_path = entry.path().to_string_lossy().to_string();
            let content = match String::from_utf8(data) {
                Ok(c) => c,
                Err(_) => continue,
            };
            for (i, line) in content.lines().enumerate() {
                if re.find(line).is_some() {
                    rows.push(vec![
                        Value::String(file_path.clone()),
                        Value::Int((i + 1) as i64),
                        Value::String(line.into()),
                    ]);
                }
            }
        }
    }

    let headers = if path.is_file() {
        vec!["line".into(), "content".into()]
    } else {
        vec!["file".into(), "line".into(), "content".into()]
    };

    Ok(Pipeline::table(headers, rows))
}

pub fn text_replace(pattern: &str, replacement: &str, path_str: &str) -> AuroraResult<Pipeline> {
    let re = Regex::new(pattern)
        .map_err(|e| aurora_core::AuroraError::InvalidInput(
            format!("invalid regex: {e}")
        ))?;

    let path = std::path::Path::new(path_str);
    let data = fs::read(path)
        .map_err(|e| aurora_core::AuroraError::Io(e))?;

    if is_binary(&data) {
        return Err(aurora_core::AuroraError::InvalidInput(
            format!("binary file: {path_str}")
        ));
    }

    let content = String::from_utf8(data)
        .map_err(|_| aurora_core::AuroraError::InvalidInput(
            format!("not valid UTF-8: {path_str}")
        ))?;

    let result = re.replace_all(&content, replacement).to_string();
    let mut count = 0;
    for _mat in re.find_iter(&content) {
        count += 1;
    }

    fs::write(path, &result)
        .map_err(|e| aurora_core::AuroraError::Io(e))?;

    Ok(Pipeline::table(
        vec!["action".into(), "file".into(), "replacements".into()],
        vec![vec![
            Value::String("replace".into()),
            Value::String(path_str.into()),
            Value::Int(count as i64),
        ]],
    ))
}

pub fn text_transform(path_str: &str, upper: bool, lower: bool) -> AuroraResult<Pipeline> {
    let path = std::path::Path::new(path_str);
    let data = fs::read(path)
        .map_err(|e| aurora_core::AuroraError::Io(e))?;

    if is_binary(&data) {
        return Err(aurora_core::AuroraError::InvalidInput(
            format!("binary file: {path_str}")
        ));
    }

    let content = String::from_utf8(data)
        .map_err(|_| aurora_core::AuroraError::InvalidInput(
            format!("not valid UTF-8: {path_str}")
        ))?;

    let result = if upper {
        content.to_uppercase()
    } else if lower {
        content.to_lowercase()
    } else {
        return Err(aurora_core::AuroraError::InvalidInput(
            "specify --upper or --lower".into()
        ));
    };

    fs::write(path, &result)
        .map_err(|e| aurora_core::AuroraError::Io(e))?;

    let action = if upper { "upper" } else { "lower" };
    Ok(Pipeline::table(
        vec!["action".into(), "file".into()],
        vec![vec![
            Value::String(action.into()),
            Value::String(path_str.into()),
        ]],
    ))
}