From 0f37d94625702c90fc957f35de399769d7b84693 Mon Sep 17 00:00:00 2001 From: LordMZTE Date: Tue, 12 Oct 2021 23:12:29 +0200 Subject: [PATCH] switch to lua and implement when check --- Cargo.toml | 9 ++++- exampleconfig.lua | 7 ++++ exampleconfig.toml | 4 -- src/config.rs | 68 ++++++++++++++++++++++++++------ src/lib.rs | 82 +++++++++++++++++++++++++++++++++++++++ src/luautil.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 65 +++++++++++++------------------ 7 files changed, 277 insertions(+), 55 deletions(-) create mode 100644 exampleconfig.lua delete mode 100644 exampleconfig.toml create mode 100644 src/lib.rs create mode 100644 src/luautil.rs diff --git a/Cargo.toml b/Cargo.toml index 2e8eb87..c1dc0e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,14 @@ edition = "2018" [dependencies] clap = { version = "3.0.0-beta.4", features = ["derive"] } -home = "0.5.3" +dirs = "4.0.0" miette = { version = "3.2.0", features = ["fancy"] } +mlua = { version = "0.6.6", features = ["luajit", "serialize"] } owo-colors = "2.1.0" serde = { version = "1.0.130", features = ["derive"] } +sled = "0.34.7" thiserror = "1.0.30" -toml = "0.5.8" +walkdir = "2.3.2" +xxhash-rust = { version = "0.8.2", features = ["xxh3"] } + +[features] diff --git a/exampleconfig.lua b/exampleconfig.lua new file mode 100644 index 0000000..2ecd143 --- /dev/null +++ b/exampleconfig.lua @@ -0,0 +1,7 @@ +steps = { + { + unint_alt = {"paru", "-Syu", "--noconfirm"}, + interactive = true, + command = {"paru", "-Syu"}, + } +} diff --git a/exampleconfig.toml b/exampleconfig.toml deleted file mode 100644 index 2852d90..0000000 --- a/exampleconfig.toml +++ /dev/null @@ -1,4 +0,0 @@ -[[steps]] -unint_alt = ["paru", "-Syu", "--noconfirm"] -interactive = true -command = ["paru", "-Syu"] diff --git a/src/config.rs b/src/config.rs index 08ad9c2..7075e64 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,26 +1,25 @@ -use miette::Diagnostic; -use std::process::Command; +use miette::{Context, Diagnostic, IntoDiagnostic}; +use mlua::{prelude::*, Lua}; +use std::{path::PathBuf, process::Command}; use thiserror::Error; use serde::Deserialize; -#[derive(Debug, Deserialize)] -pub struct Config { - pub steps: Vec, - #[serde(default = "default_shell")] +pub struct Config<'lua> { + pub steps: Vec>, pub shell: Vec, } -fn default_shell() -> Vec { - vec!["sh".to_string(), "-c".to_string()] -} - #[derive(Debug, Deserialize)] -pub struct Step { +pub struct Step<'lua> { pub command: CfgCommand, #[serde(default)] pub interactive: bool, pub unint_alt: Option, + pub workdir: Option, + + #[serde(skip)] + pub when: Option>, } #[derive(Debug, Deserialize)] @@ -65,3 +64,50 @@ impl CfgCommand { } } } + +pub fn run_config<'lua>(lua: &'lua Lua, content: &[u8]) -> miette::Result> { + lua.load(content) + .set_name("config") + .into_diagnostic()? + .exec() + .into_diagnostic()?; + + let lua_conf = lua.globals(); + + let mut conf = Config { + steps: vec![], + shell: lua_conf + .get::<_, Option<_>>("shell") + .into_diagnostic() + .wrap_err("config must declare `shell` variable!")? + .unwrap_or_else(|| vec!["sh".to_string(), "-c".to_string()]), + }; + + for pair in lua_conf + .get::<_, LuaTable>("steps") + .into_diagnostic() + .wrap_err("config must declare `steps` variable!")? + .pairs::() + { + let (_, step) = pair.into_diagnostic()?; + + let when = step + .get::<_, Option>("when") + .into_diagnostic() + .wrap_err("Failed to get `when` from step")?; + + let mut step = lua + .from_value_with::( + LuaValue::Table(step), + mlua::serde::de::Options::new().deny_unsupported_types(false), + ) + .into_diagnostic() + .wrap_err("Failed to deserialize step")?; + + step.when = when; + + conf.steps.push(step); + } + + Ok(conf) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c900d6d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,82 @@ +use crate::config::CfgCommand; +use config::Config; +use miette::{Context, IntoDiagnostic}; +use mlua::prelude::*; +use owo_colors::OwoColorize; +use std::path::{Path, PathBuf}; + +pub mod config; +pub mod luautil; + +pub fn exec_steps(conf: Config, uninteractive: bool) -> miette::Result<()> { + for step in conf.steps { + if step.interactive && uninteractive { + if let Some(cmd) = step.unint_alt { + exec_cmd(cmd, &conf.shell, step.when, step.workdir)?; + } + } else { + exec_cmd(step.command, &conf.shell, step.when, step.workdir)?; + } + } + + Ok(()) +} + +pub fn exec_cmd( + cmd: CfgCommand, + shell: &[String], + when: Option, + workdir: Option, +) -> miette::Result<()> { + let cmd_str = cmd.to_string(); + let len = cmd_str.len(); + + if let Some(func) = when { + let val: bool = func + .call(()) + .into_diagnostic() + .wrap_err("Failed to call when function")?; + + if !val { + println!("Skipping {}", cmd_str.blue()); + return Ok(()); + } + } + + let mut cmd = cmd.into_command(shell)?; + if let Some(workdir) = workdir { + cmd.current_dir(workdir); + } + + println!( + "{:= PathStatus; +} + +impl PathExt for Path { + fn status(&self) -> PathStatus { + match self.metadata() { + Ok(m) if m.is_file() => PathStatus::File, + Ok(m) if m.is_dir() => PathStatus::Directory, + _ => PathStatus::NonExistant, + } + } +} diff --git a/src/luautil.rs b/src/luautil.rs new file mode 100644 index 0000000..114cb0a --- /dev/null +++ b/src/luautil.rs @@ -0,0 +1,97 @@ +use miette::{Context, IntoDiagnostic}; +use mlua::prelude::*; +use sled::IVec; +use std::{collections::HashSet, fs::File, io, io::Read, path::Path, rc::Rc}; +use walkdir::WalkDir; +use xxhash_rust::xxh3::Xxh3; + +use crate::{PathExt, PathStatus}; + +pub fn get_obj(lua: &Lua, db: Rc) -> miette::Result { + let tree = Rc::new( + db.open_tree(b"hashes") + .into_diagnostic() + .wrap_err("Failed to open hashes subtree")?, + ); + + let table = lua + .create_table() + .into_diagnostic() + .wrap_err("Failed to create util table")?; + + table + .set( + "has_changed", + lua.create_function(move |_lua, (path, ignored): (String, _)| { + l_has_changed(&path, Rc::clone(&tree), ignored) + }) + .into_diagnostic() + .wrap_err("Failed to create has_changed lua function")?, + ) + .into_diagnostic() + .wrap_err("Failed to set has_changed")?; + + Ok(table) +} + +fn l_has_changed(path_str: &str, db: Rc, ignored: HashSet) -> LuaResult { + let path = Path::new(path_str); + let mut hasher = Xxh3::new(); + let ignored = ignored.iter().map(|s| Path::new(s)).collect::>(); + + match path.status() { + PathStatus::NonExistant => Ok(false), + PathStatus::File => { + let file = File::open(path)?; + append_read_hash(&mut hasher, file)?; + + let hash = IVec::from(&hasher.digest().to_be_bytes()); + let eq = db.get(path_str.as_bytes()).map_err(LuaError::external)? != Some(hash.clone()); + + db.insert(path_str.as_bytes(), hash) + .map_err(LuaError::external)?; + + Ok(eq) + }, + PathStatus::Directory => { + let walk = WalkDir::new(path); + for entry in walk { + let entry = entry.map_err(LuaError::external)?; + let cur_path = entry.path(); + + if cur_path.is_file() { + let relpath = cur_path.strip_prefix(path).map_err(LuaError::external)?; + if ignored.iter().any(|p| relpath.starts_with(p)) { + continue; + } + + let file = File::open(cur_path)?; + + append_read_hash(&mut hasher, file)?; + } + } + + let hash = IVec::from(&hasher.digest().to_be_bytes()); + let eq = db.get(path_str.as_bytes()).map_err(LuaError::external)? != Some(hash.clone()); + + db.insert(path_str.as_bytes(), hash) + .map_err(LuaError::external)?; + + Ok(eq) + }, + } +} + +fn append_read_hash(hasher: &mut Xxh3, mut read: impl Read) -> io::Result<()> { + let mut buf = [0u8; 1024]; + + loop { + match read.read(&mut buf) { + Ok(0) => return Ok(()), + Ok(l) => hasher.update(&buf[0..l]), + + Err(e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + } + } +} diff --git a/src/main.rs b/src/main.rs index e5475dd..058bab8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,8 @@ use clap::Clap; -use config::CfgCommand; use miette::{miette, Context, IntoDiagnostic}; -use owo_colors::OwoColorize; -use std::{fs::File, io::Read, path::PathBuf}; - -use crate::config::Config; - -pub mod config; +use mlua::Lua; +use std::{fs::File, io::Read, path::PathBuf, rc::Rc}; +use upgr::luautil; #[derive(Clap)] struct Opt { @@ -20,7 +16,7 @@ struct Opt { #[clap( long, short, - about = "The config file to use. Defaults to ~/.config/upgr/config.toml" + about = "The config file to use. Defaults to ~/.config/upgr/config.lua" )] config: Option, } @@ -31,9 +27,9 @@ fn main() -> miette::Result<()> { let cfg_path = if let Some(path) = opt.config { path } else { - home::home_dir() + dirs::config_dir() .ok_or_else(|| miette!("Couldn't get home directory"))? - .join(".config/upgr/config.toml") + .join("upgr/config.lua") }; let mut file = File::open(cfg_path) @@ -45,37 +41,30 @@ fn main() -> miette::Result<()> { .into_diagnostic() .wrap_err("Failed to read config")?; - let config = toml::from_slice::(&config_bytes) + let db = Rc::new( + sled::open( + dirs::cache_dir() + .ok_or_else(|| miette!("Couldn't get cache dir"))? + .join("upgr/db"), + ) .into_diagnostic() - .wrap_err("Failed to parse config")?; - - for step in config.steps { - if step.interactive && opt.uninteractive { - if let Some(cmd) = step.unint_alt { - exec_cmd(cmd, &config.shell)?; - } - } else { - exec_cmd(step.command, &config.shell)?; - } - } - - Ok(()) -} - -fn exec_cmd(cmd: CfgCommand, shell: &[String]) -> miette::Result<()> { - let cmd_str = cmd.to_string(); - let mut cmd = cmd.into_command(shell)?; - let len = 8 + cmd_str.len(); - - println!( - "{:=