diff --git a/Cargo.lock b/Cargo.lock index 2c8fab6..00118be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -503,6 +503,36 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_command" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da8e198c052315686d36371e8a3c5778b7852fc75cc313e4e11eeb7a644a1b62" +dependencies = [ + "clap", + "clap_complete", + "clap_complete_nushell", +] + +[[package]] +name = "clap_complete_nushell" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a0c951694691e65bf9d421d597d68416c22de9632e884c28412cb8cd8b73dce" +dependencies = [ + "clap", + "clap_complete", +] + [[package]] name = "clap_derive" version = "4.5.32" @@ -3009,6 +3039,7 @@ version = "0.2.0" dependencies = [ "binary-cookies", "clap", + "clap_complete_command", "decrypt-cookies", "rayon", "serde", diff --git a/Cargo.toml b/Cargo.toml index d64d04a..5e058cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,6 +75,7 @@ rust-ini = { version = "^0.21" } which = "8" duct = "1" clap = "^4" +clap_complete_command = "0.6.1" strum = "0.27" decrypt-cookies = { path = "./crates/decrypt-cookies", version = "0.8.0" } diff --git a/README.md b/README.md index 95b371d..44f4cf0 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ cargo install tidy-browser tidy-browser -a cd results +# Get Chrome cookie and login info +tidy-browser chromium -n Chrome -v cookie,login +cd results + # Filter by host/domain tidy-browser -a --host github.com cd results @@ -29,6 +33,13 @@ tidy-browser binary-cookies -i ~/Library/Containers/com.apple.Safari/Data/Librar cat ./binary_cookies.csv ``` +## Shell completion + +```bash +eval $(tidy-browser completions zsh) +eval $(tidy-browser completions ) +``` + ## Core crate [decrypt-cookies](./crates/decrypt-cookies) diff --git a/crates/tidy-browser/CHANGELOG.md b/crates/tidy-browser/CHANGELOG.md index 6ff7fe0..03ac7c4 100644 --- a/crates/tidy-browser/CHANGELOG.md +++ b/crates/tidy-browser/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Subcommand: binary-cookies - Output format json, jsonlines +- Generate shell completions ## [0.2.0] - 2025-07-27 diff --git a/crates/tidy-browser/Cargo.toml b/crates/tidy-browser/Cargo.toml index 1d8cede..0cf791e 100644 --- a/crates/tidy-browser/Cargo.toml +++ b/crates/tidy-browser/Cargo.toml @@ -15,6 +15,7 @@ exclude = ["CHANGELOG.md", "examples", "tests/"] [dependencies] binary-cookies = { workspace = true, features = ["csv", "serde", "sync"] } clap = { workspace = true, features = ["derive"] } +clap_complete_command = { workspace = true } decrypt-cookies = { workspace = true, features = ["serde"] } rayon = { workspace = true } serde = { workspace = true } diff --git a/crates/tidy-browser/src/args.rs b/crates/tidy-browser/src/args.rs index 06d0424..8a9e07d 100644 --- a/crates/tidy-browser/src/args.rs +++ b/crates/tidy-browser/src/args.rs @@ -2,18 +2,23 @@ use std::path::PathBuf; use clap::{ builder::{IntoResettable, OsStr, Resettable}, - ArgAction, + ArgAction, ValueHint, }; #[derive(Clone)] #[derive(Debug)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(clap::Parser)] pub struct Args { #[command(subcommand)] - pub core: Option, - - #[arg(short, long, default_value("results"), verbatim_doc_comment)] + pub cmd: Option, + + #[arg( + short, + long, + default_value("results"), + verbatim_doc_comment, + value_hint(ValueHint::DirPath) + )] /// Specify output dir /// binary-cookies ignore the arg pub output_dir: PathBuf, @@ -22,11 +27,11 @@ pub struct Args { /// All browsers data pub all_browsers: bool, - #[arg(long, default_value(","))] + #[arg(long, default_value(","), value_hint(ValueHint::Other))] /// Csv separator pub sep: String, - #[arg(long)] + #[arg(long, id("domain"), value_hint(ValueHint::Url))] /// Filter by host/domain pub host: Option, @@ -38,7 +43,6 @@ pub struct Args { #[derive(Clone, Copy)] #[derive(Debug)] #[derive(Default)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] pub enum Format { #[default] Csv, @@ -71,9 +75,8 @@ impl IntoResettable for Format { #[derive(Clone)] #[derive(Debug)] -#[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(clap::Subcommand)] -pub enum Core { +pub enum SubCmd { /// Chromium based Chromium(ChromiumArgs), /// Firefox based @@ -83,6 +86,10 @@ pub enum Core { #[cfg(target_os = "macos")] /// Safari Safari(SafariArgs), + /// Generates completions for the specified SHELL, sends them to `stdout` and exits. + Completions { + shell: clap_complete_command::Shell, + }, } #[derive(Clone, Copy)] @@ -107,7 +114,7 @@ pub struct SafariArgs { /// Only support cookie pub values: Vec, - #[arg(long)] + #[arg(long, value_hint(ValueHint::FilePath))] pub cookies_path: Option, } @@ -119,20 +126,20 @@ pub struct ChromiumArgs { #[arg(short, long)] pub name: ChromiumName, - #[arg(long, id("DIR"), verbatim_doc_comment)] + #[arg(long, id("DIR"), verbatim_doc_comment, value_hint(ValueHint::DirPath))] /// When browser is started with `--user-data-dir=DIR Specify the directory that user data (your "profile") is kept in.` - #[cfg_attr(target_os = "linux", doc = "[default value: ~/.config/google-chrome]")] + #[cfg_attr(target_os = "linux", doc = "[example value: ~/.config/google-chrome]")] #[cfg_attr( target_os = "macos", - doc = "[default value: ~/Library/Application Support/Google/Chrome]" + doc = "[example value: ~/Library/Application Support/Google/Chrome]" )] #[cfg_attr( target_os = "windows", - doc = r"[default value: ~\AppData\Local\Google\Chrome\User Data]" + doc = r"[example value: ~\AppData\Local\Google\Chrome\User Data]" )] pub user_data_dir: Option, - #[arg(short, long, value_delimiter(','))] + #[arg(short, long, value_delimiter(','), action(ArgAction::Append))] pub values: Vec, } @@ -166,20 +173,20 @@ pub struct FirefoxArgs { #[arg(short, long)] pub name: FirefoxName, - #[arg(long, id("DIR"))] + #[arg(long, id("DIR"), value_hint(ValueHint::DirPath))] /// Browser data dir. - #[cfg_attr(target_os = "linux", doc = "[default value: ~/.mozilla/firefox]")] + #[cfg_attr(target_os = "linux", doc = "[example value: ~/.mozilla/firefox]")] #[cfg_attr( target_os = "macos", - doc = "[default value: ~/Library/Application Support/Firefox]" + doc = "[example value: ~/Library/Application Support/Firefox]" )] #[cfg_attr( target_os = "windows", - doc = r"[default value: ~\AppData\Roaming\Mozilla\Firefox]" + doc = r"[example value: ~\AppData\Roaming\Mozilla\Firefox]" )] pub base: Option, - #[arg(short('P'), id("profile"))] + #[arg(short('P'), id("profile"), value_hint(ValueHint::Other))] /// When browser is started with `-P Start with .` pub profile: Option, @@ -189,7 +196,7 @@ pub struct FirefoxArgs { /// When the arg is used, other args (such as `--base`, `-P`) are ignore. pub profile_path: Option, - #[arg(short, long, value_delimiter(','))] + #[arg(short, long, value_delimiter(','), action(ArgAction::Append))] /// Only support cookie pub values: Vec, } @@ -211,8 +218,8 @@ pub enum FirefoxName { #[derive(PartialEq, Eq, PartialOrd, Ord)] #[derive(clap::Args)] pub struct BinaryCookiesArgs { - #[arg(short('i'), long)] + #[arg(short('i'), long, value_hint(ValueHint::FilePath))] pub cookies_path: PathBuf, - #[arg(short, long)] + #[arg(short, long, value_hint(ValueHint::FilePath))] pub out_file: Option, } diff --git a/crates/tidy-browser/src/cli.rs b/crates/tidy-browser/src/cli.rs index f6f7d80..eecfb99 100644 --- a/crates/tidy-browser/src/cli.rs +++ b/crates/tidy-browser/src/cli.rs @@ -1,10 +1,13 @@ -use std::{collections::HashSet, path::PathBuf, str::FromStr}; +use std::{collections::HashSet, io, path::PathBuf, str::FromStr}; +use clap::CommandFactory; use snafu::ResultExt; use strum::IntoEnumIterator; use crate::{ - args::{self, BinaryCookiesArgs, ChromiumArgs, ChromiumName, FirefoxArgs, FirefoxName, Format}, + args::{ + self, Args, BinaryCookiesArgs, ChromiumArgs, ChromiumName, FirefoxArgs, FirefoxName, Format, + }, binary_cookies::BinaryCookiesWriter, chromium::ChromiumBased, error::{self, Result}, @@ -83,9 +86,13 @@ pub async fn run_cli(args: crate::args::Args) -> Result<()> { return Ok(()); } - if let Some(core) = args.core { + + if let Some(core) = args.cmd { match core { - args::Core::Chromium(ChromiumArgs { name, user_data_dir, values }) => { + args::SubCmd::Completions { shell } => { + shell.generate(&mut Args::command(), &mut io::stdout()) + }, + args::SubCmd::Chromium(ChromiumArgs { name, user_data_dir, values }) => { ChromiumBased::write_data( name, user_data_dir, @@ -97,7 +104,7 @@ pub async fn run_cli(args: crate::args::Args) -> Result<()> { ) .await?; }, - args::Core::Firefox(FirefoxArgs { + args::SubCmd::Firefox(FirefoxArgs { name, base, profile, @@ -117,7 +124,7 @@ pub async fn run_cli(args: crate::args::Args) -> Result<()> { ) .await? }, - args::Core::BinaryCookies(BinaryCookiesArgs { cookies_path, out_file }) => { + args::SubCmd::BinaryCookies(BinaryCookiesArgs { cookies_path, out_file }) => { BinaryCookiesWriter::write_data( cookies_path, out_file.unwrap_or_else(|| match args.out_format { @@ -132,7 +139,7 @@ pub async fn run_cli(args: crate::args::Args) -> Result<()> { )?; }, #[cfg(target_os = "macos")] - args::Core::Safari(SafariArgs { values, cookies_path }) => { + args::SubCmd::Safari(SafariArgs { values, cookies_path }) => { SafariBased::write_data( HashSet::from_iter(values.into_iter()), cookies_path,