forgejo-cli/src/main.rs

140 lines
3.9 KiB
Rust
Raw Normal View History

2023-07-11 18:43:19 +02:00
use clap::{Parser, Subcommand};
2024-04-18 23:47:22 +02:00
use eyre::{eyre, Context, OptionExt};
2023-07-11 18:43:19 +02:00
use tokio::io::AsyncWriteExt;
2023-08-17 23:56:23 +02:00
mod keys;
use keys::*;
2023-11-17 20:18:48 +01:00
mod auth;
2023-12-11 05:59:04 +01:00
mod issues;
2023-12-17 01:42:38 +01:00
mod release;
2023-11-17 20:25:35 +01:00
mod repo;
2023-11-17 20:18:48 +01:00
2023-07-11 18:43:19 +02:00
#[derive(Parser, Debug)]
pub struct App {
#[clap(long, short = 'H')]
host: Option<String>,
2023-07-11 18:43:19 +02:00
#[clap(subcommand)]
command: Command,
}
#[derive(Subcommand, Clone, Debug)]
pub enum Command {
#[clap(subcommand)]
2023-11-17 20:18:48 +01:00
Repo(repo::RepoCommand),
2023-12-11 05:59:04 +01:00
Issue(issues::IssueCommand),
2024-04-19 00:53:36 +02:00
#[command(name = "whoami")]
WhoAmI {
2023-07-11 18:43:19 +02:00
#[clap(long, short)]
remote: Option<String>,
2023-07-11 18:43:19 +02:00
},
#[clap(subcommand)]
2023-11-17 20:18:48 +01:00
Auth(auth::AuthCommand),
2023-12-17 01:42:38 +01:00
Release(release::ReleaseCommand),
2023-07-11 18:43:19 +02:00
}
#[tokio::main]
async fn main() -> eyre::Result<()> {
let args = App::parse();
let mut keys = KeyInfo::load().await?;
let host_name = args.host.as_deref();
// let remote = repo::RepoInfo::get_current(host_name, remote_name)?;
2023-07-11 18:43:19 +02:00
match args.command {
Command::Repo(subcommand) => subcommand.run(&keys, host_name).await?,
Command::Issue(subcommand) => subcommand.run(&keys, host_name).await?,
2024-04-19 00:53:36 +02:00
Command::WhoAmI { remote } => {
2024-04-17 21:58:45 +02:00
let url = repo::RepoInfo::get_current(host_name, None, remote.as_deref())
.wrap_err("could not find host, try specifying with --host")?
.host_url()
.clone();
2023-11-17 20:50:08 +01:00
let name = keys.get_login(&url)?.username();
2024-04-18 23:47:22 +02:00
let host = url
.host_str()
.ok_or_eyre("instance url does not have host")?;
if url.path() == "/" || url.path().is_empty() {
println!("currently signed in to {name}@{host}");
} else {
println!("currently signed in to {name}@{host}{}", url.path());
}
}
2023-12-11 05:59:04 +01:00
Command::Auth(subcommand) => subcommand.run(&mut keys).await?,
Command::Release(subcommand) => subcommand.run(&mut keys, host_name).await?,
2023-07-11 18:43:19 +02:00
}
keys.save().await?;
Ok(())
}
async fn readline(msg: &str) -> eyre::Result<String> {
print!("{msg}");
tokio::io::stdout().flush().await?;
tokio::task::spawn_blocking(|| {
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
Ok(input)
})
.await?
}
2023-12-11 05:59:04 +01:00
async fn editor(contents: &mut String, ext: Option<&str>) -> eyre::Result<()> {
2023-12-13 06:30:47 +01:00
let editor = std::path::PathBuf::from(
std::env::var_os("EDITOR").ok_or_else(|| eyre!("unable to locate editor"))?,
);
2023-12-13 02:02:37 +01:00
2023-12-11 05:59:04 +01:00
let (mut file, path) = tempfile(ext).await?;
file.write_all(contents.as_bytes()).await?;
drop(file);
// Closure acting as a try/catch block so that the temp file is deleted even
// on errors
let res = (|| async {
eprint!("waiting on editor\r");
2023-12-13 06:25:28 +01:00
let flags = get_editor_flags(&editor);
2023-12-11 05:59:04 +01:00
let status = tokio::process::Command::new(editor)
2023-12-13 06:25:28 +01:00
.args(flags)
2023-12-11 05:59:04 +01:00
.arg(&path)
.status()
.await?;
if !status.success() {
eyre::bail!("editor exited unsuccessfully");
}
*contents = tokio::fs::read_to_string(&path).await?;
eprint!(" \r");
Ok(())
2023-12-13 02:02:37 +01:00
})()
.await;
2023-12-11 05:59:04 +01:00
tokio::fs::remove_file(path).await?;
res?;
Ok(())
}
2023-12-13 06:25:28 +01:00
fn get_editor_flags(editor_path: &std::path::Path) -> &'static [&'static str] {
let editor_name = match editor_path.file_stem().and_then(|s| s.to_str()) {
Some(name) => name,
None => return &[],
};
if editor_name == "code" {
return &["--wait"];
}
&[]
}
2023-12-11 05:59:04 +01:00
async fn tempfile(ext: Option<&str>) -> tokio::io::Result<(tokio::fs::File, std::path::PathBuf)> {
let filename = uuid::Uuid::new_v4();
let mut path = std::env::temp_dir().join(filename.to_string());
if let Some(ext) = ext {
path.set_extension(ext);
}
let file = tokio::fs::OpenOptions::new()
.create(true)
.read(true)
.write(true)
.open(&path)
.await?;
Ok((file, path))
}