diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf4fc13..65165cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build Sniff +name: CI on: [push] @@ -13,24 +13,12 @@ jobs: contents: write steps: - - uses: actions/checkout@v2 - - - name: Version tag. - run: echo "VERSION=$(grep -E '^version' ./Cargo.toml| sed 's/version = \"\(.*\)\"/\1/g')">>$GITHUB_ENV + - name: Checkout + uses: actions/checkout@v2 - - name: Compile Binary. + - name: Build run: cargo build --release --verbose - - name: Release - uses: "marvinpinto/action-automatic-releases@latest" - with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - automatic_release_tag: ${{ env.VERSION }} - prerelease: true - title: "Sniff ${{ env.VERSION }}" - files: | - target/release/sniff - rustfmt: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index ea8c4bf..7b10e8c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +*.gz +*.out diff --git a/Cargo.lock b/Cargo.lock index 5baad2b..18208bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,9 +199,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "log" @@ -301,9 +301,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -360,18 +360,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -400,23 +400,22 @@ dependencies = [ [[package]] name = "sniff" -version = "0.1.0" +version = "1.0.0" dependencies = [ "env_logger", "flate2", "libc", "log", "notify", - "regex", "serde", "serde_json", ] [[package]] name = "syn" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 259c826..7c3589b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,9 @@ [package] name = "sniff" -version = "0.1.0" +version = "1.0.0" +authors = [ + "Shinyzenith ", +] edition = "2021" [build-dependencies] @@ -11,7 +14,6 @@ env_logger = "0.9.0" libc = "0.2.124" log = "0.4.16" notify = "4.0.17" -regex = "1.5.5" serde_json = "1.0.79" serde = {version="1.0.137", features=["derive"]} diff --git a/Makefile b/Makefile index 69440f8..213acca 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ clean: install: @find ./docs -type f -iname "*.1.gz" -exec cp {} $(MAN1_DIR) \; - @find ./docs -type f -iname "*.5.gz" -exec cp {} $(MAN7_DIR) \; + @find ./docs -type f -iname "*.5.gz" -exec cp {} $(MAN5_DIR) \; @mkdir -p $(TARGET_DIR) @cp $(SOURCE_DIR)/$(BINARY) $(TARGET_DIR) @chmod +x $(TARGET_DIR)/$(BINARY) diff --git a/docs/sniff.5.scd b/docs/sniff.5.scd index 7484238..c5e3aa6 100644 --- a/docs/sniff.5.scd +++ b/docs/sniff.5.scd @@ -14,20 +14,44 @@ Sniff uses the JSON file format. Configuration files should always be named *sni # SYNTAX -All JSON keys apart from the following are treated as regular expressions to -check for file changes: +All JSON keys apart from the following are treated as patterns to check for file changes: - _sniff_ignore_dir_ (Make sniff ignore directories.) - _sniff_ignore_file_ (Make sniff ignore files.) - _sniff_cooldown_ (State the cooldown between consecutive build commands.) +Each pattern check can either be an object or an array. + - If the pattern check if an array then the commands in it are executed. + - If the pattern check is an object then the following keys are searched for: + - "commands": The array of commands to execute. + - "relative_dir": The directory relative to which the commands should be executed. + # EXAMPLE ``` { - ".*.rs": ["cargo build --release"], - ".*.zig": ["zig test .", "zig build"], - "sniff_ignore_dir": ["target"], - "sniff_ignore_file": ["test.rs"], + "zig": [ + "make" + ], + "c": { + "commands": [ + "make" + ], + "relative_dir": "./nextctl" + }, + "rs": { + "commands": [ + "cargo clippy", + "cargo build --release" + ], + "relative_dir": "./nextctl-rs" + }, + "sniff_ignore_dir": [ + "zig-out", + "zig-cache", + ".git", + "target" + ], + "sniff_ignore_file": [], "sniff_cooldown": 650 } ``` diff --git a/sniff.json b/sniff.json index be356d5..8aa611e 100644 --- a/sniff.json +++ b/sniff.json @@ -1,6 +1,12 @@ { - ".*.rs": ["cargo clippy", "cargo build --release"], - "sniff_ignore_dir": ["target"], + "rs": [ + "cargo clippy", + "cargo build --release" + ], + "sniff_ignore_dir": [ + "target", + ".git" + ], "sniff_ignore_file": [], "sniff_cooldown": 650 } diff --git a/src/sniff.rs b/src/sniff.rs index 814de8f..c159274 100644 --- a/src/sniff.rs +++ b/src/sniff.rs @@ -1,6 +1,7 @@ use libc::{wait, WNOHANG}; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; use std::time::{Duration, Instant}; use std::{ env, @@ -27,8 +28,12 @@ fn main() { match arg.as_str() { "-d" => env::set_var("RUST_LOG", "sniff=trace"), _ => { - println!("Usage:\nsniff [FLAGS]\n\nFlags:\n-d -- debug",); - exit(1); + println!( + "Usage:\nsniff [FLAGS]\nVersion: {:#?}\n\nFlags:\n-d -- debug\n\nAuthors: {:#?}", + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_AUTHORS") + ); + exit(0); } } } @@ -37,10 +42,10 @@ fn main() { let config_file = fetch_sniff_config_file(); let json: serde_json::Value = - serde_json::from_str(config_file.as_str()).expect("JSON was not well-formatted"); + serde_json::from_str(config_file.as_str()).expect("JSON not well-formatted"); let ignore_list: Ignore = - serde_json::from_str(config_file.as_str()).expect("JSON was not well-formatted"); + serde_json::from_str(config_file.as_str()).expect("JSON not well-formatted"); if let Ok(current_dir) = env::current_dir() { let (tx, rx) = channel(); @@ -100,21 +105,35 @@ fn fetch_sniff_config_file() -> String { fs::read_to_string(config_file_path).unwrap() } -fn run_system_command(command: &str) { +fn run_system_command(command_dir_pairs: Vec<(Vec, Option)>) { unsafe { let mut status = WNOHANG; wait(&mut status); } - if let Err(e) = Command::new("sh").arg("-c").arg(command).spawn() { - log::error!("Failed to execute {}", command); - log::error!("Error, {}", e); - } else { - log::info!("Ran: {:#?}", command); + for (commands, relative_dir) in command_dir_pairs { + for command in commands { + // We need to split the arg apart because it returns a temporary pointer that is free'd after + // the execution of the declaration line below. + let mut cmd = Command::new("sh"); + cmd.arg("-c").arg(command.clone()); + + // Dir setting + if let Some(relative_dir) = relative_dir.clone() { + cmd.current_dir(relative_dir); + } + + if let Err(e) = cmd.spawn() { + log::error!("Failed to execute {}", command); + log::error!("Error, {}", e); + } else { + log::info!("Ran {:#?}", command); + } + } } } fn check_and_run( - file_name: &str, + file_path: &str, json: serde_json::Value, ignore_list: Ignore, last_run: &mut Instant, @@ -127,18 +146,18 @@ fn check_and_run( // First the file check. for ignore_file in ignore_list.sniff_ignore_file { - if ignore_file[..] == file_name[file_name.rfind('/').unwrap() + 1..] { - log::debug!("Ignoring {} as it's in the ingored file list.", file_name); + if ignore_file[..] == file_path[file_path.rfind('/').unwrap() + 1..] { + log::debug!("Ignoring {} as it's in the ingored file list.", file_path); return; } } // Now the dir check. for ignore_dir in ignore_list.sniff_ignore_dir { - if file_name[0..file_name.rfind('/').unwrap()].contains(ignore_dir.as_str()) { + if file_path[0..file_path.rfind('/').unwrap()].contains(ignore_dir.as_str()) { log::debug!( "Ignoring {} as it's in the ingored directory list.", - file_name + file_path ); return; } @@ -146,32 +165,87 @@ fn check_and_run( *last_run = Instant::now(); + let mut command_dir_pairs: Vec<(Vec, Option)> = Vec::new(); + let file_name = match Path::new(file_path).extension() { + Some(x) => match x.to_str() { + Some(y) => y, + None => { + log::error!("OSstr to str conversion failed!"); + exit(1); + } + }, + None => { + log::error!("Failed to get file name from absolute path!"); + exit(1); + } + }; + dbg!(file_name); match json { serde_json::Value::Object(map) => { - for (pattern, commands) in map.iter() { - if regex::Regex::new(pattern) - .unwrap() - .captures(file_name) - .is_some() - { - log::debug!("Found a pattern match!"); - match commands { + for (pattern, instructions) in map.iter() { + if pattern.eq(file_name) { + match instructions { + serde_json::Value::Object(obj) => { + let mut relative_dir: Option = None; + let mut command_strs: Vec = Vec::new(); + for (key, pair) in obj.iter() { + match key.as_str() { + "relative_dir" => { + if let serde_json::Value::String(dir) = pair { + relative_dir = Some(PathBuf::from(dir)); + } else { + log::error!( + "Key \"relative_dir\" only takes string values!" + ); + exit(1); + } + } + "commands" => { + if let serde_json::Value::Array(commands) = pair { + for command in commands { + match command { + serde_json::Value::String(command) => { + command_strs.push(command.to_string()); + } + _ => { + log::error!("Command wasn't a string."); + exit(1); + } + } + } + } else { + log::error!("Key \"commands\" only takes arrays!"); + exit(1); + } + } + _ => {} + } + } + command_dir_pairs.push((command_strs, relative_dir.clone())); + + log::info!("Running build scripts for {:#?}", file_path); + run_system_command(command_dir_pairs.clone()); + } serde_json::Value::Array(arr) => { + let mut commands: Vec = Vec::new(); for command in arr { match command { serde_json::Value::String(command) => { - log::info!("Running build scripts for: {:#?}", file_name); - run_system_command(command); + commands.push(command.to_owned()); } _ => { - log::error!("Command wasn't a string."); + log::error!( + "Command arrays must be filled with strings only!" + ); exit(1); } } } + + run_system_command(vec![(commands, None)]); } _ => { - log::error!("Did not recieve an array of commands."); + log::error!("Received incorrect pattern object for {:#?}", pattern); exit(1); } }