Skip to content

Commit

Permalink
Merge pull request #95 from jontze/feat/config-file-and-custom-prefix…
Browse files Browse the repository at this point in the history
…-support

Add support for config file and custom commit prefixes
  • Loading branch information
jontze authored Mar 3, 2024
2 parents c7a4301 + 236cac3 commit e3054cc
Show file tree
Hide file tree
Showing 12 changed files with 545 additions and 150 deletions.
15 changes: 15 additions & 0 deletions .github/conventional-versioning.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
# Configuration for conventional-versioning
kind: Node # Node | Cargo
output: Plain # Human | Plain | Json | Yaml | Yml | Toml
prefixes:
patch:
- "fix" # e.g. fix: ... | fix(scope): ...
- "chore"
minor:
- "feat"
major:
# Commits with a "!" or "BREAKING CHANGE:"
# will always be considered
# as a major change

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "4.5.1", features = ["derive"] }
clap = { version = "4.5.1", features = ["derive", "env"] }
git2 = { version = "0.18.2", default-features = false}
miette = { version = "7.1.0", features = ["fancy"] }
node-semver = "2.1.0"
Expand Down
43 changes: 39 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,45 @@ _Call the `--help` argument for more information._
Usage: conventional-versioning [OPTIONS]

Options:
-p, --path <PATH> Path to the repository. Default is the current directory
-k, --kind <KIND> SemVer kind. Default is the Node SemVer variant [possible values: node, cargo]
-o, --out <OUT> Output format. Default is the human readable format [possible values: human, plain, json, yaml, yml, toml]
-h, --help Print help
-r, --repo <REPO> Path to the repository. Default is the current directory [env: CONVENTIONAL_VERSIONING_REPO=]
-c, --config <CONFIG> Path to the configuration file. By default, the OS specific user configuration directories are checked. WARNING: If you use the `--config` option, all other args will be ignored, besides `--repo` [env: CONVENTIONAL_VERSIONING_CONFIG=]
-k, --kind <KIND> SemVer kind. Default is the Node SemVer variant [env: CONVENTIONAL_VERSIONING_KIND=] [default: node] [possible values: node, cargo]
-o, --out <OUT> Output format. Default is the human readable format [env: CONVENTIONAL_VERSIONING_OUTPUT=] [default: human] [possible values: human, plain, json, yaml, yml, toml]
-p, --patch-scope <PATCH_SCOPE> Commit scopes that cause a patch version bump [env: CONVENTIONAL_VERSIONING_PATCH=]
-m, --minor-scope <MINOR_SCOPE> Commit scopes that cause a minor version bump [env: CONVENTIONAL_VERSIONING_MINOR=]
-M, --major-scope <MAJOR_SCOPE> Commit scopes that cause a major version bump [env: CONVENTIONAL_VERSIONING_MAJOR=]
-h, --help Print help
-V, --version Print version
```

## Configuration

You can also provide a configuration via yaml file with the `--config` option.
Be aware that all other CLI options beside of `--repo` will be ignored if you
use the `--config` option.

**The configuration file should look like this:**

```yaml
---
# Configuration for conventional-versioning
kind: Node # Node | Cargo
output: Plain # Human | Plain | Json | Yaml | Yml | Toml
prefixes:
patch:
- "fix" # e.g. fix: ... | fix(scope): ...
- "patch"
- "chore"
- "..."
minor:
- "feat"
- "..."
major:
# Commits with a "!" or "BREAKING CHANGE:"
# will always be considered as a major change
- "breaking"
- "major"
- "..."
```
## License
Expand Down
31 changes: 0 additions & 31 deletions src/args.rs

This file was deleted.

40 changes: 40 additions & 0 deletions src/configuration/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use super::shared::{OutputFormat, SemVerKindArg};
use clap::Parser;

#[derive(Parser, Debug)]
#[command(author, about, version)]
pub(crate) struct Args {
/// Path to the repository. Default is the current directory.
#[arg(short = 'r', long, value_hint = clap::ValueHint::DirPath, env = "CONVENTIONAL_VERSIONING_REPO")]
pub repo: Option<std::path::PathBuf>,
/// Path to the configuration file. By default, the OS specific
/// user configuration directories are checked.
/// WARNING: If you use the `--config` option, all other args will be ignored, besides `--repo`.
#[arg(short = 'c', long, value_hint = clap::ValueHint::FilePath, env = "CONVENTIONAL_VERSIONING_CONFIG")]
pub config: Option<std::path::PathBuf>,
/// SemVer kind. Default is the Node SemVer variant.
#[arg(
short = 'k',
long,
default_value = "node",
env = "CONVENTIONAL_VERSIONING_KIND"
)]
pub kind: Option<SemVerKindArg>,
/// Output format. Default is the human readable format
#[arg(
short = 'o',
long,
default_value = "human",
env = "CONVENTIONAL_VERSIONING_OUTPUT"
)]
pub out: Option<OutputFormat>,
/// Commit scopes that cause a patch version bump.
#[arg(short = 'p', long, env = "CONVENTIONAL_VERSIONING_PATCH")]
pub patch_scope: Option<Vec<String>>,
/// Commit scopes that cause a minor version bump.
#[arg(short = 'm', long, env = "CONVENTIONAL_VERSIONING_MINOR")]
pub minor_scope: Option<Vec<String>>,
/// Commit scopes that cause a major version bump.
#[arg(short = 'M', long, env = "CONVENTIONAL_VERSIONING_MAJOR")]
pub major_scope: Option<Vec<String>>,
}
22 changes: 22 additions & 0 deletions src/configuration/file.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use miette::miette;

use super::shared::{OutputFormat, Prefixes, SemVerKindArg};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Default, Debug)]
pub(crate) struct Settings {
pub kind: SemVerKindArg,
pub output: OutputFormat,
pub prefixes: Prefixes,
}

pub(crate) fn read_config_at_path<P, TConfig>(path: P) -> miette::Result<TConfig>
where
P: AsRef<std::path::Path>,
TConfig: for<'de> serde::Deserialize<'de>,
{
let file = std::fs::File::open(path).map_err(|err| miette!(err.to_string()))?;
let reader = std::io::BufReader::new(file);
let settings = serde_yaml::from_reader(reader).map_err(|err| miette!(err.to_string()))?;
Ok(settings)
}
80 changes: 80 additions & 0 deletions src/configuration/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use clap::Parser;
use miette::miette;
use std::path::{Path, PathBuf};

use file::{read_config_at_path, Settings};

mod args;
mod file;
mod shared;

pub(crate) use shared::OutputFormat;
pub(crate) use shared::Prefixes;
pub(crate) use shared::SemVerKindArg as SemVerKind;

const DEFAULT_REPO_PATH: &str = ".";

#[derive(Debug)]
pub(crate) struct Config {
prefixes: Prefixes,
output: OutputFormat,
kind: SemVerKind,
repo_path: PathBuf,
}

impl Config {
pub(crate) fn new() -> miette::Result<Self> {
let args = args::Args::parse();

let settings: Option<miette::Result<Settings>> = args.config.map(read_config_at_path);

match settings {
// If config path provided, read from file
Some(settings) => {
if let Ok(settings) = settings {
Ok(Self {
kind: settings.kind,
output: settings.output,
prefixes: settings.prefixes,
repo_path: args
.repo
.map_or(Path::new(DEFAULT_REPO_PATH).to_path_buf(), |p| p),
})
} else {
Err(miette!(
"Failed to read configuration file at provided path."
))
}
}
// Otherwise, use command line args
None => Ok(Self {
kind: args.kind.unwrap_or(SemVerKind::Node),
output: args.out.unwrap_or(OutputFormat::Human),
prefixes: Prefixes {
patch: args.patch_scope.unwrap_or_else(Vec::new),
minor: args.minor_scope.unwrap_or_else(Vec::new),
major: args.major_scope.unwrap_or_else(Vec::new),
},
repo_path: args
.repo
.map_or(Path::new(DEFAULT_REPO_PATH).to_path_buf(), |p| p),
}),
}
}

pub(crate) fn repo_path(&self) -> &PathBuf {
&self.repo_path
}

pub(crate) fn semver_kind(&self) -> SemVerKind {
self.kind
}

pub(crate) fn output(&self) -> OutputFormat {
self.output
}

pub(crate) fn prefixes(&self) -> &Prefixes {
&self.prefixes
}
}
64 changes: 64 additions & 0 deletions src/configuration/shared.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum, Serialize, Deserialize)]
pub(crate) enum SemVerKindArg {
Node,
Cargo,
}

impl Default for SemVerKindArg {
fn default() -> Self {
Self::Cargo
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, ValueEnum, Serialize, Deserialize)]
pub(crate) enum OutputFormat {
Human,
Plain,
Json,
Yaml,
Yml,
Toml,
}

impl Default for OutputFormat {
fn default() -> Self {
Self::Plain
}
}

#[derive(Serialize, Deserialize, Default, Debug)]
pub(crate) struct Prefixes {
pub patch: Vec<String>,
pub minor: Vec<String>,
pub major: Vec<String>,
}

impl Prefixes {
pub(crate) fn is_empty(&self) -> bool {
self.patch.is_empty() && self.minor.is_empty() && self.major.is_empty()
}

pub(crate) fn is_patch(&self, commit_prefix: impl ToString) -> bool {
let commit_prefix = commit_prefix.to_string();
self.patch
.iter()
.any(|patch_prefix| patch_prefix.eq(&commit_prefix))
}

pub(crate) fn is_minor(&self, commit_prefix: impl ToString) -> bool {
let commit_prefix = commit_prefix.to_string();
self.minor
.iter()
.any(|minor_prefix| minor_prefix.eq(&commit_prefix))
}

pub(crate) fn is_major(&self, commit_prefix: impl ToString) -> bool {
let commit_prefix = commit_prefix.to_string();
self.major
.iter()
.any(|major_prefix| major_prefix.eq(&commit_prefix))
}
}
Loading

0 comments on commit e3054cc

Please sign in to comment.