char/sorcery

static-files based git repo viewer

git clone https://git.t4t.associates/char/sorcery

Charlotte Somadd sorcery-ssh with sorcery-ssh-tui8f07a0e

main
2.9 KiB102 linesraw
1mod forge;
2mod refresh;
3mod ui;
4
5use std::io::{self, IsTerminal};
6use std::path::PathBuf;
7
8use anyhow::{Result, bail};
9use clap::{Parser, Subcommand};
10
11use refresh::Refresh;
12
13#[derive(Parser)]
14#[command(name = "sorcery-ssh")]
15struct Cli {
16    #[arg(long, env = "SORCERY_REPOSITORIES", value_name = "DIR")]
17    repositories: PathBuf,
18    #[arg(long, env = "SORCERY_SOCKET", value_name = "PATH")]
19    socket: Option<PathBuf>,
20    #[arg(long, env = "SORCERY_REFRESH_TOKEN_FILE", value_name = "PATH")]
21    refresh_token_file: Option<PathBuf>,
22    #[arg(long, env = "SORCERY_CLONE_URL_BASE", value_name = "URL")]
23    clone_url_base: Option<String>,
24    #[arg(long, env = "SORCERY_INSTANCE_NAME", value_name = "NAME")]
25    instance_name: Option<String>,
26    #[command(subcommand)]
27    command: Option<Command>,
28}
29
30#[derive(Subcommand)]
31enum Command {
32    /// Print or set a repository's description
33    Describe {
34        repo: String,
35        #[arg(
36            value_name = "TEXT",
37            trailing_var_arg = true,
38            allow_hyphen_values = true
39        )]
40        text: Vec<String>,
41    },
42}
43
44fn main() -> Result<()> {
45    let cli = Cli::parse();
46    let refresh = Refresh::from_files(cli.socket, cli.refresh_token_file)?;
47
48    match cli.command {
49        Some(Command::Describe { repo, text }) => {
50            let repo = forge::find(&cli.repositories, &repo)?;
51            if text.is_empty() {
52                if let Some(description) = repo.description {
53                    println!("{description}");
54                }
55            } else {
56                let text = text.join(" ");
57                forge::set_description(&repo, &text)?;
58                if let Some(refresh) = &refresh
59                    && let Err(error) = refresh.send(&repo.user, &repo.name)
60                {
61                    eprintln!("sorcery-ssh: description saved, but refresh failed: {error:#}");
62                }
63                println!("{}", text.trim());
64            }
65            Ok(())
66        }
67        None => {
68            if !io::stdin().is_terminal() || !io::stdout().is_terminal() {
69                bail!("interactive session needs a terminal; try `ssh -t`");
70            }
71            let title = cli
72                .instance_name
73                .unwrap_or_else(|| cli.repositories.display().to_string());
74            ui::run(cli.repositories, title, cli.clone_url_base, refresh)
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn description_consumes_the_remaining_ssh_command() {
85        let cli = Cli::try_parse_from([
86            "sorcery-ssh",
87            "--repositories",
88            "/tmp/repos",
89            "describe",
90            "alice/repo",
91            "a",
92            "tiny",
93            "repo",
94        ])
95        .unwrap();
96        let Some(Command::Describe { repo, text }) = cli.command else {
97            panic!("expected describe command");
98        };
99        assert_eq!(repo, "alice/repo");
100        assert_eq!(text, ["a", "tiny", "repo"]);
101    }
102}