dfs 0.1.1

Dotfiles synchronizer
//! # Dotfiles synchronizer
//!
//! [![GitHub](https://img.shields.io/badge/github-FedericoStra/dfs-master?logo=github)](https://github.com/FedericoStra/dfs)
//! [![Crates.io](https://img.shields.io/crates/v/dfs?logo=rust)](https://crates.io/crates/dfs)
//! [![docs.rs](https://img.shields.io/docsrs/dfs?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNNDg4LjYgMjUwLjJMMzkyIDIxNFYxMDUuNWMwLTE1LTkuMy0yOC40LTIzLjQtMzMuN2wtMTAwLTM3LjVjLTguMS0zLjEtMTcuMS0zLjEtMjUuMyAwbC0xMDAgMzcuNWMtMTQuMSA1LjMtMjMuNCAxOC43LTIzLjQgMzMuN1YyMTRsLTk2LjYgMzYuMkM5LjMgMjU1LjUgMCAyNjguOSAwIDI4My45VjM5NGMwIDEzLjYgNy43IDI2LjEgMTkuOSAzMi4ybDEwMCA1MGMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAzLjktNTIgMTAzLjkgNTJjMTAuMSA1LjEgMjIuMSA1LjEgMzIuMiAwbDEwMC01MGMxMi4yLTYuMSAxOS45LTE4LjYgMTkuOS0zMi4yVjI4My45YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43ek0zNTggMjE0LjhsLTg1IDMxLjl2LTY4LjJsODUtMzd2NzMuM3pNMTU0IDEwNC4xbDEwMi0zOC4yIDEwMiAzOC4ydi42bC0xMDIgNDEuNC0xMDItNDEuNHYtLjZ6bTg0IDI5MS4xbC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnptMjQwIDExMmwtODUgNDIuNXYtNzkuMWw4NS0zOC44djc1LjR6bTAtMTEybC0xMDIgNDEuNC0xMDItNDEuNHYtLjZsMTAyLTM4LjIgMTAyIDM4LjJ2LjZ6IiBzdHlsZT0iZmlsbDojZmZmZmZmIj48L3BhdGg+PC9zdmc+Cg==)](https://docs.rs/dfs)
//! [![MIT license](https://img.shields.io/crates/l/dfs)](https://choosealicense.com/licenses/mit/)
//! [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/FedericoStra/dfs/Rust)](https://github.com/FedericoStra/dfs/actions/workflows/rust.yml)
//! ![Lines of code](https://tokei.rs/b1/github/FedericoStra/dfs?category=code)
//!
//! [`dfs`]() is a CLI utility to synchronize your dotfiles.
//!
//! It supports managing several profiles with operations such as
//! add, remove, update, link, apply...
//!
//! You can access the help for the subcommands by executing:
//! ```bash
//! dfs help <SUBCOMMAND>
//! ```

use clap::{AppSettings, Parser, Subcommand};

use env_logger;
use log::{error, trace};

#[derive(Debug, Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(global_setting(AppSettings::PropagateVersion))]
#[clap(global_setting(AppSettings::UseLongFormatForHelpSubcommand))]
struct Cli {
    #[clap(subcommand)]
    command: CliCommand,

    #[clap(short, long)]
    verbose: bool,
}

#[derive(Debug, Subcommand)]
enum CliCommand {
    /// Show or edit the configuration
    Config {
        #[clap(subcommand)]
        command: ConfigCommand,
    },

    /// Show current status
    Status,

    /// Manage profiles
    Profile {
        #[clap(subcommand)]
        command: ProfileCommand,
    },
}

#[derive(Debug, Subcommand)]
enum ConfigCommand {
    /// Initialize a new default configuration
    Init,

    /// Show the current configuration
    Show,
}

#[derive(Debug, Subcommand)]
enum ProfileCommand {
    /// List the available profiles
    List,

    /// Select a profile
    Select {
        /// Apply the profile
        #[clap(short, long)]
        apply: bool,

        /// Profile to select
        name: String,
    },

    /// Remove a profile
    Remove {
        /// Profile to remove
        name: String,
    },
}

mod config;
mod dirs;
mod profile;

fn main() {
    let cli = Cli::parse();

    set_up_env_logger(cli.verbose);
    trace!("env_logger set up");

    trace!("cli = {cli:#?}");

    if let Err(err) = run(cli) {
        error!("{err:?}");
        std::process::exit(1);
    }
}

fn run(cli: Cli) -> anyhow::Result<()> {
    let dirs = dirs::project_dirs()?;

    match cli.command {
        CliCommand::Config { command } => config::run(command, &dirs),
        CliCommand::Status => todo!(),
        CliCommand::Profile { command } => profile::run(command, &dirs),
    }
}

fn set_up_env_logger(verbose: bool) {
    use std::io::Write;
    env_logger::Builder::new()
        .filter_level(if verbose {
            log::LevelFilter::Info
        } else {
            log::LevelFilter::Warn
        })
        .format_timestamp(None)
        .format_module_path(false)
        .format(|buf, record| {
            use env_logger::fmt::Color::*;
            use log::Level::*;

            let mut gray = buf.style();
            gray.set_color(Black).set_intense(true);

            let mut level_style = buf.style();
            match record.level() {
                Error => level_style.set_color(Red).set_intense(true),
                Warn => level_style.set_color(Yellow).set_intense(true),
                Info => level_style.set_color(Green).set_intense(true),
                Debug => level_style.set_color(Magenta).set_intense(true),
                Trace => level_style.set_color(Black).set_intense(true),
            };

            let message_style = if record.level() == Info {
                buf.style()
            } else {
                level_style.clone()
            };

            writeln!(
                buf,
                "{}{level:>5}{} {message}",
                gray.value('['),
                gray.value(']'),
                level = level_style.value(record.level()),
                message = message_style.value(record.args())
            )
        })
        .parse_default_env()
        .init();
}