gitig 0.3.0

A cli utility to manage gitignore files easily
/*! Functions and templates which can be imported by app.rs to save effort */
// Copyright 2017-2019, Stephan Sokolow

// FIXME: Report that StructOpt is tripping Clippy's `result_unwrap_used` lint (which I use to push
// for .expect() instead) in my two Option<T> fields and the `allow` gets ignored unless I
// `#![...]` it onto the entire module.
#![allow(clippy::result_unwrap_used)]
use log::{info, trace};

use crate::cache::File as FileCache;
use crate::errors::*;
use crate::{helpers, template::GithubTemplates};

use directories::ProjectDirs;
use std::{env, path::PathBuf};
use structopt::StructOpt;

/// Modified version of Clap's default template for proper help2man compatibility
///
/// Used as a workaround for:
/// 1. Clap's default template interfering with `help2man`'s proper function
///    ([clap-rs/clap/#1432](https://github.com/clap-rs/clap/issues/1432))
/// 2. Workarounds involving injecting `\n` into the description breaking help output if used
///    on subcommand descriptions.
pub const HELP_TEMPLATE: &str = "{bin} {version}

{about}

USAGE:
    {usage}

{all-args}
";

/// Options used by boilerplate code
#[derive(StructOpt, Debug)]
#[structopt(rename_all = "kebab-case")]
pub struct BoilerplateOpts {
    /// Decrease verbosity (-q, -qq, -qqq, etc.)
    #[structopt(short, long, parse(from_occurrences))]
    pub quiet: u64,

    /// Increase verbosity (-v, -vv, -vvv, etc.)
    #[structopt(short, long, parse(from_occurrences))]
    pub verbose: u64,

    /// Display timestamps on log messages (sec, ms, ns, none)
    #[structopt(short, long, value_name = "resolution")]
    pub timestamp: Option<stderrlog::Timestamp>,
}

/// Checks if the given dir contains the root `.git` dir or file
///
/// Submodules do not contain a complete .git directory, but only a .git file with a reference to
/// the actual folder. This function takes both into account.
///
/// # Errors
///
/// Returns an `Err` if any of the fs related methods return an error
fn has_git_dir(path: &PathBuf) -> Result<bool> {
    trace!("Checking for git root in {:?}", &path);
    let mut git: PathBuf = path.clone();
    git.push(".git");
    for entry in path.read_dir().chain_err(|| "Reading contents of dir")? {
        if let Ok(entry) = entry {
            // check if this is `.git`
            if entry.path() == git {
                return Ok(true);
            }
        }
    }
    Ok(false)
}

/// Returns the root git directory for the current directory if there is one
///
/// # Errors
///
/// Returns an [`Err`](std::Err) if the current cwd is invalid (refer to
/// [`current_dir`](std::env::current_dir))
pub fn git_dir() -> Result<Option<PathBuf>> {
    let mut cwd: Option<PathBuf> =
        Some(std::env::current_dir().chain_err(|| "Error with current dir")?);
    while cwd.is_some() {
        let c = cwd.ok_or("Should not have been none, as checked before in if")?;
        if has_git_dir(&c)? {
            return Ok(Some(c));
        }
        cwd = c.parent().map(PathBuf::from);
    }

    info!("Arrived at filesystem root while checking for git folder");
    Ok(None)
}

/// Returns a `PathBuf` to the cache root for this project
///
/// This will be either the appropriate cache dir according to XDG or as a fallback `/tmp`.
pub fn cache_root() -> PathBuf {
    match ProjectDirs::from("org", "webschneider", env!("CARGO_PKG_NAME")) {
        Some(dirs) => PathBuf::from(dirs.cache_dir()),
        None => "/tmp".into(),
    }
}

/// Returns a default file cache
pub fn default_cache() -> Result<FileCache> {
    let cache_root = crate::helpers::cache_root();
    FileCache::new(&cache_root, std::time::Duration::from_secs(60 * 24 * 2))
}

/// Returns a `GithubTemplates` struct with all available templates
pub fn get_templates() -> Result<GithubTemplates> {
    let cache = helpers::default_cache().chain_err(|| "Error while creating cache")?;
    let tmpl = if cache.exists("templates.json") {
        cache.get("templates.json")?
    } else {
        let tmpls = GithubTemplates::new().chain_err(|| "Error while getting Templates")?;
        cache
            .set("templates.json", &tmpls)
            .chain_err(|| "Error while writing templates to cache")?;
        tmpls
    };
    Ok(tmpl)
}