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:
|
changelog:
|
||||||
enable: true
|
enable: true
|
||||||
git-log: true
|
git-log: true
|
||||||
|
@ -8,7 +9,7 @@ uses:
|
||||||
# tokens are loaded from ~/.cargo/config.toml
|
# tokens are loaded from ~/.cargo/config.toml
|
||||||
registries:
|
registries:
|
||||||
- crates.io
|
- crates.io
|
||||||
- example.com
|
- cscherr
|
||||||
|
|
||||||
api:
|
api:
|
||||||
github:
|
github:
|
||||||
|
@ -19,7 +20,7 @@ api:
|
||||||
pass: token_superimportantsecret
|
pass: token_superimportantsecret
|
||||||
myserv:
|
myserv:
|
||||||
type: gitea
|
type: gitea
|
||||||
endpoint: https://git.example.com
|
endpoint: https://git.cscherr.de
|
||||||
auth:
|
auth:
|
||||||
user: myUserName
|
user: myUserName
|
||||||
pass: importantsecrettoken
|
pass: importantsecrettoken
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "autocrate"
|
name = "autocrate"
|
||||||
version = "0.1.0-prealpha.0"
|
version = "0.1.0-prealpha.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = true
|
publish = true
|
||||||
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
authors = ["Christoph J. Scherr <software@cscherr.de>"]
|
||||||
|
@ -21,8 +21,8 @@ keywords = [
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.79"
|
anyhow = "1.0.79"
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
cargo = "0.76.0"
|
||||||
clap-num = "1.1.1"
|
clap = { version = "4.4.18", features = ["derive", "help"] }
|
||||||
clap-verbosity-flag = "2.1.2"
|
clap-verbosity-flag = "2.1.2"
|
||||||
git2 = "0.18.1"
|
git2 = "0.18.1"
|
||||||
libpt = { version = "0.3.11", features = ["log"] }
|
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
|
their work. Although initially built for Gitea, we plan to extend support
|
||||||
for additional platforms such as GitHub and GitLab.
|
for additional platforms such as GitHub and GitLab.
|
||||||
|
|
||||||
|
|
||||||
* [Original Repository](https://git.cscherr.de/PlexSheep/Autocrate)
|
* [Original Repository](https://git.cscherr.de/PlexSheep/Autocrate)
|
||||||
* [GitHub Mirror](https://github.com/PlexSheep/Autocrate)
|
* [GitHub Mirror](https://github.com/PlexSheep/Autocrate)
|
||||||
* [crates.io](https://crates.io/crates/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 |
|
| `api.NAME.auth` | `pass` | a string | A secret for authentication o the server, probably a token |
|
||||||
|
|
||||||
An example `.autocrate.yaml` could look like this:
|
An example `.autocrate.yaml` could look like this:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
changelog:
|
changelog:
|
||||||
enable: true
|
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 libpt::log::{Level, Logger};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
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)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
#[command(
|
#[command(
|
||||||
author,
|
author,
|
||||||
version,
|
version,
|
||||||
about = ABOUT_ROOT,
|
about,
|
||||||
long_about = format!("{}{}", ABOUT_ROOT ,LONG_ABOUT_ROOT),
|
long_about,
|
||||||
help_template =
|
help_template = r#"{about-section}
|
||||||
r#"{about-section}
|
|
||||||
{usage-heading} {usage}
|
{usage-heading} {usage}
|
||||||
{all-args}{tab}
|
{all-args}{tab}
|
||||||
|
|
||||||
autocrate: {version}
|
autocrate: {version}
|
||||||
Author: {author-with-newline}
|
Author: {author-with-newline}
|
||||||
"#
|
"#
|
||||||
)]
|
)]
|
||||||
|
/// Release Manager for Your Projects on Gitea, GitHub, and GitLab.
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
// clap_verbosity_flag seems to make this a global option implicitly
|
// clap_verbosity_flag seems to make this a global option implicitly
|
||||||
/// set a verbosity, multiple allowed (f.e. -vvv)
|
/// set a verbosity, multiple allowed (f.e. -vvv)
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
pub(crate) verbose: Verbosity<InfoLevel>,
|
pub verbose: Verbosity<InfoLevel>,
|
||||||
|
|
||||||
/// show additional logging meta data
|
/// show additional logging meta data
|
||||||
#[arg(long)]
|
#[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 {
|
impl Cli {
|
||||||
|
|
|
@ -10,54 +10,84 @@ use crate::error::*;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
use cli::Cli;
|
use cli::Cli;
|
||||||
|
|
||||||
|
pub trait YamlConfigSection: Debug + Clone + for<'a> Deserialize<'a> {
|
||||||
|
fn check(&self) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Changelog {
|
pub struct Changelog {
|
||||||
enable: bool,
|
pub enable: bool,
|
||||||
#[serde(alias = "git-log")]
|
#[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)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Uses {
|
pub struct Uses {
|
||||||
cargo: Option<Cargo>,
|
cargo: UseCargo,
|
||||||
}
|
}
|
||||||
|
impl YamlConfigSection for Uses {
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
fn check(&self) -> Result<()> {
|
||||||
pub struct Cargo {
|
self.cargo.check()?;
|
||||||
publish: bool,
|
Ok(())
|
||||||
registries: Vec<String>,
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct ApiAuth {
|
pub struct ApiAuth {
|
||||||
user: String,
|
pub user: String,
|
||||||
pass: Option<String>,
|
pub pass: Option<String>,
|
||||||
pass_file: Option<PathBuf>,
|
pub pass_file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
impl YamlConfigSection for ApiAuth {
|
||||||
impl ApiAuth {
|
fn check(&self) -> Result<()> {
|
||||||
pub fn check(&self) -> Result<()> {
|
|
||||||
if self.pass.is_some() && self.pass_file.is_some() {
|
if self.pass.is_some() && self.pass_file.is_some() {
|
||||||
let err = ConfigError::YamlApiAuthBothPass(self.clone()).into();
|
let err = ConfigError::YamlApiAuthBothPass(self.clone()).into();
|
||||||
error!("{err}");
|
error!("{err}");
|
||||||
return Err(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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct Api {
|
pub struct Api {
|
||||||
r#type: ApiType,
|
pub r#type: ApiType,
|
||||||
endpoint: Url,
|
pub endpoint: Url,
|
||||||
/// May be left empty if the Api does not need auth or the auth is part of the
|
/// May be left empty if the Api does not need auth or the auth is part of the
|
||||||
/// [endpoint](Api::endpoint) [Url].
|
/// [endpoint](Api::endpoint) [Url].
|
||||||
auth: Option<ApiAuth>,
|
pub auth: Option<ApiAuth>,
|
||||||
}
|
}
|
||||||
impl Api {
|
impl YamlConfigSection for Api {
|
||||||
pub fn check(&self) -> Result<()> {
|
fn check(&self) -> Result<()> {
|
||||||
if let Some(auth) = &self.auth {
|
self.r#type.check()?;
|
||||||
auth.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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -72,6 +102,11 @@ pub enum ApiType {
|
||||||
#[serde(alias = "github", alias = "GitHub")]
|
#[serde(alias = "github", alias = "GitHub")]
|
||||||
Github,
|
Github,
|
||||||
}
|
}
|
||||||
|
impl YamlConfigSection for ApiType {
|
||||||
|
fn check(&self) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
pub struct YamlConfig {
|
pub struct YamlConfig {
|
||||||
|
@ -79,6 +114,16 @@ pub struct YamlConfig {
|
||||||
pub uses: Uses,
|
pub uses: Uses,
|
||||||
pub api: HashMap<String, Api>,
|
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 {
|
impl YamlConfig {
|
||||||
/// check if the built configuration is valid
|
/// check if the built configuration is valid
|
||||||
|
@ -92,6 +137,7 @@ impl YamlConfig {
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub yaml: YamlConfig,
|
pub yaml: YamlConfig,
|
||||||
|
pub cli: Cli,
|
||||||
pub repo: git2::Repository,
|
pub repo: git2::Repository,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
@ -110,7 +156,7 @@ impl Debug for Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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() {
|
let repo = match git2::Repository::open_from_env() {
|
||||||
Ok(repo) => repo,
|
Ok(repo) => repo,
|
||||||
Err(_err) => {
|
Err(_err) => {
|
||||||
|
@ -147,6 +193,11 @@ impl Config {
|
||||||
yaml.check()?;
|
yaml.check()?;
|
||||||
debug!("built and checked yaml config");
|
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 anyhow;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -28,4 +30,8 @@ pub enum ConfigError {
|
||||||
YamlFileIsNotFile,
|
YamlFileIsNotFile,
|
||||||
#[error("api {0:?} provides both a `pass` and a `pass_file`")]
|
#[error("api {0:?} provides both a `pass` and a `pass_file`")]
|
||||||
YamlApiAuthBothPass(ApiAuth),
|
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 config;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod changelog;
|
||||||
|
|
22
src/main.rs
22
src/main.rs
|
@ -1,12 +1,20 @@
|
||||||
use anyhow::Result;
|
use autocrate::{
|
||||||
|
changelog::*,
|
||||||
mod config;
|
config::{
|
||||||
mod error;
|
cli::{Cli, Commands},
|
||||||
use config::{cli::Cli, Config};
|
Config,
|
||||||
|
},
|
||||||
|
error::*,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let cli = Cli::cli_parse();
|
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