feat: add fancy clap cli
This commit is contained in:
143
Cargo.lock
generated
143
Cargo.lock
generated
@@ -2,6 +2,56 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.10.0"
|
version = "2.10.0"
|
||||||
@@ -26,6 +76,52 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.56"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "displaydoc"
|
name = "displaydoc"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
@@ -77,6 +173,12 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -179,6 +281,12 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jobserver"
|
name = "jobserver"
|
||||||
version = "0.1.34"
|
version = "0.1.34"
|
||||||
@@ -193,7 +301,9 @@ dependencies = [
|
|||||||
name = "lgit"
|
name = "lgit"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
"git2",
|
"git2",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -238,6 +348,12 @@ version = "0.4.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -330,6 +446,12 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.114"
|
version = "2.0.114"
|
||||||
@@ -386,6 +508,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -401,6 +529,21 @@ dependencies = [
|
|||||||
"wit-bindgen",
|
"wit-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wit-bindgen"
|
name = "wit-bindgen"
|
||||||
version = "0.51.0"
|
version = "0.51.0"
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.56", features = ["derive"] }
|
||||||
git2 = { version = "0.20.3", features = ["vendored-libgit2"], default-features = false }
|
git2 = { version = "0.20.3", features = ["vendored-libgit2"], default-features = false }
|
||||||
|
log = { version = "0.4.29", features = ["std"] }
|
||||||
|
|||||||
50
src/logger.rs
Normal file
50
src/logger.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use std::io::{self, IsTerminal};
|
||||||
|
|
||||||
|
use log::{Level, LevelFilter, Log};
|
||||||
|
|
||||||
|
const CSI: &str = "\x1B[";
|
||||||
|
|
||||||
|
struct Logger {
|
||||||
|
is_terminal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(level: LevelFilter) {
|
||||||
|
let logger = Logger {
|
||||||
|
is_terminal: io::stderr().is_terminal(),
|
||||||
|
};
|
||||||
|
|
||||||
|
log::set_boxed_logger(Box::new(logger)).expect("init only called once");
|
||||||
|
log::set_max_level(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Log for Logger {
|
||||||
|
fn enabled(&self, metadata: &log::Metadata) -> bool {
|
||||||
|
metadata.level() <= log::max_level()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&self, record: &log::Record) {
|
||||||
|
if !self.enabled(record.metadata()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match record.level() {
|
||||||
|
Level::Error if self.is_terminal => eprint!("{CSI}1;31merror:{CSI}0m "),
|
||||||
|
Level::Warn if self.is_terminal => eprint!("{CSI}1;33mwarn:{CSI}0m "),
|
||||||
|
Level::Info if self.is_terminal => eprint!("{CSI}1minfo:{CSI}0m "),
|
||||||
|
Level::Debug if self.is_terminal => eprint!("{CSI}1mdebug:{CSI}0m "),
|
||||||
|
Level::Trace if self.is_terminal => eprint!("{CSI}1mtrace:{CSI}0m "),
|
||||||
|
level => eprint!("{}: ", level.as_str().to_lowercase()),
|
||||||
|
}
|
||||||
|
|
||||||
|
// let module_path = record.module_path().unwrap_or("lssh");
|
||||||
|
// if self.is_terminal {
|
||||||
|
// eprint!("{CSI}1m{module_path}:{CSI}0m ");
|
||||||
|
// } else {
|
||||||
|
// eprint!("{module_path}: ");
|
||||||
|
// }
|
||||||
|
|
||||||
|
eprintln!("{}", record.args());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&self) {}
|
||||||
|
}
|
||||||
41
src/macros.rs
Normal file
41
src/macros.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
macro_rules! ok_or_continue {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ok_or_bail {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! io_error {
|
||||||
|
($kind:ident) => {
|
||||||
|
Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::$kind,
|
||||||
|
format!("{}", std::io::ErrorKind::$kind),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
($kind:ident, $prefix:expr) => {
|
||||||
|
Err(io::Error::new(
|
||||||
|
std::io::ErrorKind::$kind,
|
||||||
|
format!("{}: {}", $prefix, std::io::ErrorKind::$kind),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
($kind:expr, $prefix:expr) => {
|
||||||
|
Err(io::Error::new($kind, format!("{}: {}", $prefix, $kind)))
|
||||||
|
};
|
||||||
|
}
|
||||||
113
src/main.rs
113
src/main.rs
@@ -1,3 +1,7 @@
|
|||||||
|
mod logger;
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
fs::File,
|
fs::File,
|
||||||
@@ -7,78 +11,71 @@ use std::{
|
|||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
use git2::Repository;
|
use git2::Repository;
|
||||||
|
use log::LevelFilter;
|
||||||
|
|
||||||
const GIT_DIR: &str = ".git";
|
const GIT_DIR: &str = ".git";
|
||||||
const CACHE_DIR_TAG: &str = "CACHEDIR.TAG";
|
const CACHE_DIR_TAG: &str = "CACHEDIR.TAG";
|
||||||
const CACHE_DIR_TAG_HEADER: &[u8] = b"Signature: 8a477f597d28d172789f06886806bc55";
|
const CACHE_DIR_TAG_HEADER: &[u8] = b"Signature: 8a477f597d28d172789f06886806bc55";
|
||||||
|
|
||||||
macro_rules! ok_or_continue {
|
#[derive(Parser)]
|
||||||
($e:expr) => {
|
#[command(version)]
|
||||||
match $e {
|
struct Cli {
|
||||||
Ok(val) => val,
|
/// Do not ignore dot-prefixed files or caches.
|
||||||
Err(e) => {
|
#[arg(short, long)]
|
||||||
eprintln!("error: {e}");
|
all: bool,
|
||||||
continue;
|
/// Increase logging verbosity
|
||||||
}
|
#[arg(short = 'v', action = clap::ArgAction::Count)]
|
||||||
}
|
verbose: u8,
|
||||||
};
|
/// Starting point.
|
||||||
}
|
dirs: Vec<PathBuf>,
|
||||||
|
|
||||||
macro_rules! ok_or_bail {
|
|
||||||
($e:expr) => {
|
|
||||||
match $e {
|
|
||||||
Ok(val) => val,
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("error: {e}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! io_error {
|
|
||||||
($kind:ident) => {
|
|
||||||
Err(std::io::Error::new(
|
|
||||||
std::io::ErrorKind::$kind,
|
|
||||||
format!("{}", std::io::ErrorKind::$kind),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
($kind:ident, $prefix:expr) => {
|
|
||||||
Err(io::Error::new(
|
|
||||||
std::io::ErrorKind::$kind,
|
|
||||||
format!("{}: {}", $prefix, std::io::ErrorKind::$kind),
|
|
||||||
))
|
|
||||||
};
|
|
||||||
($kind:expr, $prefix:expr) => {
|
|
||||||
Err(io::Error::new($kind, format!("{}: {}", $prefix, $kind)))
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let dir = match env::args().nth(1).map(PathBuf::from) {
|
let cli = Cli::parse();
|
||||||
Some(dir) => Ok(dir),
|
|
||||||
None => env::current_dir(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let dir = ok_or_bail!(dir);
|
logger::init(match cli.verbose {
|
||||||
|
0 => LevelFilter::Error,
|
||||||
|
1 => LevelFilter::Info,
|
||||||
|
2 => LevelFilter::Debug,
|
||||||
|
3.. => LevelFilter::Trace,
|
||||||
|
});
|
||||||
|
|
||||||
let walker = Walker {
|
let walker = Walker {
|
||||||
ignore_hidden: true,
|
ignore_hidden: !cli.all,
|
||||||
ignore_caches: true,
|
ignore_caches: !cli.all,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dirs = {
|
||||||
|
let mut dirs = cli.dirs;
|
||||||
|
if dirs.is_empty() {
|
||||||
|
dirs.push(ok_or_bail!(env::current_dir()));
|
||||||
|
}
|
||||||
|
dirs
|
||||||
};
|
};
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let join_handle = thread::spawn(move || walker.walk(&dir, tx));
|
let join_handle = thread::spawn(move || -> io::Result<()> {
|
||||||
|
for dir in dirs {
|
||||||
|
walker.walk(&dir, tx.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
for repo in rx {
|
for repo in rx {
|
||||||
let repo_path = repo.path().parent().unwrap().display();
|
let repo_path = repo.path().parent().expect("GIT_DIR cannot be root").display();
|
||||||
|
|
||||||
println!("{repo_path}");
|
println!("{repo_path}");
|
||||||
|
|
||||||
let worktrees = ok_or_continue!(repo.worktrees());
|
let worktrees = ok_or_continue!(repo.worktrees());
|
||||||
for worktree in worktrees.iter() {
|
for name in worktrees.iter() {
|
||||||
let worktree = repo.find_worktree(worktree.unwrap()).unwrap();
|
let Some(name) = name else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let worktree = ok_or_continue!(repo.find_worktree(name));
|
||||||
let worktree_path = worktree.path().display();
|
let worktree_path = worktree.path().display();
|
||||||
println!("{worktree_path}");
|
println!("{worktree_path}");
|
||||||
}
|
}
|
||||||
@@ -117,7 +114,7 @@ impl Walker {
|
|||||||
|
|
||||||
let entry_path = entry.path();
|
let entry_path = entry.path();
|
||||||
|
|
||||||
if self.ignore_hidden && entry.file_name().as_encoded_bytes().first() == Some(&b'.') {
|
if self.ignore_hidden && is_hidden_file(&entry_path) {
|
||||||
// Skip "hidden" dot-prefixed files.
|
// Skip "hidden" dot-prefixed files.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -155,3 +152,15 @@ fn is_cache_dir(path: &Path) -> io::Result<bool> {
|
|||||||
|
|
||||||
Ok(CACHE_DIR_TAG_HEADER == buf)
|
Ok(CACHE_DIR_TAG_HEADER == buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_hidden_file(path: &Path) -> bool {
|
||||||
|
let Some(str) = path.file_name() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(ch) = str.as_encoded_bytes().first() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
*ch == b'.'
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user