feat: refactor frontend configuration
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
71
crates/cuddle-please-frontend/src/gatheres/cli.rs
Normal file
71
crates/cuddle-please-frontend/src/gatheres/cli.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Args;
|
||||
|
||||
use crate::stage0_config::{
|
||||
PleaseConfigBuilder, PleaseProjectConfigBuilder, PleaseSettingsConfigBuilder,
|
||||
};
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct ConfigArgs {
|
||||
/// Which repository to publish against. If not supplied remote url will be inferred from environment or fail if not present.
|
||||
#[arg(
|
||||
env = "CUDDLE_PLEASE_API_URL",
|
||||
long,
|
||||
global = true,
|
||||
help_heading = "Config"
|
||||
)]
|
||||
pub api_url: Option<String>,
|
||||
|
||||
/// repo is the name of repository you want to release for
|
||||
#[arg(
|
||||
env = "CUDDLE_PLEASE_REPO",
|
||||
long,
|
||||
global = true,
|
||||
help_heading = "Config"
|
||||
)]
|
||||
pub repo: Option<String>,
|
||||
|
||||
/// owner is the name of user from which the repository belongs <user>/<repo>
|
||||
#[arg(
|
||||
env = "CUDDLE_PLEASE_OWNER",
|
||||
long,
|
||||
global = true,
|
||||
help_heading = "Config"
|
||||
)]
|
||||
pub owner: Option<String>,
|
||||
|
||||
/// which source directory to use, if not set `std::env::current_dir` is used instead.
|
||||
#[arg(
|
||||
env = "CUDDLE_PLEASE_SOURCE",
|
||||
long,
|
||||
global = true,
|
||||
help_heading = "Config"
|
||||
)]
|
||||
pub source: Option<PathBuf>,
|
||||
|
||||
/// which branch is being run from
|
||||
#[arg(
|
||||
env = "CUDDLE_PLEASE_BRANCH",
|
||||
long,
|
||||
global = true,
|
||||
help_heading = "Config"
|
||||
)]
|
||||
pub branch: Option<String>,
|
||||
}
|
||||
|
||||
impl From<ConfigArgs> for PleaseConfigBuilder {
|
||||
fn from(value: ConfigArgs) -> Self {
|
||||
Self {
|
||||
project: Some(PleaseProjectConfigBuilder {
|
||||
owner: value.owner,
|
||||
repository: value.repo,
|
||||
source: value.source,
|
||||
branch: value.branch,
|
||||
}),
|
||||
settings: Some(PleaseSettingsConfigBuilder {
|
||||
api_url: value.api_url,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
80
crates/cuddle-please-frontend/src/gatheres/config_file.rs
Normal file
80
crates/cuddle-please-frontend/src/gatheres/config_file.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
use crate::stage0_config::PleaseConfigBuilder;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CuddleEmbeddedPleaseConfig {
|
||||
please: PleaseConfigBuilder,
|
||||
}
|
||||
|
||||
impl From<CuddleEmbeddedPleaseConfig> for PleaseConfigBuilder {
|
||||
fn from(value: CuddleEmbeddedPleaseConfig) -> Self {
|
||||
value.please
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct CuddlePleaseConfig {
|
||||
#[serde(flatten)]
|
||||
please: PleaseConfigBuilder,
|
||||
}
|
||||
impl From<CuddlePleaseConfig> for PleaseConfigBuilder {
|
||||
fn from(value: CuddlePleaseConfig) -> Self {
|
||||
value.please
|
||||
}
|
||||
}
|
||||
|
||||
const CUDDLE_FILE_NAME: &str = "cuddle";
|
||||
const CUDDLE_CONFIG_FILE_NAME: &str = "cuddle.please";
|
||||
const YAML_EXTENSION: &str = "yaml";
|
||||
|
||||
pub fn get_config_from_config_file(current_dir: &Path) -> PleaseConfigBuilder {
|
||||
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 = PleaseConfigBuilder::default();
|
||||
|
||||
if let Some(config) = get_config_from_file::<CuddleEmbeddedPleaseConfig>(current_cuddle_path) {
|
||||
please_config = please_config.merge(&config).clone();
|
||||
}
|
||||
|
||||
if let Some(config) = get_config_from_file::<CuddlePleaseConfig>(current_cuddle_config_path) {
|
||||
please_config = please_config.merge(&config).clone();
|
||||
}
|
||||
|
||||
please_config
|
||||
}
|
||||
|
||||
pub fn get_config_from_file<T>(current_cuddle_path: PathBuf) -> Option<PleaseConfigBuilder>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
T: Into<PleaseConfigBuilder>,
|
||||
{
|
||||
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
|
||||
}
|
53
crates/cuddle-please-frontend/src/gatheres/execution_env.rs
Normal file
53
crates/cuddle-please-frontend/src/gatheres/execution_env.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::stage0_config::{PleaseConfigBuilder, PleaseProjectConfigBuilder};
|
||||
|
||||
pub fn get_from_environment(vars: std::env::Vars) -> PleaseConfigBuilder {
|
||||
let vars: HashMap<String, String> = vars.collect();
|
||||
|
||||
let env = detect_environment(&vars);
|
||||
|
||||
match env {
|
||||
ExecutionEnvironment::Local => PleaseConfigBuilder {
|
||||
project: Some(PleaseProjectConfigBuilder {
|
||||
source: Some(PathBuf::from(".")),
|
||||
..Default::default()
|
||||
}),
|
||||
settings: None,
|
||||
},
|
||||
ExecutionEnvironment::Drone => PleaseConfigBuilder {
|
||||
project: Some(PleaseProjectConfigBuilder {
|
||||
owner: Some(
|
||||
vars.get("DRONE_REPO_OWNER")
|
||||
.expect("DRONE_REPO_OWNER to be present")
|
||||
.clone(),
|
||||
),
|
||||
repository: Some(
|
||||
vars.get("DRONE_REPO_NAME")
|
||||
.expect("DRONE_REPO_NAME to be present")
|
||||
.clone(),
|
||||
),
|
||||
source: Some(PathBuf::from(".")),
|
||||
branch: Some(
|
||||
vars.get("DRONE_REPO_BRANCH")
|
||||
.expect("DRONE_REPO_BRANCH to be present")
|
||||
.clone(),
|
||||
),
|
||||
}),
|
||||
settings: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_environment(vars: &HashMap<String, String>) -> ExecutionEnvironment {
|
||||
if let Some(_) = vars.get("DRONE".into()) {
|
||||
return ExecutionEnvironment::Drone;
|
||||
}
|
||||
|
||||
ExecutionEnvironment::Local
|
||||
}
|
||||
|
||||
pub enum ExecutionEnvironment {
|
||||
Local,
|
||||
Drone,
|
||||
}
|
9
crates/cuddle-please-frontend/src/gatheres/mod.rs
Normal file
9
crates/cuddle-please-frontend/src/gatheres/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
mod cli;
|
||||
mod config_file;
|
||||
mod execution_env;
|
||||
mod stdin;
|
||||
|
||||
pub use cli::ConfigArgs;
|
||||
pub(crate) use config_file::get_config_from_config_file;
|
||||
pub(crate) use execution_env::get_from_environment;
|
||||
pub(crate) use stdin::get_config_from_stdin;
|
19
crates/cuddle-please-frontend/src/gatheres/stdin.rs
Normal file
19
crates/cuddle-please-frontend/src/gatheres/stdin.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::stage0_config::PleaseConfigBuilder;
|
||||
|
||||
pub fn get_config_from_stdin<'d, T>(stdin: &'d str) -> PleaseConfigBuilder
|
||||
where
|
||||
T: Deserialize<'d>,
|
||||
T: Into<PleaseConfigBuilder>,
|
||||
{
|
||||
match serde_yaml::from_str::<'d, T>(stdin) {
|
||||
Ok(config) => {
|
||||
return config.into();
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::debug!("stdin doesn't contain a valid please config: {}", e);
|
||||
}
|
||||
}
|
||||
PleaseConfigBuilder::default()
|
||||
}
|
95
crates/cuddle-please-frontend/src/lib.rs
Normal file
95
crates/cuddle-please-frontend/src/lib.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub mod gatheres;
|
||||
mod stage0_config;
|
||||
|
||||
pub use gatheres::ConfigArgs;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PleaseProjectConfig {
|
||||
pub owner: String,
|
||||
pub repository: String,
|
||||
pub source: PathBuf,
|
||||
pub branch: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PleaseSettingsConfig {
|
||||
pub api_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PleaseConfig {
|
||||
pub project: PleaseProjectConfig,
|
||||
pub settings: PleaseSettingsConfig,
|
||||
}
|
||||
|
||||
impl PleaseConfig {
|
||||
pub fn get_owner<'a>(&'a self) -> &'a str {
|
||||
&self.project.owner
|
||||
}
|
||||
pub fn get_repository<'a>(&'a self) -> &'a str {
|
||||
&self.project.repository
|
||||
}
|
||||
pub fn get_source<'a>(&'a self) -> &'a PathBuf {
|
||||
&self.project.source
|
||||
}
|
||||
pub fn get_branch<'a>(&'a self) -> &'a str {
|
||||
&self.project.branch
|
||||
}
|
||||
pub fn get_api_url<'a>(&'a self) -> &'a str {
|
||||
&self.settings.api_url
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PleaseConfigBuilder {
|
||||
stdin: Option<stage0_config::PleaseConfigBuilder>,
|
||||
execution_env: Option<stage0_config::PleaseConfigBuilder>,
|
||||
cli: Option<stage0_config::PleaseConfigBuilder>,
|
||||
config: Option<stage0_config::PleaseConfigBuilder>,
|
||||
}
|
||||
|
||||
impl PleaseConfigBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_stdin(&mut self, stdin: String) -> &mut Self {
|
||||
self.stdin = Some(gatheres::get_config_from_stdin::<
|
||||
stage0_config::PleaseConfigBuilder,
|
||||
>(stdin.as_str()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_config_file(&mut self, current_dir: &Path) -> &mut Self {
|
||||
self.config = Some(gatheres::get_config_from_config_file(current_dir));
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_execution_env(&mut self, env_bag: std::env::Vars) -> &mut Self {
|
||||
self.execution_env = Some(gatheres::get_from_environment(env_bag));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_cli(&mut self, cli: gatheres::ConfigArgs) -> &mut Self {
|
||||
self.cli = Some(cli.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&mut self) -> anyhow::Result<PleaseConfig> {
|
||||
let gathered = vec![&self.execution_env, &self.config, &self.stdin, &self.cli];
|
||||
|
||||
let final_config = gathered
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.fold(stage0_config::PleaseConfigBuilder::default(), |mut a, x| {
|
||||
a.merge(x).clone()
|
||||
});
|
||||
|
||||
Ok(final_config.try_into()?)
|
||||
}
|
||||
}
|
111
crates/cuddle-please-frontend/src/stage0_config.rs
Normal file
111
crates/cuddle-please-frontend/src/stage0_config.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
use std::{default, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{PleaseConfig, PleaseProjectConfig, PleaseSettingsConfig};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct PleaseProjectConfigBuilder {
|
||||
pub owner: Option<String>,
|
||||
pub repository: Option<String>,
|
||||
pub source: Option<PathBuf>,
|
||||
pub branch: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct PleaseSettingsConfigBuilder {
|
||||
pub api_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct PleaseConfigBuilder {
|
||||
pub project: Option<PleaseProjectConfigBuilder>,
|
||||
pub settings: Option<PleaseSettingsConfigBuilder>,
|
||||
}
|
||||
|
||||
impl PleaseConfigBuilder {
|
||||
pub fn merge(&mut self, config: &PleaseConfigBuilder) -> &Self {
|
||||
let config = config.clone();
|
||||
let mut fproject = match self.project.clone() {
|
||||
None => PleaseProjectConfigBuilder::default(),
|
||||
Some(project) => project,
|
||||
};
|
||||
let mut fsettings = match self.settings.clone() {
|
||||
None => PleaseSettingsConfigBuilder::default(),
|
||||
Some(settings) => settings,
|
||||
};
|
||||
|
||||
if let Some(mut project) = config.project {
|
||||
if let Some(owner) = project.owner {
|
||||
fproject.owner = Some(owner);
|
||||
}
|
||||
if let Some(repository) = project.repository {
|
||||
fproject.repository = Some(repository);
|
||||
}
|
||||
if let Some(source) = project.source {
|
||||
fproject.source = Some(source);
|
||||
}
|
||||
if let Some(branch) = project.branch {
|
||||
fproject.branch = Some(branch);
|
||||
}
|
||||
self.project = Some(fproject);
|
||||
}
|
||||
|
||||
if let Some(mut settings) = config.settings {
|
||||
if let Some(api_url) = settings.api_url {
|
||||
fsettings.api_url = Some(api_url);
|
||||
}
|
||||
|
||||
self.settings = Some(fsettings);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PleaseConfigBuilder> for PleaseConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: PleaseConfigBuilder) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
project: value
|
||||
.project
|
||||
.ok_or(value_is_missing("project"))?
|
||||
.try_into()?,
|
||||
settings: value
|
||||
.settings
|
||||
.ok_or(value_is_missing("settings"))?
|
||||
.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PleaseProjectConfigBuilder> for PleaseProjectConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: PleaseProjectConfigBuilder) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
owner: value.owner.ok_or(value_is_missing("owner"))?,
|
||||
repository: value.repository.ok_or(value_is_missing("repository"))?,
|
||||
source: value.source.ok_or(value_is_missing("source"))?,
|
||||
branch: value.branch.ok_or(value_is_missing("branch"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PleaseSettingsConfigBuilder> for PleaseSettingsConfig {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: PleaseSettingsConfigBuilder) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
api_url: value.api_url.ok_or(value_is_missing("api_url"))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn value_is_missing(message: &str) -> anyhow::Error {
|
||||
anyhow::anyhow!(
|
||||
"{} is required, pass via. cli, env or config file, see --help",
|
||||
message.to_string()
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user