From 0d2ac7e1638436caddac06e713e88ae8d51b0a55 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 27 Jan 2024 23:26:49 +0100 Subject: [PATCH] a basic changelog interface --- .autocrate.yaml | 9 ++-- Cargo.toml | 6 +-- README.md | 2 +- src/changelog/mod.rs | 53 ++++++++++++++++++++++++ src/config/cli.rs | 59 ++++++++++++++++++++------- src/config/mod.rs | 97 +++++++++++++++++++++++++++++++++----------- src/error.rs | 6 +++ src/lib.rs | 1 + src/main.rs | 22 ++++++---- 9 files changed, 202 insertions(+), 53 deletions(-) create mode 100644 src/changelog/mod.rs diff --git a/.autocrate.yaml b/.autocrate.yaml index eb8ea98..9797223 100644 --- a/.autocrate.yaml +++ b/.autocrate.yaml @@ -1,3 +1,4 @@ +--- changelog: enable: true git-log: true @@ -5,10 +6,10 @@ changelog: uses: cargo: publish: true - # tokens are loaded from ~/.cargo/config.toml + # tokens are loaded from ~/.cargo/config.toml registries: - - crates.io - - example.com + - crates.io + - cscherr api: github: @@ -19,7 +20,7 @@ api: pass: token_superimportantsecret myserv: type: gitea - endpoint: https://git.example.com + endpoint: https://git.cscherr.de auth: user: myUserName pass: importantsecrettoken diff --git a/Cargo.toml b/Cargo.toml index 5495a59..88b92a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "autocrate" -version = "0.1.0-prealpha.0" +version = "0.1.0-prealpha.1" edition = "2021" publish = true authors = ["Christoph J. Scherr "] @@ -21,8 +21,8 @@ keywords = [ [dependencies] anyhow = "1.0.79" -clap = { version = "4.4.18", features = ["derive"] } -clap-num = "1.1.1" +cargo = "0.76.0" +clap = { version = "4.4.18", features = ["derive", "help"] } clap-verbosity-flag = "2.1.2" git2 = "0.18.1" libpt = { version = "0.3.11", features = ["log"] } diff --git a/README.md b/README.md index 7a8aab1..86f688d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Autocrate streamlines the release process, allowing developers to focus on their work. Although initially built for Gitea, we plan to extend support for additional platforms such as GitHub and GitLab. - * [Original Repository](https://git.cscherr.de/PlexSheep/Autocrate) * [GitHub Mirror](https://github.com/PlexSheep/Autocrate) * [crates.io](https://crates.io/crates/autocrate) @@ -83,6 +82,7 @@ repository. It should contain the following parameters (replace the placeholders | `api.NAME.auth` | `pass` | a string | A secret for authentication o the server, probably a token | An example `.autocrate.yaml` could look like this: + ```yaml changelog: enable: true diff --git a/src/changelog/mod.rs b/src/changelog/mod.rs new file mode 100644 index 0000000..c4900cf --- /dev/null +++ b/src/changelog/mod.rs @@ -0,0 +1,53 @@ +use std::fmt::Display; + +use crate::{ + config::{cli::Commands, Config}, + error::*, +}; + +/// Represents a changelog that is currently under construction. +#[derive(Clone, Debug)] +pub struct Changelog { + message: Option, + git_log: Option, +} + +impl Changelog { + pub fn build(cfg: &Config) -> Result { + if !cfg.yaml.changelog.enable { + return Err(ConfigError::IsDisabledButUsed("changelog").into()); + } + let message: Option = match cfg.cli.command.clone() { + Commands::Changelog { message } => match message { + Some(msgs) => Some(msgs.concat()), + None => None, + }, + }; + let git_log = Self::make_git_log(cfg)?; + Ok(Changelog { + message, + git_log, + }) + } + + fn make_git_log(cfg: &Config) -> Result> { + if !cfg.yaml.changelog.enable { + return Ok(None); + } + Ok(Some(format!("todo"))) + } +} + +impl Display for Changelog { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut full: String = String::new(); + full += "Changelog"; + if self.message.is_some() { + full += format!("\n\n{}", self.message.clone().unwrap()).as_str(); + } + if self.git_log.is_some() { + full += format!("\n\n{}", self.git_log.clone().unwrap()).as_str(); + } + write!(f, "{full}") + } +} diff --git a/src/config/cli.rs b/src/config/cli.rs index 9fff45f..a0c81f2 100644 --- a/src/config/cli.rs +++ b/src/config/cli.rs @@ -1,39 +1,68 @@ +use std::fmt::Display; + use libpt::log::{Level, Logger}; -use clap::Parser; +use clap::{Parser, Subcommand}; use clap_verbosity_flag::{InfoLevel, Verbosity}; -/// short about section displayed in help -const ABOUT_ROOT: &'static str = r##" -Release Manager for Your Projects on Gitea, GitHub, and GitLab. -"##; -/// longer about section displayed in help, is combined with [the short help](ABOUT_ROOT) -static LONG_ABOUT_ROOT: &'static str = r##""##; - #[derive(Debug, Clone, Parser)] #[command( author, version, - about = ABOUT_ROOT, - long_about = format!("{}{}", ABOUT_ROOT ,LONG_ABOUT_ROOT), - help_template = -r#"{about-section} + about, + long_about, + help_template = r#"{about-section} {usage-heading} {usage} {all-args}{tab} autocrate: {version} Author: {author-with-newline} "# - )] +)] +/// Release Manager for Your Projects on Gitea, GitHub, and GitLab. pub struct Cli { // clap_verbosity_flag seems to make this a global option implicitly /// set a verbosity, multiple allowed (f.e. -vvv) #[command(flatten)] - pub(crate) verbose: Verbosity, + pub verbose: Verbosity, /// show additional logging meta data #[arg(long)] - pub(crate) meta: bool, + pub meta: bool, + + /// the subcommands are part of this enum + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Debug, Clone, Subcommand)] +pub enum Commands { + Changelog { + // FIXME: allow taking a message like this: + // `autocrate changelog -m arg1 arg2 arg3` + // -> msg="arg1 arg2 arg3" + // Instead of only + // `autocrate changelog -m "arg1 arg2 arg3"` + // -> msg="arg1 arg2 arg3" + // + // TODO: + // Perhaps open the $EDITOR of the user if + // no message is provided, like git does + #[arg(short, long)] + message: Option>, + }, +} + +impl Display for Commands { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Changelog { .. } => "Changelog", + } + ) + } } impl Cli { diff --git a/src/config/mod.rs b/src/config/mod.rs index 45ac6aa..1695c33 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -10,54 +10,84 @@ use crate::error::*; pub mod cli; use cli::Cli; +pub trait YamlConfigSection: Debug + Clone + for<'a> Deserialize<'a> { + fn check(&self) -> Result<()>; +} + #[derive(Debug, Clone, Deserialize)] pub struct Changelog { - enable: bool, + pub enable: bool, #[serde(alias = "git-log")] - git_log: bool, + pub git_log: bool, +} +impl YamlConfigSection for Changelog { + fn check(&self) -> Result<()> { + Ok(()) + } +} + +#[derive(Debug, Clone, Deserialize)] +pub struct UseCargo { + publish: bool, + registries: Vec, +} +impl YamlConfigSection for UseCargo { + fn check(&self) -> Result<()> { + Ok(()) + } } #[derive(Debug, Clone, Deserialize)] pub struct Uses { - cargo: Option, + cargo: UseCargo, } - -#[derive(Debug, Clone, Deserialize)] -pub struct Cargo { - publish: bool, - registries: Vec, +impl YamlConfigSection for Uses { + fn check(&self) -> Result<()> { + self.cargo.check()?; + Ok(()) + } } #[derive(Debug, Clone, Deserialize)] pub struct ApiAuth { - user: String, - pass: Option, - pass_file: Option, + pub user: String, + pub pass: Option, + pub pass_file: Option, } - -impl ApiAuth { - pub fn check(&self) -> Result<()> { +impl YamlConfigSection for ApiAuth { + fn check(&self) -> Result<()> { if self.pass.is_some() && self.pass_file.is_some() { let err = ConfigError::YamlApiAuthBothPass(self.clone()).into(); error!("{err}"); return Err(err); } + if self.pass_file.is_some() { + let file = self.pass_file.clone().unwrap(); + if !file.exists() { + return Err(ConfigError::PassFileDoesNotExist(file).into()); + } + } Ok(()) } } #[derive(Debug, Clone, Deserialize)] pub struct Api { - r#type: ApiType, - endpoint: Url, + pub r#type: ApiType, + pub endpoint: Url, /// May be left empty if the Api does not need auth or the auth is part of the /// [endpoint](Api::endpoint) [Url]. - auth: Option, + pub auth: Option, } -impl Api { - pub fn check(&self) -> Result<()> { - if let Some(auth) = &self.auth { - auth.check()?; +impl YamlConfigSection for Api { + fn check(&self) -> Result<()> { + self.r#type.check()?; + match self.endpoint.socket_addrs(|| None) { + Ok(_) => (), + Err(err) => return Err(err.into()), + } + if self.auth.is_some() { + self.auth.clone().unwrap().check()?; } Ok(()) } @@ -72,6 +102,11 @@ pub enum ApiType { #[serde(alias = "github", alias = "GitHub")] Github, } +impl YamlConfigSection for ApiType { + fn check(&self) -> Result<()> { + Ok(()) + } +} #[derive(Debug, Clone, Deserialize)] pub struct YamlConfig { @@ -79,6 +114,16 @@ pub struct YamlConfig { pub uses: Uses, pub api: HashMap, } +impl YamlConfigSection for YamlConfig { + fn check(&self) -> Result<()> { + self.changelog.check()?; + self.uses.check()?; + for api in self.api.values() { + api.check()?; + } + Ok(()) + } +} impl YamlConfig { /// check if the built configuration is valid @@ -92,6 +137,7 @@ impl YamlConfig { pub struct Config { pub yaml: YamlConfig, + pub cli: Cli, pub repo: git2::Repository, pub path: PathBuf, } @@ -110,7 +156,7 @@ impl Debug for Config { } impl Config { - pub fn load(_cli: Cli) -> Result { + pub fn load(cli: Cli) -> Result { let repo = match git2::Repository::open_from_env() { Ok(repo) => repo, Err(_err) => { @@ -147,6 +193,11 @@ impl Config { yaml.check()?; debug!("built and checked yaml config"); - Ok(Config { yaml, repo, path }) + Ok(Config { + yaml, + repo, + path, + cli, + }) } } diff --git a/src/error.rs b/src/error.rs index 01b172b..99d635a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use anyhow; use thiserror::Error; @@ -28,4 +30,8 @@ pub enum ConfigError { YamlFileIsNotFile, #[error("api {0:?} provides both a `pass` and a `pass_file`")] YamlApiAuthBothPass(ApiAuth), + #[error("{0} has 'enabled = false' in the yaml config")] + IsDisabledButUsed(&'static str), + #[error("password provided as file, but does not exist: {0}")] + PassFileDoesNotExist(PathBuf), } diff --git a/src/lib.rs b/src/lib.rs index 7404805..2dcf561 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod config; pub mod error; +pub mod changelog; diff --git a/src/main.rs b/src/main.rs index 275c473..c6074d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,20 @@ -use anyhow::Result; - -mod config; -mod error; -use config::{cli::Cli, Config}; +use autocrate::{ + changelog::*, + config::{ + cli::{Cli, Commands}, + Config, + }, + error::*, +}; fn main() -> Result<()> { let cli = Cli::cli_parse(); - let _config = Config::load(cli.clone())?; + let cfg = Config::load(cli.clone())?; - todo!() + match cli.command { + Commands::Changelog { .. } => { + println!("{}", Changelog::build(&cfg)?.to_string()); + Ok(()) + } + } }