diff --git a/Cargo.lock b/Cargo.lock index 0acf258..38ab7a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2182,6 +2182,25 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -2683,6 +2702,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + [[package]] name = "opendal" version = "0.50.0" @@ -2913,6 +2943,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + [[package]] name = "pbkdf2" version = "0.12.2" @@ -3695,6 +3731,7 @@ dependencies = [ "merge", "mimalloc", "once_cell", + "open", "predicates", "pretty_assertions", "quickcheck", diff --git a/Cargo.toml b/Cargo.toml index 4ecd422..48e81df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,6 +96,7 @@ indicatif = "0.17" itertools = "0.13" merge = "0.1" once_cell = "1.19" +open = "5.1.2" self_update = { version = "0.41", default-features = false, optional = true, features = ["rustls", "archive-tar", "compression-flate2"] } toml = "0.8" diff --git a/src/application.rs b/src/application.rs index 60a90cd..ac69ae6 100644 --- a/src/application.rs +++ b/src/application.rs @@ -16,6 +16,14 @@ use crate::{commands::EntryPoint, config::RusticConfig}; /// Application state pub static RUSTIC_APP: AppCell = AppCell::new(); +// Constants +pub mod constants { + pub const RUSTIC_DOCS_URL: &str = "https://rustic.cli.rs/docs"; + pub const RUSTIC_DEV_DOCS_URL: &str = "https://rustic.cli.rs/dev-docs"; + pub const RUSTIC_CONFIG_DOCS_URL: &str = + "https://github.com/rustic-rs/rustic/blob/main/config/README.md"; +} + /// Rustic Application #[derive(Debug)] pub struct RusticApp { diff --git a/src/commands.rs b/src/commands.rs index 7bb7a49..a9a29b8 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -7,6 +7,7 @@ pub(crate) mod completions; pub(crate) mod config; pub(crate) mod copy; pub(crate) mod diff; +pub(crate) mod docs; pub(crate) mod dump; pub(crate) mod find; pub(crate) mod forget; @@ -38,10 +39,11 @@ use crate::commands::webdav::WebDavCmd; use crate::{ commands::{ backup::BackupCmd, cat::CatCmd, check::CheckCmd, completions::CompletionsCmd, - config::ConfigCmd, copy::CopyCmd, diff::DiffCmd, dump::DumpCmd, forget::ForgetCmd, - init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd, prune::PruneCmd, - repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd, self_update::SelfUpdateCmd, - show_config::ShowConfigCmd, snapshots::SnapshotCmd, tag::TagCmd, + config::ConfigCmd, copy::CopyCmd, diff::DiffCmd, docs::DocsCmd, dump::DumpCmd, + forget::ForgetCmd, init::InitCmd, key::KeyCmd, list::ListCmd, ls::LsCmd, merge::MergeCmd, + prune::PruneCmd, repair::RepairCmd, repoinfo::RepoInfoCmd, restore::RestoreCmd, + self_update::SelfUpdateCmd, show_config::ShowConfigCmd, snapshots::SnapshotCmd, + tag::TagCmd, }, config::{progress_options::ProgressOptions, AllRepositoryOptions, RusticConfig}, {Application, RUSTIC_APP}, @@ -95,6 +97,9 @@ enum RusticCmd { /// Note that the exclude options only apply for comparison with a local path Diff(DiffCmd), + /// Open the documentation + Docs(DocsCmd), + /// dump the contents of a file in a snapshot to stdout Dump(DumpCmd), diff --git a/src/commands/docs.rs b/src/commands/docs.rs new file mode 100644 index 0000000..03460ce --- /dev/null +++ b/src/commands/docs.rs @@ -0,0 +1,61 @@ +//! `docs` subcommand + +use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; +use anyhow::Result; +use clap::Subcommand; + +use crate::{ + application::constants::{RUSTIC_CONFIG_DOCS_URL, RUSTIC_DEV_DOCS_URL, RUSTIC_DOCS_URL}, + RUSTIC_APP, +}; + +#[derive(Command, Debug, Clone, Copy, Default, Subcommand, Runnable)] +enum DocsTypeSubcommand { + #[default] + /// Show the user documentation + User, + /// Show the development documentation + Dev, + /// Show the configuration documentation + Config, +} + +/// Opens the documentation in the default browser. +#[derive(Clone, Command, Default, Debug, clap::Parser)] +pub struct DocsCmd { + #[clap(subcommand)] + cmd: Option, +} + +impl Runnable for DocsCmd { + fn run(&self) { + if let Err(err) = self.inner_run() { + status_err!("{}", err); + RUSTIC_APP.shutdown(Shutdown::Crash); + }; + } +} + +impl DocsCmd { + fn inner_run(&self) -> Result<()> { + let user_string = match self.cmd { + // Default to user docs if no subcommand is provided + Some(DocsTypeSubcommand::User) | None => { + open::that(RUSTIC_DOCS_URL)?; + format!("Opening the user documentation at {RUSTIC_DOCS_URL}") + } + Some(DocsTypeSubcommand::Dev) => { + open::that(RUSTIC_DEV_DOCS_URL)?; + format!("Opening the development documentation at {RUSTIC_DEV_DOCS_URL}") + } + Some(DocsTypeSubcommand::Config) => { + open::that(RUSTIC_CONFIG_DOCS_URL)?; + format!("Opening the configuration documentation at {RUSTIC_CONFIG_DOCS_URL}") + } + }; + + println!("{user_string}"); + + Ok(()) + } +}