use std::{ io::Read, ops::Deref, path::{Path, PathBuf}, sync::{Arc, Mutex}, }; use clap::{Parser, Subcommand}; use cuddle_please_frontend::{gatheres::ConfigArgs, PleaseConfig, PleaseConfigBuilder}; use cuddle_please_misc::{ ConsoleUi, DynRemoteGitClient, DynUi, GiteaClient, GlobalArgs, LocalGitClient, StdinFn, VcsClient, }; use crate::{ config_command::{ConfigCommand, ConfigCommandHandler}, gitea_command::{GiteaCommand, GiteaCommandHandler}, release_command::ReleaseCommandHandler, }; #[derive(Parser)] #[command(author, version, about, long_about = None)] pub struct Command { #[command(flatten)] global: GlobalArgs, #[command(flatten)] config: ConfigArgs, #[command(subcommand)] commands: Option, #[clap(skip)] ui: DynUi, #[clap(skip)] stdin: StdinFn, } impl Default for Command { fn default() -> Self { Self::new() } } impl Command { pub fn new() -> Self { let args = std::env::args(); Self::new_from_args_with_stdin(Some(ConsoleUi::default()), args, || { let mut input = String::new(); std::io::stdin().read_to_string(&mut input)?; Ok(input) }) } pub fn new_from_args(ui: Option, i: I) -> Self where I: IntoIterator, T: Into + Clone, UIF: Into, { let mut s = Self::parse_from(i); if let Some(ui) = ui { s.ui = ui.into(); } s } pub fn new_from_args_with_stdin(ui: Option, i: I, input: F) -> Self where I: IntoIterator, T: Into + Clone, F: Fn() -> anyhow::Result + Send + Sync + 'static, UIF: Into, { let mut s = Self::parse_from(i); if let Some(ui) = ui { s.ui = ui.into(); } s.stdin = Some(Arc::new(Mutex::new(input))); s } pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> { let config = self.build_config(current_dir)?; let git_client = self.get_git(config.get_source())?; let gitea_client = self.get_gitea_client(); match &self.commands { Some(Commands::Release {}) => { ReleaseCommandHandler::new(config, git_client, gitea_client) .execute(self.global.dry_run)?; } Some(Commands::Config { command }) => { ConfigCommandHandler::new(self.ui, config).execute(command)?; } Some(Commands::Gitea { command }) => { GiteaCommandHandler::new(self.ui, config, gitea_client) .execute(command, self.global.token.expect("token to be set").deref())?; } Some(Commands::Doctor {}) => { match std::process::Command::new("git").arg("-v").output() { Ok(o) => { let stdout = std::str::from_utf8(&o.stdout).unwrap_or(""); self.ui.write_str_ln(&format!("OK: {}", stdout)); } Err(e) => { self.ui .write_str_ln(&format!("WARNING: git is not installed: {}", e)); } } } None => {} } Ok(()) } fn build_config(&self, current_dir: Option<&Path>) -> Result { let mut builder = &mut PleaseConfigBuilder::new(); if self.global.config_stdin { if let Some(stdin_fn) = self.stdin.clone() { let output = (stdin_fn.lock().unwrap().deref())(); builder = builder.with_stdin(output?); } } let current_dir = get_current_path(current_dir, self.config.source.clone())?; let config = builder .with_config_file(¤t_dir) .with_source(¤t_dir) .with_execution_env(std::env::vars()) .with_cli(self.config.clone()) .build()?; Ok(config) } fn get_git(&self, current_dir: &Path) -> anyhow::Result { if self.global.no_vcs { Ok(VcsClient::new_noop()) } else { VcsClient::new_git(current_dir) } } fn get_gitea_client(&self) -> DynRemoteGitClient { match self.global.engine { cuddle_please_misc::RemoteEngine::Local => Box::new(LocalGitClient::new()), cuddle_please_misc::RemoteEngine::Gitea => Box::new(GiteaClient::new( &self.config.api_url.clone().expect("api_url to be set"), self.global.token.as_deref(), )), } } } #[derive(Debug, Clone, Subcommand)] pub enum Commands { /// Config is mostly used for debugging the final config output Release {}, #[command(hide = true)] Config { #[command(subcommand)] command: ConfigCommand, }, #[command(hide = true)] Gitea { #[command(subcommand)] command: GiteaCommand, }, /// Helps you identify missing things from your execution environment for cuddle-please to function as intended Doctor {}, } fn get_current_path( optional_current_dir: Option<&Path>, optional_source_path: Option, ) -> anyhow::Result { let path = optional_source_path .or_else(|| optional_current_dir.map(|p| p.to_path_buf())) // fall back on current env from environment .filter(|v| v.to_string_lossy() != "") // make sure we don't get empty values //.and_then(|p| p.canonicalize().ok()) // Make sure we get the absolute path //.context("could not find current dir, pass --source as a replacement")?; .unwrap_or(PathBuf::from(".")); if !path.exists() { anyhow::bail!("path doesn't exist {}", path.display()); } Ok(path) }