/*! 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 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, } /// 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 { 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> { let mut cwd: Option = 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 { 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 { 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) }