switch to lua and implement when check
This commit is contained in:
parent
05052031e3
commit
0f37d94625
|
@ -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]
|
||||
|
|
7
exampleconfig.lua
Normal file
7
exampleconfig.lua
Normal file
|
@ -0,0 +1,7 @@
|
|||
steps = {
|
||||
{
|
||||
unint_alt = {"paru", "-Syu", "--noconfirm"},
|
||||
interactive = true,
|
||||
command = {"paru", "-Syu"},
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
[[steps]]
|
||||
unint_alt = ["paru", "-Syu", "--noconfirm"]
|
||||
interactive = true
|
||||
command = ["paru", "-Syu"]
|
|
@ -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<Step>,
|
||||
#[serde(default = "default_shell")]
|
||||
pub struct Config<'lua> {
|
||||
pub steps: Vec<Step<'lua>>,
|
||||
pub shell: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_shell() -> Vec<String> {
|
||||
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<CfgCommand>,
|
||||
pub workdir: Option<PathBuf>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub when: Option<LuaFunction<'lua>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -65,3 +64,50 @@ impl CfgCommand {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_config<'lua>(lua: &'lua Lua, content: &[u8]) -> miette::Result<Config<'lua>> {
|
||||
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::<LuaValue, LuaTable>()
|
||||
{
|
||||
let (_, step) = pair.into_diagnostic()?;
|
||||
|
||||
let when = step
|
||||
.get::<_, Option<LuaFunction>>("when")
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to get `when` from step")?;
|
||||
|
||||
let mut step = lua
|
||||
.from_value_with::<Step>(
|
||||
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)
|
||||
}
|
||||
|
|
82
src/lib.rs
Normal file
82
src/lib.rs
Normal file
|
@ -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<LuaFunction>,
|
||||
workdir: Option<PathBuf>,
|
||||
) -> 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!(
|
||||
"{:=<len$}\nRunning {}\n{:=<len$}",
|
||||
"",
|
||||
cmd_str.green(),
|
||||
"",
|
||||
len = 8 + len
|
||||
);
|
||||
|
||||
cmd.spawn().into_diagnostic()?.wait().into_diagnostic()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub enum PathStatus {
|
||||
NonExistant,
|
||||
Directory,
|
||||
File,
|
||||
}
|
||||
|
||||
pub trait PathExt {
|
||||
fn status(&self) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
97
src/luautil.rs
Normal file
97
src/luautil.rs
Normal file
|
@ -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<sled::Db>) -> miette::Result<LuaTable> {
|
||||
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<sled::Tree>, ignored: HashSet<String>) -> LuaResult<bool> {
|
||||
let path = Path::new(path_str);
|
||||
let mut hasher = Xxh3::new();
|
||||
let ignored = ignored.iter().map(|s| Path::new(s)).collect::<Vec<_>>();
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
65
src/main.rs
65
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<PathBuf>,
|
||||
}
|
||||
|
@ -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>(&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!(
|
||||
"{:=<len$}\nRunning {}\n{:=<len$}",
|
||||
"",
|
||||
cmd_str.green(),
|
||||
"",
|
||||
len = len
|
||||
.wrap_err("Couldn't open database")?,
|
||||
);
|
||||
|
||||
cmd.spawn().into_diagnostic()?.wait().into_diagnostic()?;
|
||||
let lua = unsafe { Lua::unsafe_new() };
|
||||
lua.globals()
|
||||
.set(
|
||||
"upgr",
|
||||
luautil::get_obj(&lua, Rc::clone(&db)).wrap_err("Couldn't create lua upgr object")?,
|
||||
)
|
||||
.into_diagnostic()
|
||||
.wrap_err("Couldn't set upgr lua object")?;
|
||||
|
||||
let config = upgr::config::run_config(&lua, &config_bytes).wrap_err("Failed to run config")?;
|
||||
upgr::exec_steps(config, opt.uninteractive).wrap_err("Failed to execute steps")?;
|
||||
db.flush()
|
||||
.into_diagnostic()
|
||||
.wrap_err("Failed to flush DB")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue