repo-backup 0.2.2

A program for backing up your GitHub and GitLab repos.
Documentation
extern crate chrono;
extern crate env_logger;
extern crate failure;
#[macro_use]
extern crate log;
extern crate repo_backup;
extern crate shellexpand;
extern crate structopt;

use std::env;
use std::io::{self, Write};

use chrono::Local;
use env_logger::Builder;
use failure::{Error, ResultExt};
use log::LevelFilter;
use repo_backup::{Config, Driver, UpdateFailure};
use structopt::StructOpt;

fn main() {
    let args = Args::from_args();

    if args.example_config {
        generate_example();
        return;
    }

    if let Err(e) = run(&args) {
        if let Some(outcome_failure) = e.downcast_ref::<UpdateFailure>() {
            let mut stderr = io::stderr();
            outcome_failure.display(&mut stderr).unwrap();
        } else {
            eprintln!("Error: {}", e);

            for cause in e.iter_chain().skip(1) {
                eprintln!("\tCaused By: {}", cause);
            }

            eprintln!("{}", e.backtrace());
        }
    }
}

fn generate_example() {
    let example = Config::example();

    println!("{}", example.as_toml());
}

fn run(args: &Args) -> Result<(), Error> {
    initialize_logging(args)?;
    let cfg = args.config()?;

    if log_enabled!(log::Level::Debug) {
        for line in format!("{:#?}", cfg).lines() {
            debug!("{}", line);
        }
    }

    let driver = Driver::with_config(cfg);

    driver.run()?;

    Ok(())
}

#[derive(Debug, Clone, PartialEq, StructOpt)]
struct Args {
    #[structopt(
        short = "c",
        long = "config",
        default_value = "~/.repo-backup.toml",
        help = "The configuration file to use."
    )]
    config_file: String,
    #[structopt(
        short = "v",
        long = "verbose",
        help = "Verbose output (repeat for more verbosity)",
        parse(from_occurrences)
    )]
    verbosity: u64,
    #[structopt(
        long = "example-config",
        help = "Generate an example config and immediately exit."
    )]
    example_config: bool,
}

impl Args {
    pub fn config(&self) -> Result<Config, Error> {
        let config_file = shellexpand::full(&self.config_file)
            .context("Unable to expand wildcards")?;

        Config::from_file(&*config_file)
            .context("Couldn't load the config")
            .map_err(Into::into)
    }
}

fn initialize_logging(args: &Args) -> Result<(), Error> {
    let mut builder = Builder::new();

    let level = match args.verbosity {
        0 => None,
        1 => Some(LevelFilter::Info),
        2 => Some(LevelFilter::Debug),
        _ => Some(LevelFilter::Trace),
    };

    if let Some(lvl) = level {
        builder.filter(Some("repo_backup"), lvl);
    }

    if let Ok(filter) = env::var("RUST_LOG") {
        builder.parse(&filter);
    }

    builder.format(|out, record| {
        match (record.line(), cfg!(debug_assertions)) {
            (Some(line), true) => writeln!(
                out,
                "{} [{:5}] ({}#{}): {}",
                Local::now().format("%Y-%m-%d %H:%M:%S"),
                record.level(),
                record.target(),
                line,
                record.args()
            ),
            _ => writeln!(
                out,
                "{} [{:5}]: {}",
                Local::now().format("%Y-%m-%d %H:%M:%S"),
                record.level(),
                record.args()
            ),
        }
    });

    builder.try_init()?;

    Ok(())
}