You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
6.8 KiB
207 lines
6.8 KiB
/*! 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 `<name>` `<version>` 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 `<name>
|
|
/// <version>` 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<String>,
|
|
/// 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<structopt::clap::Shell>,
|
|
},
|
|
/// Print current .gitignore to stdout
|
|
Cat,
|
|
}
|
|
|
|
/// Runs the command `add`
|
|
fn run_add(glob: Vec<String>, 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<structopt::clap::Shell>) -> 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
|
|
}
|
|
}
|