mod forge; mod refresh; mod ui; use std::io::{self, IsTerminal}; use std::path::PathBuf; use anyhow::{Result, bail}; use clap::{Parser, Subcommand}; use refresh::Refresh; #[derive(Parser)] #[command(name = "sorcery-ssh")] struct Cli { #[arg(long, env = "SORCERY_REPOSITORIES", value_name = "DIR")] repositories: PathBuf, #[arg(long, env = "SORCERY_SOCKET", value_name = "PATH")] socket: Option, #[arg(long, env = "SORCERY_REFRESH_TOKEN_FILE", value_name = "PATH")] refresh_token_file: Option, #[arg(long, env = "SORCERY_CLONE_URL_BASE", value_name = "URL")] clone_url_base: Option, #[arg(long, env = "SORCERY_INSTANCE_NAME", value_name = "NAME")] instance_name: Option, #[command(subcommand)] command: Option, } #[derive(Subcommand)] enum Command { /// Print or set a repository's description Describe { repo: String, #[arg( value_name = "TEXT", trailing_var_arg = true, allow_hyphen_values = true )] text: Vec, }, } fn main() -> Result<()> { let cli = Cli::parse(); let refresh = Refresh::from_files(cli.socket, cli.refresh_token_file)?; match cli.command { Some(Command::Describe { repo, text }) => { let repo = forge::find(&cli.repositories, &repo)?; if text.is_empty() { if let Some(description) = repo.description { println!("{description}"); } } else { let text = text.join(" "); forge::set_description(&repo, &text)?; if let Some(refresh) = &refresh && let Err(error) = refresh.send(&repo.user, &repo.name) { eprintln!("sorcery-ssh: description saved, but refresh failed: {error:#}"); } println!("{}", text.trim()); } Ok(()) } None => { if !io::stdin().is_terminal() || !io::stdout().is_terminal() { bail!("interactive session needs a terminal; try `ssh -t`"); } let title = cli .instance_name .unwrap_or_else(|| cli.repositories.display().to_string()); ui::run(cli.repositories, title, cli.clone_url_base, refresh) } } } #[cfg(test)] mod tests { use super::*; #[test] fn description_consumes_the_remaining_ssh_command() { let cli = Cli::try_parse_from([ "sorcery-ssh", "--repositories", "/tmp/repos", "describe", "alice/repo", "a", "tiny", "repo", ]) .unwrap(); let Some(Command::Describe { repo, text }) = cli.command else { panic!("expected describe command"); }; assert_eq!(repo, "alice/repo"); assert_eq!(text, ["a", "tiny", "repo"]); } }