use std::{error::Error, path::PathBuf, process::exit};
use clap::Parser;
use directories_next::ProjectDirs;
use log::info;
use sopass::{
cmd,
config::{Config, ConfigBuilder},
LeafCommand,
};
const QUAL: &str = "";
const ORG: &str = "";
const APP: &str = "sopass";
fn main() {
if let Err(err) = fallible_main() {
eprintln!("ERROR: {err}");
let mut err = err.source();
while let Some(underlying) = err {
eprintln!("caused by: {underlying}");
err = underlying.source();
}
exit(1);
}
}
fn fallible_main() -> Result<(), SopassError> {
let dirs = directories()?;
let args = Args::parse();
env_logger::init_from_env("SOPASS_LOG");
let config = args.config(&dirs)?;
info!("sopass starts");
info!("store is {}", config.store().display());
info!("main command start");
match &args.cmd {
Command::Cert(x) => x.run(&config)?,
Command::Config(x) => x.run(&config)?,
Command::Export(x) => x.run(&config)?,
Command::Import(x) => x.run(&config)?,
Command::Init(x) => x.run(&config)?,
Command::Key(x) => x.run(&config)?,
Command::Value(x) => x.run(&config)?,
Command::Version(x) => x.run(&config)?,
}
info!("main command OK");
Ok(())
}
fn directories() -> Result<ProjectDirs, SopassError> {
ProjectDirs::from(QUAL, ORG, APP).ok_or(SopassError::Dirs)
}
#[derive(Debug, Parser)]
#[command(version)]
struct Args {
#[clap(long)]
config: Option<PathBuf>,
#[clap(long)]
store: Option<PathBuf>,
#[clap(long)]
sop: Option<PathBuf>,
#[clap(subcommand)]
cmd: Command,
}
impl Args {
fn config(&self, dirs: &ProjectDirs) -> Result<Config, SopassError> {
let mut builder = ConfigBuilder::new(APP, dirs.data_dir());
if let Some(filename) = &self.config {
builder.filename(filename);
}
if let Some(filename) = &self.store {
builder.store(filename);
}
if let Some(filename) = &self.sop {
builder.sop(filename);
}
Ok(builder.build()?)
}
}
#[derive(Debug, Parser)]
enum Command {
Cert(CertCommand),
Config(cmd::ConfigCommand),
Export(cmd::ExportCommand),
Import(cmd::ImportCommand),
Init(cmd::InitCommand),
Key(KeyCommand),
Value(ValueCommand),
Version(cmd::VersionCommand),
}
#[derive(Debug, Parser)]
struct CertCommand {
#[clap(subcommand)]
cmd: CertSubcommand,
}
impl CertCommand {
fn run(&self, config: &Config) -> Result<(), SopassError> {
match &self.cmd {
CertSubcommand::Add(x) => x.run(config)?,
CertSubcommand::Remove(x) => x.run(config)?,
CertSubcommand::List(x) => x.run(config)?,
}
Ok(())
}
}
#[derive(Debug, Parser)]
enum CertSubcommand {
Add(cmd::AddCert),
Remove(cmd::RemoveCert),
List(cmd::ListCerts),
}
#[derive(Debug, Parser)]
struct KeyCommand {
#[clap(subcommand)]
cmd: KeySubcommand,
}
impl KeyCommand {
fn run(&self, config: &Config) -> Result<(), SopassError> {
match &self.cmd {
KeySubcommand::Extract(x) => x.run(config)?,
}
Ok(())
}
}
#[derive(Debug, Parser)]
enum KeySubcommand {
Extract(cmd::ExtractCert),
}
#[derive(Debug, Parser)]
struct ValueCommand {
#[clap(subcommand)]
cmd: ValueSubcommand,
}
impl ValueCommand {
fn run(&self, config: &Config) -> Result<(), SopassError> {
info!("value command start");
match &self.cmd {
ValueSubcommand::Add(x) => x.run(config)?,
ValueSubcommand::List(x) => x.run(config)?,
ValueSubcommand::Remove(x) => x.run(config)?,
ValueSubcommand::Rename(x) => x.run(config)?,
ValueSubcommand::Show(x) => x.run(config)?,
}
info!("value command OK");
Ok(())
}
}
#[derive(Debug, Parser)]
enum ValueSubcommand {
Add(cmd::AddValue),
List(cmd::ListValues),
Remove(cmd::RemoveValue),
Rename(cmd::RenameValue),
Show(cmd::ShowValue),
}
#[derive(Debug, thiserror::Error)]
enum SopassError {
#[error("failed to determine project directories")]
Dirs,
#[error(transparent)]
Configuration(#[from] sopass::config::ConfigError),
#[error(transparent)]
Cert(#[from] cmd::CertError),
#[error(transparent)]
Config(#[from] cmd::ConfigError),
#[error(transparent)]
Export(#[from] cmd::ExportError),
#[error(transparent)]
Init(#[from] cmd::InitError),
#[error(transparent)]
Key(#[from] cmd::KeyError),
#[error(transparent)]
Value(#[from] cmd::ValueError),
#[error(transparent)]
Version(#[from] cmd::VersionError),
}