feat: refactor frontend configuration

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2023-08-01 02:31:44 +02:00
parent e235483783
commit 8cd68d569b
22 changed files with 557 additions and 268 deletions

View 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,
}),
}
}
}

View 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(&current_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: {}",
&current_cuddle_path.display(),
e
);
}
},
Err(e) => {
tracing::debug!(
"did not find or was not allowed to read {}, error: {}",
&current_cuddle_path.display(),
e,
);
}
}
None
}

View 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,
}

View 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;

View 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()
}

View 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()?)
}
}

View 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()
)
}