diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4e5dd7..58eb0f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,6 +48,9 @@ jobs: override: true # These dependencies are required for `clipboard` - run: sudo apt-get install -y -qq libxcb1-dev libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev + - uses: actions-rs/cargo@v1 + with: + command: build - uses: actions-rs/cargo@v1 with: command: test diff --git a/Cargo.lock b/Cargo.lock index cbc63df..3181aa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,20 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" +[[package]] +name = "assert_cmd" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b6bd5df287567ffdf4ddf4d33060048e1068308e5f62d81c6f9824a045a48" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -244,6 +258,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "dirs" version = "3.0.2" @@ -264,6 +284,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dtoa" version = "0.4.8" @@ -615,6 +641,32 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "predicates" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df" +dependencies = [ + "difference", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro2" version = "1.0.26" @@ -887,6 +939,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "tui" version = "0.15.0" @@ -934,6 +992,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.3.2" @@ -1048,10 +1115,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "xplr" -version = "0.13.6" +version = "0.13.7" dependencies = [ "ansi-to-tui", "anyhow", + "assert_cmd", "chrono", "criterion", "crossterm", diff --git a/Cargo.toml b/Cargo.toml index a411dab..427ffa4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "xplr" -version = "0.13.6" # Update lua.rs +version = "0.13.7" # Update lua.rs authors = ["Arijit Basu "] edition = "2018" description = "A hackable, minimal, fast TUI file explorer" @@ -14,6 +14,9 @@ categories = ["command-line-interface", "command-line-utilities"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "xplr" + [dependencies] tui = { version = "0.15.0", default-features = false, features = ['crossterm', 'serde'] } termion = "1.5.6" @@ -34,6 +37,7 @@ libc = "0.2.95" [dev-dependencies] criterion = "0.3" +assert_cmd = "1.0" [[bench]] name = "criterion" diff --git a/examples/run.rs b/examples/run.rs index 8cef95e..0334ce8 100644 --- a/examples/run.rs +++ b/examples/run.rs @@ -1,6 +1,6 @@ fn main() { let pwd = std::path::PathBuf::from("/"); - match xplr::run(pwd, None) { + match xplr::run(pwd, None, None) { Ok(Some(out)) => print!("{}", out), Ok(None) => {} Err(err) => { diff --git a/src/app.rs b/src/app.rs index 774bc07..dfeab67 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2803,8 +2803,17 @@ impl App { } /// Run xplr TUI -pub fn run(pwd: PathBuf, focused_path: Option) -> Result> { +pub fn run( + pwd: PathBuf, + focused_path: Option, + on_load: Option>, +) -> Result> { let lua = mlua::Lua::new(); - let app = App::create(pwd, &lua)?; + let mut app = App::create(pwd, &lua)?; + if let Some(msgs) = on_load { + for msg in msgs { + app = app.enqueue(Task::new(MsgIn::External(msg), None)); + } + } app.run(focused_path, &lua) } diff --git a/src/bin/xplr.rs b/src/bin/xplr.rs new file mode 100644 index 0000000..cd8bff8 --- /dev/null +++ b/src/bin/xplr.rs @@ -0,0 +1,228 @@ +#![allow(clippy::too_many_arguments)] + +use anyhow::Result; +use std::collections::VecDeque; +use std::env; +use std::io; +use std::path::PathBuf; +use xplr::app; + +#[derive(Debug, Clone, Default)] +struct Cli { + version: bool, + help: bool, + path: Option, + on_load: Vec, +} + +impl Cli { + fn parse(args: env::Args) -> Result { + let mut args: VecDeque = args.skip(1).collect(); + let mut cli = Self::default(); + + while let Some(arg) = args.pop_front() { + match arg.as_str() { + // Flags + "-" => { + let mut path = String::new(); + if io::stdin().read_line(&mut path).is_ok() { + cli.path = + Some(path.trim_end_matches("\r\n").trim_end_matches('\n').into()); + }; + } + + "-h" | "--help" => { + cli.help = true; + } + + "-V" | "--version" => { + cli.version = true; + } + + "--" => { + if cli.path.is_none() { + cli.path = args.pop_front(); + } + return Ok(cli); + } + + // Options + "--on-load" => { + while let Some(msg) = args.pop_front() { + if msg.starts_with('-') { + args.push_front(msg); + break; + } else { + cli.on_load.push(serde_yaml::from_str(&msg)?); + } + } + } + + // path + path => { + if cli.path.is_none() { + cli.path = Some(path.into()); + } + } + } + } + Ok(cli) + } +} + +fn main() { + let cli = Cli::parse(env::args()).unwrap_or_else(|e| { + println!("error: {}", e); + std::process::exit(1); + }); + + if cli.help { + let usage = r###" + xplr [FLAG]... [OPTION]... [PATH]"###; + + let flags = r###" + - Read PATH from stdin + -- End of flags and options + -h, --help Prints help information + -V, --version Prints version information"###; + + let options = r###" + --on-load ... Send messages when xplr loads"###; + + let args = r###" + Path to focus on, or enter if directory"###; + + let help = format!( + "xplr {}\n{}\n{}\n\nUSAGE:{}\n\nFLAGS:{}\n\nOPTIONS:{}\n\nARGS:{}", + xplr::app::VERSION, + env!("CARGO_PKG_AUTHORS"), + env!("CARGO_PKG_DESCRIPTION"), + usage, + flags, + options, + args, + ); + let help = help.trim(); + + println!("{}", help); + } else if cli.version { + println!("xplr {}", xplr::app::VERSION); + } else { + let mut pwd = PathBuf::from(cli.path.unwrap_or_else(|| ".".into())) + .canonicalize() + .unwrap_or_default(); + let mut focused_path = None; + + if pwd.is_file() { + focused_path = pwd.file_name().map(|p| p.into()); + pwd = pwd.parent().map(|p| p.into()).unwrap_or_else(|| ".".into()); + } + + match app::run(pwd, focused_path, Some(cli.on_load)) { + Ok(Some(out)) => print!("{}", out), + Ok(None) => {} + Err(err) => { + if !err.to_string().is_empty() { + eprintln!("error: {}", err); + }; + + std::process::exit(1); + } + } + } +} + +#[cfg(test)] +mod tests { + use assert_cmd::Command; + + #[test] + fn test_cli_version() { + Command::cargo_bin("xplr") + .unwrap() + .arg("--version") + .assert() + .success() + .code(0) + .stdout(format!("xplr {}\n", xplr::app::VERSION)) + .stderr(""); + + Command::cargo_bin("xplr") + .unwrap() + .arg("-V") + .assert() + .success() + .code(0) + .stdout(format!("xplr {}\n", xplr::app::VERSION)) + .stderr(""); + } + + #[test] + fn test_cli_help() { + Command::cargo_bin("xplr") + .unwrap() + .arg("-h") + .assert() + .success() + .code(0) + .stderr(""); + + Command::cargo_bin("xplr") + .unwrap() + .arg("--help") + .assert() + .success() + .code(0) + .stderr(""); + } + + // TODO: Fix running GitHub action + // + // #[test] + // fn test_cli_path_arg_valid() { + // Command::cargo_bin("xplr") + // .unwrap() + // .arg("/tmp") + // .arg("--on-load") + // .arg("PrintResultAndQuit") + // .assert() + // .success() + // .code(0) + // .stderr(""); + + // Command::cargo_bin("xplr") + // .unwrap() + // .arg("/tmp") + // .arg("--on-load") + // .arg("PrintResultAndQuit") + // .assert() + // .success() + // .code(0) + // .stderr(""); + + // Command::cargo_bin("xplr") + // .unwrap() + // .arg("--on-load") + // .arg("PrintResultAndQuit") + // .arg("--") + // .arg("/tmp") + // .assert() + // .success() + // .code(0) + // .stderr(""); + // } + + // #[test] + // fn test_cli_path_stdin_valid() { + // Command::cargo_bin("xplr") + // .unwrap() + // .arg("-") + // .arg("--on-load") + // .arg("PrintResultAndQuit") + // .write_stdin("/tmp\n") + // .assert() + // .success() + // .code(0) + // .stderr(""); + // } +} diff --git a/src/lua.rs b/src/lua.rs index fd30596..ba0e07d 100644 --- a/src/lua.rs +++ b/src/lua.rs @@ -138,10 +138,11 @@ mod test { assert!(check_version("0.13.4", "foo path").is_ok()); assert!(check_version("0.13.5", "foo path").is_ok()); assert!(check_version("0.13.6", "foo path").is_ok()); + assert!(check_version("0.13.7", "foo path").is_ok()); - assert!(check_version("0.13.7", "foo path").is_err()); - assert!(check_version("0.14.6", "foo path").is_err()); - assert!(check_version("0.11.6", "foo path").is_err()); - assert!(check_version("1.13.6", "foo path").is_err()); + assert!(check_version("0.13.8", "foo path").is_err()); + assert!(check_version("0.14.7", "foo path").is_err()); + assert!(check_version("0.11.7", "foo path").is_err()); + assert!(check_version("1.13.7", "foo path").is_err()); } } diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 985e7ae..0000000 --- a/src/main.rs +++ /dev/null @@ -1,29 +0,0 @@ -#![allow(clippy::too_many_arguments)] - -use std::env; -use std::path::PathBuf; -use xplr::app; - -fn main() { - let mut pwd = PathBuf::from(env::args().nth(1).unwrap_or_else(|| ".".into())) - .canonicalize() - .unwrap_or_default(); - let mut focused_path = None; - - if pwd.is_file() { - focused_path = pwd.file_name().map(|p| p.into()); - pwd = pwd.parent().map(|p| p.into()).unwrap_or_else(|| ".".into()); - } - - match app::run(pwd, focused_path) { - Ok(Some(out)) => print!("{}", out), - Ok(None) => {} - Err(err) => { - if !err.to_string().is_empty() { - eprintln!("error: {}", err); - }; - - std::process::exit(1); - } - } -} diff --git a/tests/bdd/mod.rs b/tests/bdd/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/tests/bdd/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/mod.rs b/tests/mod.rs deleted file mode 100644 index 4f18d71..0000000 --- a/tests/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod bdd;