generated from PlexSheep/rs-base
a basic changelog interface
cargo devel CI / cargo CI (push) Successful in 4m21s
Details
cargo devel CI / cargo CI (push) Successful in 4m21s
Details
This commit is contained in:
parent
e75f072cb0
commit
0d2ac7e163
|
@ -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
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}")
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod changelog;
|
||||
|
|
22
src/main.rs
22
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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue