feat: add config parsing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
72afd0b968
commit
6a3a14ec94
109
Cargo.lock
generated
109
Cargo.lock
generated
@ -17,6 +17,15 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -155,7 +164,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.27",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -182,6 +191,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"tracing-test",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -302,6 +312,15 @@ version = "0.4.19"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata 0.1.10",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -425,6 +444,50 @@ dependencies = [
|
|||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata 0.3.4",
|
||||||
|
"regex-syntax 0.7.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax 0.6.29",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.7.4",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
@ -473,7 +536,7 @@ checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.27",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -529,6 +592,17 @@ version = "0.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.27"
|
version = "2.0.27"
|
||||||
@ -578,7 +652,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.27",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -602,7 +676,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.27",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -632,14 +706,41 @@ version = "0.3.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"matchers",
|
||||||
"nu-ansi-term",
|
"nu-ansi-term",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
"sharded-slab",
|
"sharded-slab",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thread_local",
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-test"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3a2c0ff408fe918a94c428a3f2ad04e4afd5c95bbc08fcf868eff750c15728a4"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"tracing-test-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-test-macro"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -11,3 +11,8 @@ tracing = { version = "0.1", features = ["log"] }
|
|||||||
tracing-subscriber = { version = "0.3.17" }
|
tracing-subscriber = { version = "0.3.17" }
|
||||||
clap = { version = "4.3.4", features = ["derive", "env"] }
|
clap = { version = "4.3.4", features = ["derive", "env"] }
|
||||||
dotenv = { version = "0.15.0" }
|
dotenv = { version = "0.15.0" }
|
||||||
|
|
||||||
|
serde_yaml = {version = "*"}
|
||||||
|
serde = {version = "*", features = ["derive"]}
|
||||||
|
|
||||||
|
tracing-test = "*"
|
@ -10,5 +10,8 @@ tracing.workspace = true
|
|||||||
tracing-subscriber.workspace = true
|
tracing-subscriber.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
dotenv.workspace = true
|
dotenv.workspace = true
|
||||||
serde_yaml = "0.9.25"
|
serde_yaml.workspace = true
|
||||||
serde = { version = "1.0.177", features = ["derive"] }
|
serde.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tracing-test = {workspace = true, features = ["no-env-filter"]}
|
293
crates/cuddle-release/src/command.rs
Normal file
293
crates/cuddle-release/src/command.rs
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
use std::{
|
||||||
|
io::Read,
|
||||||
|
ops::Deref,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::{Parser, Subcommand};
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::ui::{ConsoleUi, DynUi};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
pub struct Command {
|
||||||
|
/// token is the personal access token from gitea.
|
||||||
|
#[arg(
|
||||||
|
env = "CUDDLE_PLEASE_TOKEN",
|
||||||
|
long,
|
||||||
|
long_help = "token is the personal access token from gitea. It requires at least repository write access, it isn't required by default, but for most usecases the flow will fail without it",
|
||||||
|
global = true
|
||||||
|
)]
|
||||||
|
token: Option<String>,
|
||||||
|
|
||||||
|
/// Which repository to publish against. If not supplied remote url will be used.
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
repo_url: Option<String>,
|
||||||
|
|
||||||
|
/// which source directory to use, if not set `std::env::current_dir` is used instead.
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
source: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long, global = true)]
|
||||||
|
config_stdin: bool,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
commands: Option<Commands>,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
ui: DynUi,
|
||||||
|
|
||||||
|
#[clap(skip)]
|
||||||
|
stdin: Option<Arc<Mutex<dyn Fn() -> anyhow::Result<String> + Send + Sync + 'static>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let args = std::env::args();
|
||||||
|
|
||||||
|
Self::new_from_args_with_stdin(Some(ConsoleUi::default()), args, || {
|
||||||
|
let mut input = String::new();
|
||||||
|
std::io::stdin().read_to_string(&mut input)?;
|
||||||
|
Ok(input)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_args<I, T, UIF>(ui: Option<UIF>, i: I) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: Into<std::ffi::OsString> + Clone,
|
||||||
|
UIF: Into<DynUi>,
|
||||||
|
{
|
||||||
|
let mut s = Self::parse_from(i);
|
||||||
|
|
||||||
|
if let Some(ui) = ui {
|
||||||
|
s.ui = ui.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_args_with_stdin<I, T, F, UIF>(ui: Option<UIF>, i: I, input: F) -> Self
|
||||||
|
where
|
||||||
|
I: IntoIterator<Item = T>,
|
||||||
|
T: Into<std::ffi::OsString> + Clone,
|
||||||
|
F: Fn() -> anyhow::Result<String> + Send + Sync + 'static,
|
||||||
|
UIF: Into<DynUi>,
|
||||||
|
{
|
||||||
|
let mut s = Self::parse_from(i);
|
||||||
|
|
||||||
|
if let Some(ui) = ui {
|
||||||
|
s.ui = ui.into();
|
||||||
|
}
|
||||||
|
s.stdin = Some(Arc::new(Mutex::new(input)));
|
||||||
|
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config(
|
||||||
|
&self,
|
||||||
|
current_dir: &Path,
|
||||||
|
stdin: Option<String>,
|
||||||
|
) -> anyhow::Result<PleaseConfig> {
|
||||||
|
let config = get_config(current_dir, stdin)?;
|
||||||
|
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(self, current_dir: Option<&Path>) -> anyhow::Result<()> {
|
||||||
|
// 1. Parse the current directory
|
||||||
|
let current_dir = get_current_path(current_dir, self.source.clone())?;
|
||||||
|
let stdin = if self.config_stdin {
|
||||||
|
if let Some(stdin_fn) = self.stdin.clone() {
|
||||||
|
let output = (stdin_fn.lock().unwrap().deref())();
|
||||||
|
Some(output.unwrap())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
match &self.commands {
|
||||||
|
Some(Commands::Config { command }) => match command {
|
||||||
|
ConfigCommand::List {} => {
|
||||||
|
tracing::debug!("running command: config list");
|
||||||
|
let _config = self.get_config(current_dir.as_path(), stdin)?;
|
||||||
|
|
||||||
|
self.ui.write_str_ln(&format!("cuddle-config"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
tracing::debug!("running bare command");
|
||||||
|
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
|
||||||
|
// 2a. if not existing use default.
|
||||||
|
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
|
||||||
|
|
||||||
|
// 3. Create gitea client and do a health check
|
||||||
|
// 4. Fetch git tags for the current repository
|
||||||
|
// 5. Fetch git commits since last git tag
|
||||||
|
|
||||||
|
// 6. Slice commits since last git tag
|
||||||
|
|
||||||
|
// 7. Create a versioning client
|
||||||
|
// 8. Parse conventional commits and determine next version
|
||||||
|
|
||||||
|
// 9a. Check for open pr.
|
||||||
|
// 10a. If exists parse history, rebase from master and rewrite pr
|
||||||
|
|
||||||
|
// 9b. check for release commit and release, if release exists continue
|
||||||
|
// 10b. create release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
|
enum Commands {
|
||||||
|
Config {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: ConfigCommand,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
#[derive(Subcommand, Debug, Clone)]
|
||||||
|
enum ConfigCommand {
|
||||||
|
List {},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_path(
|
||||||
|
optional_current_dir: Option<&Path>,
|
||||||
|
optional_source_path: Option<PathBuf>,
|
||||||
|
) -> anyhow::Result<PathBuf> {
|
||||||
|
let path = optional_source_path
|
||||||
|
.or_else(|| optional_current_dir.map(|p| p.to_path_buf())) // fall back on current env from environment
|
||||||
|
.filter(|v| v.to_string_lossy() != "") // make sure we don't get empty values
|
||||||
|
//.and_then(|p| p.canonicalize().ok()) // Make sure we get the absolute path
|
||||||
|
.context("could not find current dir, pass --source as a replacement")?;
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
anyhow::bail!("path doesn't exist {}", path.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct PleaseConfig {}
|
||||||
|
|
||||||
|
impl PleaseConfig {
|
||||||
|
fn merge(self, _config: PleaseConfig) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PleaseConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct CuddleEmbeddedPleaseConfig {
|
||||||
|
please: PleaseConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CuddleEmbeddedPleaseConfig> for PleaseConfig {
|
||||||
|
fn from(value: CuddleEmbeddedPleaseConfig) -> Self {
|
||||||
|
value.please
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
struct CuddlePleaseConfig {
|
||||||
|
#[serde(flatten)]
|
||||||
|
please: PleaseConfig,
|
||||||
|
}
|
||||||
|
impl From<CuddlePleaseConfig> for PleaseConfig {
|
||||||
|
fn from(value: CuddlePleaseConfig) -> Self {
|
||||||
|
value.please
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const CUDDLE_FILE_NAME: &'static str = "cuddle";
|
||||||
|
const CUDDLE_CONFIG_FILE_NAME: &'static str = "cuddle.please";
|
||||||
|
const YAML_EXTENSION: &'static str = "yaml";
|
||||||
|
|
||||||
|
fn get_config(current_dir: &Path, stdin: Option<String>) -> anyhow::Result<PleaseConfig> {
|
||||||
|
let current_cuddle_path = current_dir
|
||||||
|
.clone()
|
||||||
|
.join(format!("{CUDDLE_FILE_NAME}.{YAML_EXTENSION}"));
|
||||||
|
let current_cuddle_config_path = current_dir
|
||||||
|
.clone()
|
||||||
|
.join(format!("{CUDDLE_CONFIG_FILE_NAME}.{YAML_EXTENSION}"));
|
||||||
|
let mut please_config = PleaseConfig::default();
|
||||||
|
|
||||||
|
if let Some(config) = get_config_from_file::<CuddleEmbeddedPleaseConfig>(current_cuddle_path) {
|
||||||
|
please_config = please_config.merge(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(config) = get_config_from_file::<CuddlePleaseConfig>(current_cuddle_config_path) {
|
||||||
|
please_config = please_config.merge(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(input_config) = get_config_from_stdin::<CuddlePleaseConfig>(stdin.as_ref()) {
|
||||||
|
please_config = please_config.merge(input_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(please_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_from_file<'d, T>(current_cuddle_path: PathBuf) -> Option<PleaseConfig>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
T: Into<PleaseConfig>,
|
||||||
|
{
|
||||||
|
match std::fs::File::open(¤t_cuddle_path) {
|
||||||
|
Ok(file) => match serde_yaml::from_reader::<_, T>(file) {
|
||||||
|
Ok(config) => {
|
||||||
|
return Some(config.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"{} doesn't contain a valid please config: {}",
|
||||||
|
¤t_cuddle_path.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::debug!(
|
||||||
|
"did not find or was not allowed to read {}, error: {}",
|
||||||
|
¤t_cuddle_path.display(),
|
||||||
|
e,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_config_from_stdin<'d, T>(stdin: Option<&'d String>) -> Option<PleaseConfig>
|
||||||
|
where
|
||||||
|
T: Deserialize<'d>,
|
||||||
|
T: Into<PleaseConfig>,
|
||||||
|
{
|
||||||
|
match stdin {
|
||||||
|
Some(content) => match serde_yaml::from_str::<'d, T>(&content) {
|
||||||
|
Ok(config) => {
|
||||||
|
return Some(config.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::debug!("stdin doesn't contain a valid please config: {}", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
tracing::trace!("Stdin was not set continueing",);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
2
crates/cuddle-release/src/lib.rs
Normal file
2
crates/cuddle-release/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod command;
|
||||||
|
pub mod ui;
|
@ -1,78 +1,20 @@
|
|||||||
use std::{net::SocketAddr, path::PathBuf};
|
pub mod command;
|
||||||
|
pub mod ui;
|
||||||
|
|
||||||
use anyhow::Context;
|
use command::Command;
|
||||||
use clap::{Parser, Subcommand};
|
use ui::{ConsoleUi};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let cli = Command::parse();
|
let current_dir = std::env::current_dir().ok();
|
||||||
|
let current_dir = current_dir.as_ref().map(|p| p.as_path());
|
||||||
|
|
||||||
// 1. Parse the current directory
|
let _ui = ConsoleUi::new();
|
||||||
let current_dir = get_current_path(cli.source.clone())?;
|
|
||||||
let get_config = get_config(¤t_dir)?;
|
|
||||||
|
|
||||||
// 2. Parse the cuddle.please.yaml let cuddle.please.yaml take precedence
|
Command::new().execute(current_dir)?;
|
||||||
// 2a. if not existing use default.
|
|
||||||
// 2b. if not in a git repo abort. (unless --no-vcs is turned added)
|
|
||||||
|
|
||||||
// 3. Create gitea client and do a health check
|
|
||||||
// 4. Fetch git tags for the current repository
|
|
||||||
// 5. Fetch git commits since last git tag
|
|
||||||
|
|
||||||
// 6. Slice commits since last git tag
|
|
||||||
|
|
||||||
// 7. Create a versioning client
|
|
||||||
// 8. Parse conventional commits and determine next version
|
|
||||||
|
|
||||||
// 9a. Check for open pr.
|
|
||||||
// 10a. If exists parse history, rebase from master and rewrite pr
|
|
||||||
|
|
||||||
// 9b. check for release commit and release, if release exists continue
|
|
||||||
// 10b. create release
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
struct Command {
|
|
||||||
/// token is the personal access token from gitea.
|
|
||||||
#[arg(
|
|
||||||
env = "CUDDLE_PLEASE_TOKEN",
|
|
||||||
long,
|
|
||||||
long_help = "token is the personal access token from gitea. It requires at least repository write access, it isn't required by default, but for most usecases the flow will fail without it"
|
|
||||||
)]
|
|
||||||
token: Option<String>,
|
|
||||||
|
|
||||||
/// Which repository to publish against. If not supplied remote url will be used.
|
|
||||||
#[arg(long)]
|
|
||||||
repo_url: Option<String>,
|
|
||||||
|
|
||||||
/// which source directory to use, if not set `std::env::current_dir` is used instead.
|
|
||||||
#[arg(long)]
|
|
||||||
source: Option<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_current_path(optional_source_path: Option<PathBuf>) -> anyhow::Result<PathBuf> {
|
|
||||||
let path = optional_source_path
|
|
||||||
.or_else(|| std::env::current_dir().ok()) // fall back on current env from environment
|
|
||||||
.filter(|v| v.to_string_lossy() != "") // make sure we don't get empty values
|
|
||||||
.and_then(|p| p.canonicalize().ok()) // Make sure we get the absolute path
|
|
||||||
.context("could not find current dir, pass --source as a replacement")?;
|
|
||||||
|
|
||||||
if !path.exists() {
|
|
||||||
anyhow::bail!("path doesn't exist {}", path.display());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(path)
|
|
||||||
}
|
|
||||||
struct PleaseConfig {}
|
|
||||||
|
|
||||||
const CUDDLE_FILE_NAME: &'static str = "cuddle";
|
|
||||||
const CUDDLE_CONFIG_FILE_NAME: &'static str = "cuddle.please";
|
|
||||||
const YAML_EXTENSIONS: Vec<&'static str> = vec!["yaml", "yml"];
|
|
||||||
|
|
||||||
fn get_config(current_dir: &PathBuf) -> anyhow::Result<()> {}
|
|
||||||
|
53
crates/cuddle-release/src/ui.rs
Normal file
53
crates/cuddle-release/src/ui.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
pub trait Ui {
|
||||||
|
fn write_str(&self, content: &str);
|
||||||
|
fn write_err_str(&self, content: &str);
|
||||||
|
|
||||||
|
fn write_str_ln(&self, content: &str);
|
||||||
|
fn write_err_str_ln(&self, content: &str);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DynUi = Box<dyn Ui + Send + Sync>;
|
||||||
|
|
||||||
|
impl Default for DynUi {
|
||||||
|
fn default() -> Self {
|
||||||
|
Box::new(ConsoleUi::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ConsoleUi {}
|
||||||
|
|
||||||
|
impl ConsoleUi {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ConsoleUi {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ConsoleUi> for DynUi {
|
||||||
|
fn from(value: ConsoleUi) -> Self {
|
||||||
|
Box::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui for ConsoleUi {
|
||||||
|
fn write_str(&self, content: &str) {
|
||||||
|
print!("{}", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_err_str(&self, content: &str) {
|
||||||
|
eprint!("{}", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_str_ln(&self, content: &str) {
|
||||||
|
println!("{}", content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_err_str_ln(&self, content: &str) {
|
||||||
|
eprintln!("{}", content)
|
||||||
|
}
|
||||||
|
}
|
0
crates/cuddle-release/testdata/cuddle-embed/cuddle.yaml
vendored
Normal file
0
crates/cuddle-release/testdata/cuddle-embed/cuddle.yaml
vendored
Normal file
0
crates/cuddle-release/testdata/cuddle-please/cuddle.please.yaml
vendored
Normal file
0
crates/cuddle-release/testdata/cuddle-please/cuddle.please.yaml
vendored
Normal file
113
crates/cuddle-release/tests/common/mod.rs
Normal file
113
crates/cuddle-release/tests/common/mod.rs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
use cuddle_release::ui::{DynUi, Ui};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
io::Write,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BufferInner {
|
||||||
|
pub stdout: Vec<u8>,
|
||||||
|
pub stderr: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferInner {
|
||||||
|
fn write_str(&mut self, content: &str) {
|
||||||
|
write!(&mut self.stdout, "{}", content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_err_str(&mut self, content: &str) {
|
||||||
|
write!(&mut self.stderr, "{}", content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_str_ln(&mut self, content: &str) {
|
||||||
|
writeln!(&mut self.stdout, "{}", content).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_err_str_ln(&mut self, content: &str) {
|
||||||
|
writeln!(&mut self.stderr, "{}", content).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BufferUi {
|
||||||
|
inner: Arc<Mutex<BufferInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BufferUi {
|
||||||
|
pub fn get_stdout(&self) -> String {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
let output = std::str::from_utf8(&inner.stdout).unwrap();
|
||||||
|
|
||||||
|
output.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_stderr(&self) -> String {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
let output = std::str::from_utf8(&inner.stderr).unwrap();
|
||||||
|
|
||||||
|
output.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_output(&self) -> (String, String) {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
let stdout = std::str::from_utf8(&inner.stdout).unwrap();
|
||||||
|
let stderr = std::str::from_utf8(&inner.stderr).unwrap();
|
||||||
|
|
||||||
|
(stdout.to_string(), stderr.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui for BufferUi {
|
||||||
|
fn write_str(&self, content: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
print!("{}", content);
|
||||||
|
inner.write_str(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_err_str(&self, content: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
eprint!("{}", content);
|
||||||
|
inner.write_err_str(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_str_ln(&self, content: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
println!("{}", content);
|
||||||
|
inner.write_str_ln(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_err_str_ln(&self, content: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
eprintln!("{}", content);
|
||||||
|
inner.write_err_str_ln(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BufferInner {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
stdout: Vec::new(),
|
||||||
|
stderr: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BufferUi {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Arc::new(Mutex::new(BufferInner::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BufferUi> for DynUi {
|
||||||
|
fn from(value: BufferUi) -> Self {
|
||||||
|
Box::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&BufferUi> for DynUi {
|
||||||
|
fn from(value: &BufferUi) -> Self {
|
||||||
|
value.clone().into()
|
||||||
|
}
|
||||||
|
}
|
83
crates/cuddle-release/tests/config.rs
Normal file
83
crates/cuddle-release/tests/config.rs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
pub mod common;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use common::BufferUi;
|
||||||
|
use cuddle_release::command::Command;
|
||||||
|
use tracing_test::traced_test;
|
||||||
|
|
||||||
|
fn get_base_args<'a>() -> Vec<&'a str> {
|
||||||
|
vec!["cuddle-please", "config", "list"]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_output(ui: &BufferUi, expected_stdout: &str, expected_stderr: &str) {
|
||||||
|
let (stdout, stderr) = ui.get_output();
|
||||||
|
|
||||||
|
assert_eq!(expected_stdout, &stdout);
|
||||||
|
assert_eq!(expected_stderr, &stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_test_data_path(item: &str) -> PathBuf {
|
||||||
|
std::env::current_dir()
|
||||||
|
.ok()
|
||||||
|
.map(|p| p.join("testdata").join(item))
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[traced_test]
|
||||||
|
fn test_config_from_current_dir() {
|
||||||
|
let args = get_base_args();
|
||||||
|
let ui = &BufferUi::default();
|
||||||
|
let current_dir = get_test_data_path("cuddle-embed");
|
||||||
|
|
||||||
|
Command::new_from_args(Some(ui), args.into_iter())
|
||||||
|
.execute(Some(¤t_dir))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_output(ui, "cuddle-config\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[traced_test]
|
||||||
|
fn test_config_from_source_dir() {
|
||||||
|
let mut args = get_base_args();
|
||||||
|
let ui = &BufferUi::default();
|
||||||
|
let current_dir = get_test_data_path("cuddle-embed");
|
||||||
|
args.push("--source");
|
||||||
|
args.push(current_dir.to_str().unwrap());
|
||||||
|
|
||||||
|
Command::new_from_args(Some(ui), args.into_iter())
|
||||||
|
.execute(None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_output(ui, "cuddle-config\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[traced_test]
|
||||||
|
fn test_config_from_stdin() {
|
||||||
|
let mut args = get_base_args();
|
||||||
|
let ui = &BufferUi::default();
|
||||||
|
let current_dir = get_test_data_path("cuddle-embed");
|
||||||
|
args.push("--source");
|
||||||
|
args.push(current_dir.to_str().unwrap());
|
||||||
|
args.push("--config-stdin");
|
||||||
|
|
||||||
|
Command::new_from_args_with_stdin(Some(ui), args.into_iter(), || Ok("please".into()))
|
||||||
|
.execute(None)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_output(ui, "cuddle-config\n", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[traced_test]
|
||||||
|
fn test_config_fails_when_not_path_is_set() {
|
||||||
|
let args = get_base_args();
|
||||||
|
let ui = &BufferUi::default();
|
||||||
|
|
||||||
|
let res = Command::new_from_args(Some(ui), args.into_iter()).execute(None);
|
||||||
|
|
||||||
|
assert!(res.is_err())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user