feat: refactor frontend configuration

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

18
Cargo.lock generated
View File

@ -344,6 +344,7 @@ dependencies = [
"chrono",
"clap",
"conventional_commit_parser",
"cuddle-please-frontend",
"dotenv",
"git-cliff-core",
"lazy_static",
@ -361,6 +362,23 @@ dependencies = [
"url",
]
[[package]]
name = "cuddle-please-frontend"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"clap",
"dotenv",
"pretty_assertions",
"serde",
"serde_yaml",
"tempdir",
"tracing",
"tracing-subscriber",
"tracing-test",
]
[[package]]
name = "deunicode"
version = "0.4.3"

View File

@ -1,9 +1,10 @@
[workspace]
members = ["crates/cuddle-please"]
members = ["crates/cuddle-please", "crates/cuddle-please-frontend"]
resolver = "2"
[workspace.dependencies]
cuddle-please = { path = "crates/cuddle-please" }
cuddle-please-frontend = { path = "crates/cuddle-please-frontend" }
anyhow = { version = "1.0.71" }
tracing = { version = "0.1", features = ["log"] }

View File

@ -34,7 +34,7 @@ See docs for more information about installation and some such
### 0.1 Milestone
- [x] Hide unneccessary commands
- [ ] Redo configuration frontend
- [x] Redo configuration frontend
- [ ] Refactor command.rs into smaller bits so that bits are easier to test
- [ ] Add reporter for PR and Repositories
- [ ] Setup temporary git name and email to use for git committing

View File

@ -0,0 +1,19 @@
[package]
name = "cuddle-please-frontend"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
clap.workspace = true
dotenv.workspace = true
serde_yaml.workspace = true
serde.workspace = true
chrono.workspace = true
tempdir.workspace = true
[dev-dependencies]
tracing-test = { workspace = true, features = ["no-env-filter"] }
pretty_assertions.workspace = true

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

View File

@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
cuddle-please-frontend.workspace = true
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true

View File

@ -8,11 +8,10 @@ use std::{
use ::semver::Version;
use anyhow::Context;
use clap::{Args, Parser, Subcommand};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use cuddle_please_frontend::{gatheres::ConfigArgs, PleaseConfig, PleaseConfigBuilder};
use crate::{
cliff::{self, changelog_parser},
environment::get_from_environment,
git_client::VcsClient,
gitea_client::GiteaClient,
ui::{ConsoleUi, DynUi},
@ -27,6 +26,9 @@ pub struct Command {
#[command(flatten)]
global: GlobalArgs,
#[command(flatten)]
config: ConfigArgs,
#[command(subcommand)]
commands: Option<Commands>,
@ -49,32 +51,13 @@ struct GlobalArgs {
)]
token: Option<String>,
/// Which repository to publish against. If not supplied remote url will be inferred from environment or fail if not present.
#[arg(long, global = true, help_heading = "Global")]
api_url: Option<String>,
/// repo is the name of repository you want to release for
#[arg(long, global = true, help_heading = "Global")]
repo: Option<String>,
/// owner is the name of user from which the repository belongs <user>/<repo>
#[arg(long, global = true, help_heading = "Global")]
owner: Option<String>,
/// which source directory to use, if not set `std::env::current_dir` is used instead.
#[arg(long, global = true, help_heading = "Global")]
source: Option<PathBuf>,
/// which branch is being run from
#[arg(long, global = true, help_heading = "Global")]
branch: Option<String>,
/// whether to run in dry run mode (i.e. no pushes or releases)
#[arg(long, global = true, help_heading = "Global")]
dry_run: bool,
/// Inject configuration from stdin
#[arg(
env = "CUDDLE_PLEASE_CONFIG_STDIN",
long,
global = true,
help_heading = "Global",
@ -138,31 +121,23 @@ impl Command {
s
}
fn get_config(
&self,
current_dir: &Path,
stdin: Option<String>,
) -> anyhow::Result<PleaseConfig> {
let mut config = get_config(current_dir, stdin)?;
self.get_from_environment(&mut config)?;
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.global.source.clone())?;
let stdin = if self.global.config_stdin {
// 0. Get config
let mut builder = &mut PleaseConfigBuilder::new();
if self.global.config_stdin {
if let Some(stdin_fn) = self.stdin.clone() {
let output = (stdin_fn.lock().unwrap().deref())();
Some(output.unwrap())
} else {
None
builder = builder.with_stdin(output?);
}
} else {
None
};
}
// 1. Parse the current directory
let current_dir = get_current_path(current_dir, self.config.source.clone())?;
let config = builder
.with_config_file(&current_dir)
.with_execution_env(std::env::vars())
.with_cli(self.config.clone())
.build()?;
match &self.commands {
Some(Commands::Release {}) => {
@ -170,30 +145,24 @@ impl 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)
let _config = self.get_config(&current_dir, stdin)?;
let owner = self.global.owner.as_ref().expect("owner to be set");
let repo = self.global.repo.as_ref().expect("repo to be set");
let branch = self.global.branch.as_ref().expect("branch to be set");
let git_client = self.get_git(&current_dir)?;
let git_client = self.get_git(config.get_source())?;
// 3. Create gitea client and do a health check
let gitea_client = self.get_gitea_client();
gitea_client
.connect(owner, repo)
.connect(config.get_owner(), config.get_repository())
.context("failed to connect to gitea repository")?;
// 4. Fetch git tags for the current repository
let tags = gitea_client.get_tags(owner, repo)?;
let tags = gitea_client.get_tags(config.get_owner(), config.get_repository())?;
let significant_tag = get_most_significant_version(tags.iter().collect());
// 5. Fetch git commits since last git tag
let commits = gitea_client.get_commits_since(
owner,
repo,
config.get_owner(),
config.get_repository(),
significant_tag.map(|st| st.commit.sha.clone()),
branch,
config.get_branch(),
)?;
// 7. Create a versioning client
@ -219,12 +188,7 @@ impl Command {
let builder =
cliff::ChangeLogBuilder::new(&commit_strs, next_version.to_string()).build();
let changelog_placement = self
.global
.source
.as_ref()
.map(|s| s.join("CHANGELOG.md"))
.unwrap_or(PathBuf::from("CHANGELOG.md"));
let changelog_placement = config.get_source().join("CHANGELOG.md");
let changelog = match std::fs::read_to_string(&changelog_placement).ok() {
Some(existing_changelog) => builder.prepend(existing_changelog)?,
@ -239,8 +203,8 @@ impl Command {
if first_commit.contains("chore(release): ") {
if !self.global.dry_run {
gitea_client.create_release(
owner,
repo,
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog_last_changes.unwrap(),
!next_version.pre.is_empty(),
@ -261,12 +225,14 @@ impl Command {
git_client.commit_and_push(next_version.to_string(), self.global.dry_run)?;
let _pr_number = match gitea_client.get_pull_request(owner, repo)? {
let _pr_number = match gitea_client
.get_pull_request(config.get_owner(), config.get_repository())?
{
Some(existing_pr) => {
if !self.global.dry_run {
gitea_client.update_pull_request(
owner,
repo,
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog_last_changes.unwrap(),
existing_pr,
@ -279,11 +245,11 @@ impl Command {
None => {
if !self.global.dry_run {
gitea_client.create_pull_request(
owner,
repo,
config.get_owner(),
config.get_repository(),
next_version.to_string(),
changelog,
self.global.branch.clone().unwrap(),
config.get_branch(),
)?
} else {
tracing::debug!("creating pull request (dry_run)");
@ -296,13 +262,12 @@ impl Command {
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("cuddle-config");
}
},
Some(Commands::Gitea { command }) => {
let git_url = url::Url::parse(&self.global.api_url.unwrap())?;
let git_url = url::Url::parse(config.get_api_url())?;
let mut url = String::new();
url.push_str(git_url.scheme());
@ -315,13 +280,13 @@ impl Command {
let client = GiteaClient::new(url, self.global.token);
match command {
GiteaCommand::Connect {} => {
client.connect(self.global.owner.unwrap(), self.global.repo.unwrap())?;
client.connect(config.get_owner(), config.get_repository())?;
self.ui.write_str_ln("connected succesfully go gitea");
}
GiteaCommand::Tags { command } => match command {
Some(GiteaTagsCommand::MostSignificant {}) => {
let tags = client
.get_tags(self.global.owner.unwrap(), self.global.repo.unwrap())?;
let tags =
client.get_tags(config.get_owner(), config.get_repository())?;
match get_most_significant_version(tags.iter().collect()) {
Some(tag) => {
@ -336,8 +301,8 @@ impl Command {
}
}
None => {
let tags = client
.get_tags(self.global.owner.unwrap(), self.global.repo.unwrap())?;
let tags =
client.get_tags(config.get_owner(), config.get_repository())?;
self.ui.write_str_ln("got tags from gitea");
for tag in tags {
self.ui.write_str_ln(&format!("- {}", tag.name))
@ -346,8 +311,8 @@ impl Command {
},
GiteaCommand::SinceCommit { sha, branch } => {
let commits = client.get_commits_since(
self.global.owner.unwrap(),
self.global.repo.unwrap(),
config.get_owner(),
config.get_repository(),
Some(sha),
branch,
)?;
@ -357,10 +322,8 @@ impl Command {
}
}
GiteaCommand::CheckPr {} => {
let pr = client.get_pull_request(
self.global.owner.unwrap(),
self.global.repo.unwrap(),
)?;
let pr =
client.get_pull_request(config.get_owner(), config.get_repository())?;
match pr {
Some(index) => {
@ -398,16 +361,9 @@ impl Command {
VcsClient::new_git(current_dir)
}
fn get_from_environment(&self, config: &mut PleaseConfig) -> anyhow::Result<()> {
let input_config = get_from_environment();
config.merge_mut(input_config);
Ok(())
}
fn get_gitea_client(&self) -> GiteaClient {
GiteaClient::new(
self.global.api_url.clone().expect("api_url to be set"),
self.config.api_url.clone().expect("api_url to be set"),
self.global.token.clone(),
)
}
@ -469,7 +425,8 @@ fn get_current_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")?;
//.context("could not find current dir, pass --source as a replacement")?;
.unwrap_or(PathBuf::from("."));
if !path.exists() {
anyhow::bail!("path doesn't exist {}", path.display());
@ -477,131 +434,3 @@ fn get_current_path(
Ok(path)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PleaseProjectConfig {
pub owner: Option<String>,
pub repository: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PleaseSettingsConfig {
pub api_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct PleaseConfig {
pub project: Option<PleaseProjectConfig>,
pub settings: Option<PleaseSettingsConfig>,
}
impl PleaseConfig {
fn merge(self, _config: PleaseConfig) -> Self {
self
}
fn merge_mut(&mut self, _config: PleaseConfig) -> &mut 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: &str = "cuddle";
const CUDDLE_CONFIG_FILE_NAME: &str = "cuddle.please";
const YAML_EXTENSION: &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<T>(current_cuddle_path: PathBuf) -> Option<PleaseConfig>
where
T: DeserializeOwned,
T: Into<PleaseConfig>,
{
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
}
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
}

View File

@ -1,38 +0,0 @@
use crate::command::{PleaseConfig, PleaseProjectConfig};
pub mod drone;
pub fn get_from_environment() -> PleaseConfig {
let env = detect_environment();
match env {
ExecutionEnvironment::Local => PleaseConfig {
project: None,
settings: None,
},
ExecutionEnvironment::Drone => PleaseConfig {
project: Some(PleaseProjectConfig {
owner: Some(
std::env::var("DRONE_REPO_OWNER").expect("DRONE_REPO_OWNER to be present"),
),
repository: Some(
std::env::var("DRONE_REPO_NAME").expect("DRONE_REPO_NAME to be present"),
),
}),
settings: None,
},
}
}
pub fn detect_environment() -> ExecutionEnvironment {
if std::env::var("DRONE").is_ok() {
return ExecutionEnvironment::Drone;
}
ExecutionEnvironment::Local
}
pub enum ExecutionEnvironment {
Local,
Drone,
}

View File

@ -1,6 +1,5 @@
pub mod cliff;
pub mod command;
pub mod environment;
pub mod git_client;
pub mod gitea_client;
pub mod ui;

View File

@ -1,6 +1,5 @@
pub mod cliff;
pub mod command;
pub mod environment;
pub mod git_client;
pub mod gitea_client;
pub mod ui;

View File

@ -0,0 +1,6 @@
project:
owner: kjuulh
repository: cuddle-please
branch: main
settings:
api_url: https://some-example.gitea-instance

View File

@ -0,0 +1,7 @@
please:
project:
owner: kjuulh
repository: cuddle-please
branch: main
settings:
api_url: https://some-example.gitea-instance

View File

@ -0,0 +1,6 @@
project:
owner: kjuulh
repository: cuddle-please
branch: main
settings:
api_url: https://some-example.gitea-instance

View File

@ -45,15 +45,19 @@ fn test_config_from_source_dir() {
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");
let config = r#"
project:
owner: kjuulh
repository: cuddle-please
branch: main
settings:
api_url: https://some-example.gitea-instance
"#;
Command::new_from_args_with_stdin(Some(ui), args, || Ok("please".into()))
args.push("--config-stdin");
Command::new_from_args_with_stdin(Some(ui), args, || Ok(config.into()))
.execute(None)
.unwrap();
assert_output(ui, "cuddle-config\n", "");
}