a basic changelog interface
cargo devel CI / cargo CI (push) Successful in 4m21s Details

This commit is contained in:
Christoph J. Scherr 2024-01-27 23:26:49 +01:00
parent e75f072cb0
commit 0d2ac7e163
Signed by: PlexSheep
GPG Key ID: 7CDD0B14851A08EF
9 changed files with 202 additions and 53 deletions

View File

@ -1,3 +1,4 @@
---
changelog:
enable: true
git-log: true
@ -8,7 +9,7 @@ uses:
# tokens are loaded from ~/.cargo/config.toml
registries:
- crates.io
- example.com
- 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

View File

@ -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 <software@cscherr.de>"]
@ -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"] }

View File

@ -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

53
src/changelog/mod.rs Normal file
View File

@ -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<String>,
git_log: Option<String>,
}
impl Changelog {
pub fn build(cfg: &Config) -> Result<Self> {
if !cfg.yaml.changelog.enable {
return Err(ConfigError::IsDisabledButUsed("changelog").into());
}
let message: Option<String> = 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<Option<String>> {
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}")
}
}

View File

@ -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<InfoLevel>,
pub verbose: Verbosity<InfoLevel>,
/// 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<Vec<String>>,
},
}
impl Display for Commands {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Changelog { .. } => "Changelog",
}
)
}
}
impl Cli {

View File

@ -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<String>,
}
impl YamlConfigSection for UseCargo {
fn check(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Uses {
cargo: Option<Cargo>,
cargo: UseCargo,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Cargo {
publish: bool,
registries: Vec<String>,
impl YamlConfigSection for Uses {
fn check(&self) -> Result<()> {
self.cargo.check()?;
Ok(())
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ApiAuth {
user: String,
pass: Option<String>,
pass_file: Option<PathBuf>,
pub user: String,
pub pass: Option<String>,
pub pass_file: Option<PathBuf>,
}
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<ApiAuth>,
pub auth: Option<ApiAuth>,
}
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<String, Api>,
}
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<Self> {
pub fn load(cli: Cli) -> Result<Self> {
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,
})
}
}

View File

@ -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),
}

View File

@ -1,2 +1,3 @@
pub mod config;
pub mod error;
pub mod changelog;

View File

@ -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(())
}
}
}