Implement CLI arguments

Going with custom CLI parsing for minimalism and flexibility.

Closes: https://github.com/sayanarijit/xplr/issues/228
pull/235/head v0.13.7
Arijit Basu 3 years ago committed by Arijit Basu
parent a1a1dee4af
commit fabcc8e865

@ -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

70
Cargo.lock generated

@ -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",

@ -1,6 +1,6 @@
[package]
name = "xplr"
version = "0.13.6" # Update lua.rs
version = "0.13.7" # Update lua.rs
authors = ["Arijit Basu <sayanarijit@gmail.com>"]
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"

@ -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) => {

@ -2803,8 +2803,17 @@ impl App {
}
/// Run xplr TUI
pub fn run(pwd: PathBuf, focused_path: Option<PathBuf>) -> Result<Option<String>> {
pub fn run(
pwd: PathBuf,
focused_path: Option<PathBuf>,
on_load: Option<Vec<ExternalMsg>>,
) -> Result<Option<String>> {
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)
}

@ -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<String>,
on_load: Vec<app::ExternalMsg>,
}
impl Cli {
fn parse(args: env::Args) -> Result<Self> {
let mut args: VecDeque<String> = 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 <MESSAGE>... Send messages when xplr loads"###;
let args = r###"
<PATH> 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("");
// }
}

@ -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());
}
}

@ -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);
}
}
}

@ -1 +0,0 @@
mod bdd;
Loading…
Cancel
Save