1use std::io::IsTerminal;
2
3use clap::{arg, Parser, Subcommand};
4use color_eyre::eyre::{Error, Result};
5use sha2::{Digest, Sha256};
6use tracing::debug;
7
8#[derive(Parser, Debug)]
9#[command(name = "distrans")]
10#[command(bin_name = "distrans")]
11pub struct Cli {
12 #[arg(long, env)]
13 pub no_ui: bool,
14
15 #[arg(long, env)]
16 pub state_dir: Option<String>,
17
18 #[command(subcommand)]
19 pub commands: Commands,
20}
21
22impl Cli {
23 pub fn no_ui(&self) -> bool {
24 return self.no_ui || !std::io::stdout().is_terminal();
25 }
26
27 pub fn state_dir(&self) -> Result<String> {
28 if let Some(s) = &self.state_dir {
29 return Ok(s.to_owned());
30 }
31 match self.commands {
32 Commands::Fetch {
33 ref dht_key,
34 ref root,
35 } => self.state_dir_for(format!("get:{}:{}", dht_key, root)),
36 Commands::Seed { ref file } => self.state_dir_for(format!("seed:{}", file.to_owned())),
37 _ => Err(Error::msg("invalid command")),
38 }
39 }
40
41 pub fn state_dir_for(&self, key: String) -> Result<String> {
42 let mut key_digest = Sha256::new();
43 key_digest.update(&key.as_bytes());
44 let key_digest_bytes: [u8; 32] = key_digest.finalize().into();
45 let dir_name = hex::encode(key_digest_bytes);
46 let data_dir = dirs::state_dir()
47 .or(dirs::data_local_dir())
48 .ok_or(Error::msg("cannot resolve state dir"))?;
49 let state_dir = data_dir
50 .join("distrans")
51 .join(dir_name)
52 .into_os_string()
53 .into_string()
54 .map_err(|os| Error::msg(format!("{:?}", os)))?;
55 debug!(state_dir);
56 Ok(state_dir)
57 }
58
59 pub fn version(&self) -> bool {
60 if let Commands::Version = self.commands {
61 return true;
62 }
63 return false;
64 }
65}
66
67#[derive(Subcommand, Debug)]
68pub enum Commands {
69 Fetch {
70 dht_key: String,
71 #[arg(default_value = ".")]
72 root: String,
73 },
74 Seed {
75 file: String,
76 },
77 Version,
78}