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 tracing::Level; use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, EnvFilter}; use crate::{ config_command::{ConfigCommand, ConfigCommandHandler}, doctor_command::DoctorCommandHandler, 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)?; let gitea_client = self.get_gitea_client(&config); let filter = match self.global.log_level { cuddle_please_misc::LogLevel::None => None, cuddle_please_misc::LogLevel::Trace => Some(Level::TRACE), cuddle_please_misc::LogLevel::Debug => Some(Level::DEBUG), cuddle_please_misc::LogLevel::Info => Some(Level::INFO), cuddle_please_misc::LogLevel::Error => Some(Level::ERROR), }; if let Some(filter) = filter { let env_filter = EnvFilter::builder().with_regex(false).parse(format!( "{},hyper=error,reqwest=error,git_cliff_core=error", filter ))?; tracing_subscriber::fmt().with_env_filter(env_filter).init(); } match &self.commands { Some(Commands::Release {}) => { ReleaseCommandHandler::new(self.ui, 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 {}) => { DoctorCommandHandler::new(self.ui).execute()?; } 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, config: &PleaseConfig) -> anyhow::Result { if self.global.no_vcs { Ok(VcsClient::new_noop()) } else { VcsClient::new_git( config.get_source(), config.settings.git_username.clone(), config.settings.git_email.clone(), ) } } fn get_gitea_client(&self, config: &PleaseConfig) -> DynRemoteGitClient { match self.global.engine { cuddle_please_misc::RemoteEngine::Local => Box::new(LocalGitClient::new()), cuddle_please_misc::RemoteEngine::Gitea => Box::new(GiteaClient::new( config.get_api_url(), 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) }