Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "star-setup"
version = "0.4.5"
version = "0.4.6"
edition = "2021"
repository = "https://github.com/star-setup/core"
description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems"
Expand Down
174 changes: 81 additions & 93 deletions src/cli/resolve.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::{
cli::{
Args, BuildType, ResolvedArgs, ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags,
Args, BuildFlags, BuildType, ConnectionFlags, DiagnosticFlags, MonoRepoFlags, ResolvedArgs,
ResolvedBuildFlags, ResolvedConnectionFlags, ResolvedMonoFlags,
},
config::SetupConfig,
config::{ConfigEntry, SetupConfig},
ctx::RunFlags,
};

Expand Down Expand Up @@ -35,112 +36,99 @@ fn resolve_flag_pair(
)
}

/// Resolves raw `Args` into `ResolvedArgs` by applying config defaults and CLI overrides.
/// # Errors
/// Returns an error if the named config does not exist in the provided `SetupConfig`.
pub fn resolve_with_config(mut args: Args, config: &SetupConfig) -> Result<ResolvedArgs, String> {
let config_name = args.config_name.as_deref().unwrap_or("default");
let default = config.configs.get(config_name);
fn resolve_connection_flags(
c: &ConnectionFlags,
default: Option<&ConfigEntry>,
) -> ResolvedConnectionFlags {
ResolvedConnectionFlags {
ssh: resolve_bool(c.ssh, c.https, default.map(|e| e.ssh), false),
}
}

if args.config_name.is_some() && default.is_none() {
return Err(format!("Configuration '{config_name}' not found"));
fn resolve_run_flags(d: &DiagnosticFlags, default: Option<&ConfigEntry>) -> RunFlags {
RunFlags {
verbose: resolve_bool(d.verbose, d.no_verbose, default.map(|e| e.verbose), false),
timing: resolve_bool(d.timing, d.no_timing, default.map(|e| e.timing), false),
dry_run: resolve_bool(d.dry_run, d.no_dry_run, default.map(|e| e.dry_run), false),
}
}

let ssh = resolve_bool(
args.connection.ssh,
args.connection.https,
default.map(|e| e.ssh),
false,
);
let verbose = resolve_bool(
args.diagnostic.verbose,
args.diagnostic.no_verbose,
default.map(|e| e.verbose),
false,
);
let timing = resolve_bool(
args.diagnostic.timing,
args.diagnostic.no_timing,
default.map(|e| e.timing),
false,
);
let dry_run = resolve_bool(
args.diagnostic.dry_run,
args.diagnostic.no_dry_run,
default.map(|e| e.dry_run),
false,
);
let no_build = resolve_bool(
args.build.no_build,
args.build.build,
default.map(|e| e.no_build),
false,
);
let clean = resolve_bool(
args.build.clean,
args.build.no_clean,
default.map(|e| e.clean),
false,
);
fn resolve_build_flags(
b: BuildFlags,
default: Option<&ConfigEntry>,
) -> Result<ResolvedBuildFlags, String> {
let (watch, no_watch) = resolve_flag_pair(
args.build.watch, args.build.no_watch,
default.map(|e| e.watch), default.map(|e| e.no_watch),
b.watch,
b.no_watch,
default.map(|e| e.watch),
default.map(|e| e.no_watch),
);
let (dev, no_dev) = resolve_flag_pair(
args.build.dev, args.build.no_dev,
default.map(|e| e.dev), default.map(|e| e.no_dev),
b.dev,
b.no_dev,
default.map(|e| e.dev),
default.map(|e| e.no_dev),
);

let cmake_flags = Some(args.build.cmake_flags)
let cmake_flags = Some(b.cmake_flags)
.filter(|f| !f.is_empty())
.unwrap_or_else(|| default.map_or_else(Vec::new, |e| e.cmake_flags.clone()));

let meson_flags = Some(args.build.meson_flags)
let meson_flags = Some(b.meson_flags)
.filter(|f| !f.is_empty())
.unwrap_or_else(|| default.map_or_else(Vec::new, |e| e.meson_flags.clone()));

let repos = args.mono.repos.take();
let profile = args.mono.profile.take();
let mono_repo = args.mono.mono_repo || repos.is_some() || profile.is_some();
Ok(ResolvedBuildFlags {
build_type: match b.build_type {
Some(s) => s.parse::<BuildType>()?,
None => default.map(|e| e.build_type).unwrap_or_default(),
},
build_dir: b
.build_dir
.or_else(|| default.map(|e| e.build_dir.clone()))
.unwrap_or_else(|| "build".to_string()),
build_system: b.build_system,
no_build: resolve_bool(b.no_build, b.build, default.map(|e| e.no_build), false),
clean: resolve_bool(b.clean, b.no_clean, default.map(|e| e.clean), false),
watch,
no_watch,
dev,
no_dev,
cmake_flags,
meson_flags,
})
}

fn resolve_mono_flags(mono: MonoRepoFlags, default: Option<&ConfigEntry>) -> ResolvedMonoFlags {
let repos = mono.repos;
let profile = mono.profile;
let mono_repo = mono.mono_repo || repos.is_some() || profile.is_some();
ResolvedMonoFlags {
mono_repo,
mono_dir: mono
.mono_dir
.or_else(|| default.map(|e| e.mono_dir.clone()))
.unwrap_or_else(|| "build-mono".to_string()),
repos,
profile,
}
}

/// Resolves raw `Args` into `ResolvedArgs` by applying config defaults and CLI overrides.
/// # Errors
/// Returns an error if the named config does not exist in the provided `SetupConfig`.
pub fn resolve_with_config(args: Args, config: &SetupConfig) -> Result<ResolvedArgs, String> {
let config_name = args.config_name.as_deref().unwrap_or("default");
let default = config.configs.get(config_name);

if args.config_name.is_some() && default.is_none() {
return Err(format!("Configuration '{config_name}' not found"));
}

Ok(ResolvedArgs {
repo: args.repo,
yes: args.yes,
connection: ResolvedConnectionFlags { ssh },
diagnostic: RunFlags {
verbose,
timing,
dry_run,
},
build: ResolvedBuildFlags {
build_type: match args.build.build_type {
Some(s) => s.parse::<BuildType>()?,
None => default.map(|e| e.build_type).unwrap_or_default(),
},
build_dir: args
.build
.build_dir
.or_else(|| default.map(|e| e.build_dir.clone()))
.unwrap_or_else(|| "build".to_string()),
build_system: args.build.build_system,
no_build,
clean,
watch,
no_watch,
dev,
no_dev,
cmake_flags,
meson_flags,
},
mono: ResolvedMonoFlags {
mono_repo,
mono_dir: args
.mono
.mono_dir
.or_else(|| default.map(|e| e.mono_dir.clone()))
.unwrap_or_else(|| "build-mono".to_string()),
repos,
profile,
},
connection: resolve_connection_flags(&args.connection, default),
diagnostic: resolve_run_flags(&args.diagnostic, default),
build: resolve_build_flags(args.build, default)?,
mono: resolve_mono_flags(args.mono, default),
})
}
24 changes: 3 additions & 21 deletions src/config/crud.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::{
cli::BuildType::Debug,
config::{format_entry, persist_or_dry_run, save_config, ConfigEntry, SetupConfig},
ctx::{IoCtx, RunFlags},
prompts::confirm_abort,
Expand Down Expand Up @@ -54,26 +53,9 @@ pub fn create_default_config(
} else {
let mut config = SetupConfig::new();
config.path = Some(path.clone());
config.configs.insert(
"default".to_string(),
ConfigEntry {
ssh: false,
build_type: Debug,
build_dir: "build".to_string(),
mono_dir: "build-mono".to_string(),
no_build: false,
clean: false,
verbose: false,
timing: false,
dry_run: false,
watch: false,
no_watch: false,
dev: false,
no_dev: false,
cmake_flags: vec![],
meson_flags: vec![],
},
);
config
.configs
.insert("default".to_string(), ConfigEntry::default());

let path = save_config(&mut config, flags.timing, &mut io.output)?;

Expand Down
25 changes: 24 additions & 1 deletion src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::{collections::HashMap, path::PathBuf};

/// Represents a single named configuration entry.
#[allow(clippy::struct_excessive_bools)]
#[derive(Serialize, Deserialize, Default)]
#[derive(Serialize, Deserialize)]
#[serde(default)]
pub struct ConfigEntry {
/// Use SSH instead of HTTPS for cloning.
pub ssh: bool,
Expand Down Expand Up @@ -80,6 +81,28 @@ impl ConfigEntry {
}
}

impl Default for ConfigEntry {
fn default() -> Self {
Self {
ssh: false,
build_type: BuildType::Debug,
build_dir: "build".to_string(),
mono_dir: "build-mono".to_string(),
no_build: false,
clean: false,
verbose: false,
timing: false,
dry_run: false,
watch: false,
no_watch: false,
dev: false,
no_dev: false,
cmake_flags: vec![],
meson_flags: vec![],
}
}
}

impl From<&ResolvedArgs> for ConfigEntry {
fn from(args: &ResolvedArgs) -> Self {
Self {
Expand Down
38 changes: 8 additions & 30 deletions tests/cli/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,6 @@ use star_setup::{
config::{ConfigEntry, SetupConfig},
};

/* ===== HELPERS ===== */
/// Generates a base `ConfigEntry` with defaults.
fn create_test_config_entry() -> ConfigEntry {
ConfigEntry {
ssh: false,
verbose: false,
build_type: BuildType::Debug,
build_dir: "build".to_string(),
mono_dir: "build-mono".to_string(),
no_build: false,
clean: false,
timing: false,
dry_run: false,
cmake_flags: vec![],
meson_flags: vec![],
watch: false,
no_watch: false,
dev: false,
no_dev: false,
}
}

/// Helper to quickly build a `SetupConfig` with a populated profile entry.
fn config_with_entry(name: &str, entry: ConfigEntry) -> SetupConfig {
let mut config = SetupConfig::new();
Expand Down Expand Up @@ -124,7 +102,7 @@ fn test_resolve_with_config_applies_config_defaults() {
no_build: true,
clean: true,
cmake_flags: vec!["-DTEST=ON".to_string()],
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand All @@ -139,7 +117,7 @@ fn test_resolve_with_config_applies_config_defaults() {

#[test]
fn test_resolve_with_config_cli_overrides_config() {
let config = config_with_entry("default", create_test_config_entry());
let config = config_with_entry("default", ConfigEntry::default());

let mut args = default_args();
args.connection.ssh = true;
Expand Down Expand Up @@ -188,7 +166,7 @@ fn test_resolve_with_config_named_config_pulls_correct_values() {
build_type: BuildType::RelWithDebInfo,
build_dir: "out".to_string(),
clean: true,
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand All @@ -208,7 +186,7 @@ fn test_resolve_with_config_cli_cmake_flags_not_overwritten_by_config() {
"default",
ConfigEntry {
cmake_flags: vec!["-DCONFIG_FLAG=ON".to_string()],
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand All @@ -228,7 +206,7 @@ fn test_resolve_with_config_negative_flags_override_config() {
verbose: true,
no_build: true,
clean: true,
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand All @@ -252,7 +230,7 @@ fn test_resolve_with_config_watch_dev_defaults_from_config() {
ConfigEntry {
watch: true,
dev: true,
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand All @@ -270,7 +248,7 @@ fn test_resolve_with_config_cli_negatives_override_watch_dev_config() {
ConfigEntry {
watch: true,
dev: true,
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand All @@ -292,7 +270,7 @@ fn test_resolve_with_config_cli_positives_override_no_watch_no_dev_config() {
ConfigEntry {
no_watch: true,
no_dev: true,
..create_test_config_entry()
..ConfigEntry::default()
},
);

Expand Down
2 changes: 0 additions & 2 deletions tests/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ mod common;
mod crud;
#[path = "config/display.rs"]
mod display;
#[path = "config/fixtures.rs"]
mod fixtures;
#[path = "config/io.rs"]
mod io;
#[path = "config/types.rs"]
Expand Down
Loading
Loading