garbage 0.4.3

CLI tool for interacting with the freedesktop trashcan
Documentation
#![deny(warnings)]

#[macro_use]
extern crate tracing;

use std::{io, process::exit};

use anyhow::Result;
use clap::{crate_name, crate_version, ArgAction, CommandFactory, Parser};
use clap_complete::Shell;
use garbage::ops::{
  self, EmptyOptions, ListOptions, PutOptions, RestoreOptions,
};
use garbage_fs::{stdfs::StdFilesystem, Filesystem};
use tracing::level_filters::LevelFilter;

#[derive(Parser)]
#[clap(about, version = concat!(crate_version!(), "-", env!("GARBAGE_VERSION")), author)]
struct Opt {
  #[clap(subcommand)]
  command: Command,

  /// Verbosity (-v, -vv, -vvv, etc)
  #[clap(global = true, short = 'v', long = "verbose", action = ArgAction::Count)]
  verbose: u8,

  /// Quiet mode (suppress all output)
  #[clap(global = true, short = 'q', long = "quiet")]
  quiet: bool,
}

#[derive(Parser)]
enum Command {
  /// Empty a trash directory.
  #[clap(name = "empty")]
  Empty(EmptyOptions),

  /// List the contents of a trash directory.
  #[clap(name = "list")]
  List(ListOptions),

  /// Puts files into the trash. (also 'garbage rm')
  ///
  /// If a trash directory isn't specified, the best strategy is picked
  /// for each file that's deleted (after shell glob expansion). The
  /// algorithm for deciding a strategy is specified in the FreeDesktop
  /// Trash spec.
  #[clap(name = "put", alias = "rm")]
  Put(PutOptions),

  /// Restores files from the trash.
  #[clap(name = "restore")]
  Restore(RestoreOptions),

  /// Generate a completion script to stdout.
  ///
  /// E.g. `garbage generate-completions bash > ~/.local/share/bash-completion/completions/garbage`
  #[clap(name = "generate-completions")]
  Complete {
    /// The type of shell.
    #[clap(required = true, value_name = "SHELL_TYPE")]
    shell_type: Shell,
  },
}

fn run(cmd: Command, fs: &impl Filesystem) -> Result<()> {
  match cmd {
    Command::Empty(options) => ops::empty(options, fs),
    Command::List(options) => ops::list(options, fs),
    Command::Put(options) => ops::put(options, fs),
    Command::Restore(options) => ops::restore(options, fs),
    Command::Complete { shell_type } => {
      clap_complete::generate(
        shell_type,
        &mut Command::command(),
        crate_name!(),
        &mut std::io::stdout(),
      );
      Ok(())
    }
  }
}

fn convert_verbosity(opt: &Opt) -> LevelFilter {
  if opt.quiet {
    return LevelFilter::OFF;
  }

  match opt.verbose {
    0 => LevelFilter::WARN,
    1 => LevelFilter::INFO,
    2 => LevelFilter::DEBUG,
    _ => LevelFilter::TRACE,
  }
}

fn main() -> Result<()> {
  let fs = StdFilesystem::default();
  let opt = Opt::parse();

  let fmt = tracing_subscriber::fmt()
    .with_ansi(atty::is(atty::Stream::Stderr))
    .with_max_level(convert_verbosity(&opt))
    .with_writer(io::stderr);

  if opt.verbose < 2 {
    fmt
      .without_time()
      .with_file(false)
      .with_target(false)
      .init();
  } else {
    fmt.init();
  }

  match run(opt.command, &fs) {
    Ok(_) => Ok(()),
    Err(err) => {
      debug!("{}", err);
      debug!("{:?}", err);
      debug!("{}", err.backtrace());
      exit(1);
    }
  }
}