/*! Application-specific logic lives here */ // Parts Copyright 2017-2019, Stephan Sokolow // Standard library imports use std::path::PathBuf; // 3rd-party crate imports use structopt::StructOpt; use log::{debug, info, trace}; // Local Imports use crate::errors::*; use crate::gitignore::Gitignore; use crate::helpers; use crate::helpers::{git_dir, BoilerplateOpts, HELP_TEMPLATE}; use crate::template::*; /// The verbosity level when no `-q` or `-v` arguments are given, with `0` being `-q` pub const DEFAULT_VERBOSITY: u64 = 1; /// Command-line argument schema /// /// ## Relevant Conventions: /// /// * Make sure that there is a blank space between the `` `` line and the /// description text or the `--help` output won't comply with the platform conventions that /// `help2man` depends on to generate your manpage. (Specifically, it will mistake the ` /// ` line for part of the description.) /// * `StructOpt`'s default behaviour of including the author name in the `--help` output is an /// oddity among Linux commands and, if you don't disable it, you run the risk of people /// unfamiliar with `StructOpt` assuming that you are an egotistical person who made a conscious /// choice to add it. /// /// The proper standardized location for author information is the `AUTHOR` section which you /// can read about by typing `man help2man`. /// /// ## Cautions: /// * Subcommands do not inherit `template` and it must be re-specified for each one. ([clap-rs/clap#1184](https://github.com/clap-rs/clap/issues/1184)) /// * Double-check that your choice of `about` or `long_about` is actually overriding this /// doc comment. The precedence is affected by things you wouldn't expect, such as the presence /// or absence of `template` and it's easy to wind up with this doc-comment as your `--help` /// ([TeXitoi/structopt#173](https://github.com/TeXitoi/structopt/issues/173)) /// * Do not begin the description text for subcommands with `\n`. It will break the formatting in /// the top-level help output's list of subcommands. #[derive(StructOpt, Debug)] #[structopt(template = HELP_TEMPLATE, about = "TODO: Replace me with the description text for the command", global_setting = structopt::clap::AppSettings::ColoredHelp)] pub struct CliOpts { #[allow(clippy::missing_docs_in_private_items)] // StructOpt won't let us document this #[structopt(flatten)] pub boilerplate: BoilerplateOpts, /// Subcommands #[structopt(subcommand)] cmd: Command, } /// gitig lets you easily start with a fresh gitignore from a template and adds new lines as you /// wish #[derive(StructOpt, Debug)] pub enum Command { /// Add a line to the gitignore Add { /// The glob string that should be added glob: Vec, /// Add the entry to the repo local ignore file #[structopt(short)] local: bool, }, /// Download a gitignore for a language Get { /// Append template to an existing .gitignore file #[structopt(short)] append: bool, /// The language for which the gitignore should be downloaded /// /// A list with all available languages and projects can be printed with `list-templates`. lang: String, }, /// List all available templates that can be downloaded ListTemplates, /// Write a completion definition for the specified shell to stdout (bash, zsh, etc.) DumpCompletions { /// Shell to generate completion for shell: Option, }, /// Print current .gitignore to stdout Cat, } /// Runs the command `add` fn run_add(glob: Vec, local: bool) -> Result<()> { for g in glob { add(&g, local)?; } Ok(()) } fn add(glob: &str, local: bool) -> Result<()> { trace!("running command `add` with glob '{}'", &glob); let root = match git_dir()? { Some(r) => r, None => return Err(ErrorKind::NoGitRootFound.into()), }; info!("Working with git root in {:?}", root); let mut file_path = PathBuf::from(&root); if local { file_path.push(".git/info/exclude") } else { file_path.push(".gitignore"); } let gitig = Gitignore::from_path(&file_path); gitig.add_line(glob)?; debug!("Added '{}' to {}", glob, gitig); Ok(()) } /// Runs the command `get` fn run_get(lang: &str, append: bool) -> Result<()> { trace!("Run command `get` with lang {}", &lang); let mut root = match git_dir()? { Some(r) => r, None => return Err(ErrorKind::NoGitRootFound.into()), }; info!("Working with git root in {:?}", root); let cache = helpers::default_cache()?; let tmpl: Template = if cache.exists(lang) { debug!("Found a template for {} in cache", lang); cache.get(lang)? } else { let tmpls = helpers::get_templates()?; let mut tmpl = tmpls.get(lang).ok_or_else(|| ErrorKind::TemplateNotFound(lang.to_string()))?.clone(); tmpl.load_content()?; cache.set(lang, &tmpl)?; tmpl }; root.push(".gitignore"); tmpl.write_to(&root, append)?; trace!("Wrote template to file"); Ok(()) } /// Runs the command `list-templates` #[allow(clippy::print_stdout)] fn run_list_templates() -> Result<()> { let tmpl = helpers::get_templates()?; let names = tmpl.list_names(); println!("{}", names.join("\n")); Ok(()) } /// Runs the command `dump-completion` to generate a shell completion script fn run_dump_completion(shell: Option) -> Result<()> { let shell = shell.ok_or(ErrorKind::NoShellProvided)?; debug!("Request to dump completion for {}", shell); CliOpts::clap().gen_completions_to( CliOpts::clap().get_bin_name().unwrap_or_else(|| structopt::clap::crate_name!()), shell, &mut ::std::io::stdout(), ); Ok(()) } /// Runs the `cat` command to print the contents fn run_cat() -> Result<()> { let ignore_file = Gitignore::from_default_path()?; let mut buf = String::new(); ignore_file.contents(&mut buf)?; println!("{}", buf); Ok(()) } /// The actual `main()` pub fn main(opts: CliOpts) -> Result<()> { match opts.cmd { Command::Add { glob, local } => run_add(glob, local)?, Command::Get { lang, append } => run_get(&lang, append)?, Command::ListTemplates => run_list_templates()?, Command::DumpCompletions { shell } => run_dump_completion(shell)?, Command::Cat => run_cat()?, }; Ok(()) } // Tests go below the code where they'll be out of the way when not the target of attention #[cfg(test)] mod tests { // TODO: Unit test to verify that the doc comment on `CliOpts` isn't overriding the intended // about string. #[test] /// Test something fn test_something() { // TODO: Test something } }