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"
|
||||
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]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
@ -155,7 +164,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -182,6 +191,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-test",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -302,6 +312,15 @@ version = "0.4.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
@ -425,6 +444,50 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
@ -473,7 +536,7 @@ checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -529,6 +592,17 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "syn"
|
||||
version = "2.0.27"
|
||||
@ -578,7 +652,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -602,7 +676,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -632,14 +706,41 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"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]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
|
@ -11,3 +11,8 @@ tracing = { version = "0.1", features = ["log"] }
|
||||
tracing-subscriber = { version = "0.3.17" }
|
||||
clap = { version = "4.3.4", features = ["derive", "env"] }
|
||||
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
|
||||
clap.workspace = true
|
||||
dotenv.workspace = true
|
||||
serde_yaml = "0.9.25"
|
||||
serde = { version = "1.0.177", features = ["derive"] }
|
||||
serde_yaml.workspace = true
|
||||
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 clap::{Parser, Subcommand};
|
||||
use command::Command;
|
||||
use ui::{ConsoleUi};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
dotenv::dotenv().ok();
|
||||
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 current_dir = get_current_path(cli.source.clone())?;
|
||||
let get_config = get_config(¤t_dir)?;
|
||||
let _ui = ConsoleUi::new();
|
||||
|
||||
// 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
|
||||
Command::new().execute(current_dir)?;
|
||||
|
||||
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