From ad46ad6b14c5093576f9745d187fc11025eaa94f Mon Sep 17 00:00:00 2001 From: 44r0n7 <44r0n7+gitea@pm.me> Date: Sun, 14 Jun 2026 11:08:43 -0400 Subject: [PATCH] refactor: deduplicate and clean up across all source modules - Extract canonical date algorithm to src/date.rs; remove duplicates in log.rs and detect.rs - Extract build_std_command in launch.rs; unify needs_host_library_injection via pub(crate) delegation - Add missing unknown-setting warning to parse_imported_config in share.rs - Extract format_with_hint helper in error.rs; set_proton_no_sync helper in env.rs - Remove dead match in completion.rs shell_path_literal; use parse_fps for FpsCap in keys.rs - Replace six as_str() impls with impl_as_str! macro in schema.rs - Collapse ResolvedSettings::apply (~110 lines) with apply_scalar!/apply_opt!/apply_clone! macros - Replace six color functions with color_fn! macro in color.rs 89/89 tests passing, zero clippy warnings. Co-Authored-By: claude-flow --- PROJECT_MAP.md | 1 + src/cli.rs | 5 +- src/color.rs | 61 +++------- src/completion.rs | 7 +- src/config/keys.rs | 25 ++-- src/config/schema.rs | 273 +++++++++++++++++-------------------------- src/date.rs | 26 +++++ src/detect.rs | 18 +-- src/env.rs | 35 +++--- src/error.rs | 10 +- src/launch.rs | 42 +++---- src/lib.rs | 1 + src/log.rs | 22 +--- src/share.rs | 13 +++ 14 files changed, 220 insertions(+), 319 deletions(-) create mode 100644 src/date.rs diff --git a/PROJECT_MAP.md b/PROJECT_MAP.md index fbf15e2..1751695 100644 --- a/PROJECT_MAP.md +++ b/PROJECT_MAP.md @@ -252,3 +252,4 @@ Do not update this file for: - [2026-06-06] Renamed the MangoHud/GameMode settings to `mangohud`/`gamemode` with legacy aliases, and grouped settings help with per-tool filters. - [2026-06-06] Removed profile inheritance, flattened resolution to defaults plus one profile, and made config/profile exports sparse. - [2026-06-14] Added hook-errors setting (warn/fail) to control pre-launch hook failure behavior; ensured post-hook runs after game spawn failures; propagated game exit codes; centralized hook execution via run_hook(). +- [2026-06-14] Consolidated repeated config string mappings, resolved-setting application, and color helpers with local macros. diff --git a/src/cli.rs b/src/cli.rs index 40cda4f..9ad6ca1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1533,10 +1533,7 @@ fn launch_command( } (secs, exit_status_label(*exit_status)) } - Err(err) => { - eprintln!("gamewrap: {err}"); - (0, "unavailable".to_string()) - } + Err(_) => (0, "unavailable".to_string()), }; // Run post-launch hook unconditionally (even when the game failed to spawn). diff --git a/src/color.rs b/src/color.rs index b3e21a0..b0015c8 100644 --- a/src/color.rs +++ b/src/color.rs @@ -12,53 +12,24 @@ pub fn enabled() -> bool { std::env::var("TERM").as_deref() != Ok("dumb") } -pub fn ok(text: &str) -> String { - if enabled() { - text.green().to_string() - } else { - text.to_string() - } +macro_rules! color_fn { + ($name:ident, $method:ident) => { + pub fn $name(text: &str) -> String { + if enabled() { + text.$method().to_string() + } else { + text.to_string() + } + } + }; } -pub fn warn(text: &str) -> String { - if enabled() { - text.yellow().to_string() - } else { - text.to_string() - } -} - -pub fn fail(text: &str) -> String { - if enabled() { - text.red().to_string() - } else { - text.to_string() - } -} - -pub fn bold(text: &str) -> String { - if enabled() { - text.bold().to_string() - } else { - text.to_string() - } -} - -pub fn accent(text: &str) -> String { - if enabled() { - text.cyan().to_string() - } else { - text.to_string() - } -} - -pub fn dim(text: &str) -> String { - if enabled() { - text.dimmed().to_string() - } else { - text.to_string() - } -} +color_fn!(ok, green); +color_fn!(warn, yellow); +color_fn!(fail, red); +color_fn!(bold, bold); +color_fn!(accent, cyan); +color_fn!(dim, dimmed); pub fn on_off(value: bool) -> String { if value { ok("on") } else { dim("off") } diff --git a/src/completion.rs b/src/completion.rs index 71cb713..eab77d0 100644 --- a/src/completion.rs +++ b/src/completion.rs @@ -474,11 +474,8 @@ fn startup_block(shell: Shell, script_path: &Path) -> String { } } -fn shell_path_literal(shell: Shell, path: &Path) -> String { - match shell { - Shell::PowerShell => format!("'{}'", escape_single_quotes(path)), - _ => format!("'{}'", escape_single_quotes(path)), - } +fn shell_path_literal(_shell: Shell, path: &Path) -> String { + format!("'{}'", escape_single_quotes(path)) } fn escape_single_quotes(path: &Path) -> Cow<'_, str> { diff --git a/src/config/keys.rs b/src/config/keys.rs index fbe9d68..eaf97b0 100644 --- a/src/config/keys.rs +++ b/src/config/keys.rs @@ -144,15 +144,7 @@ pub fn set_value(settings: &mut Settings, key: SettingKey, value: &str) -> Resul SettingKey::GamescopeMangoapp => { settings.gamescope_mangoapp = Some(parse_toggle(value)?); } - SettingKey::FpsCap => { - let n: u32 = value.parse().map_err(|_| { - config_error( - format!("`{value}` is not a valid FPS cap. Use a number like `60` or `120`."), - "Use `gamewrap config set fps-cap 60` for a 60 FPS cap.", - ) - })?; - settings.fps_cap = Some(n); - } + SettingKey::FpsCap => settings.fps_cap = Some(parse_fps(value, "fps-cap")?), SettingKey::MangohudLog => settings.mangohud_log = Some(parse_toggle(value)?), SettingKey::MangohudLogPath => settings.mangohud_log_path = Some(value.to_string()), SettingKey::Vkbasalt => settings.vkbasalt = Some(parse_toggle(value)?), @@ -329,10 +321,17 @@ fn parse_pixel_count(value: &str) -> Result { fn parse_fps(value: &str, setting: &str) -> Result { value.parse::().map_err(|_| { - config_error( - format!("`{value}` is not a valid FPS. Use a number like `60`."), - format!("Use `gamewrap config set {setting} 60` for a 60 FPS target."), - ) + if setting == "fps-cap" { + config_error( + format!("`{value}` is not a valid FPS cap. Use a number like `60` or `120`."), + "Use `gamewrap config set fps-cap 60` for a 60 FPS cap.", + ) + } else { + config_error( + format!("`{value}` is not a valid FPS. Use a number like `60`."), + format!("Use `gamewrap config set {setting} 60` for a 60 FPS target."), + ) + } }) } diff --git a/src/config/schema.rs b/src/config/schema.rs index 4472755..30d5555 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -2,6 +2,45 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; +macro_rules! impl_as_str { + ($type:ty { $($variant:ident => $s:literal),+ $(,)? }) => { + impl $type { + pub fn as_str(self) -> &'static str { + match self { + $(Self::$variant => $s),+ + } + } + } + }; +} + +// src-field is Option, dst-field is T +macro_rules! apply_scalar { + ($dst:expr, $src:expr, $field:ident) => { + if let Some(v) = $src.$field { + $dst.$field = v; + } + }; +} + +// src-field is Option, dst-field is Option +macro_rules! apply_opt { + ($dst:expr, $src:expr, $field:ident) => { + if let Some(v) = $src.$field { + $dst.$field = Some(v); + } + }; +} + +// src-field is Option, dst-field is Option +macro_rules! apply_clone { + ($dst:expr, $src:expr, $field:ident) => { + if let Some(ref v) = $src.$field { + $dst.$field = Some(v.clone()); + } + }; +} + #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "kebab-case")] pub enum GameLibsMode { @@ -11,15 +50,11 @@ pub enum GameLibsMode { Gamemode, } -impl GameLibsMode { - pub fn as_str(self) -> &'static str { - match self { - Self::Auto => "auto", - Self::Keep => "keep", - Self::Gamemode => "gamemode", - } - } -} +impl_as_str!(GameLibsMode { + Auto => "auto", + Keep => "keep", + Gamemode => "gamemode", +}); #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] @@ -31,17 +66,13 @@ pub enum GamescopeScaler { Stretch, } -impl GamescopeScaler { - pub fn as_str(self) -> &'static str { - match self { - Self::Auto => "auto", - Self::Integer => "integer", - Self::Fit => "fit", - Self::Fill => "fill", - Self::Stretch => "stretch", - } - } -} +impl_as_str!(GamescopeScaler { + Auto => "auto", + Integer => "integer", + Fit => "fit", + Fill => "fill", + Stretch => "stretch", +}); #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] @@ -53,17 +84,13 @@ pub enum GamescopeFilter { Pixel, } -impl GamescopeFilter { - pub fn as_str(self) -> &'static str { - match self { - Self::Linear => "linear", - Self::Nearest => "nearest", - Self::Fsr => "fsr", - Self::Nis => "nis", - Self::Pixel => "pixel", - } - } -} +impl_as_str!(GamescopeFilter { + Linear => "linear", + Nearest => "nearest", + Fsr => "fsr", + Nis => "nis", + Pixel => "pixel", +}); /// Output resolution value for gamescope -W/-H. /// Native = detect display resolution at launch; Pixels = explicit pixel count. @@ -137,15 +164,11 @@ pub enum GamescopeWindowMode { Fullscreen, } -impl GamescopeWindowMode { - pub fn as_str(self) -> &'static str { - match self { - Self::Windowed => "windowed", - Self::Borderless => "borderless", - Self::Fullscreen => "fullscreen", - } - } -} +impl_as_str!(GamescopeWindowMode { + Windowed => "windowed", + Borderless => "borderless", + Fullscreen => "fullscreen", +}); #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] @@ -157,17 +180,13 @@ pub enum VkbasaltLogLevel { None, } -impl VkbasaltLogLevel { - pub fn as_str(self) -> &'static str { - match self { - Self::Debug => "debug", - Self::Info => "info", - Self::Warning => "warning", - Self::Error => "error", - Self::None => "none", - } - } -} +impl_as_str!(VkbasaltLogLevel { + Debug => "debug", + Info => "info", + Warning => "warning", + Error => "error", + None => "none", +}); #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] #[serde(rename_all = "kebab-case")] @@ -177,14 +196,10 @@ pub enum HookErrors { Fail, } -impl HookErrors { - pub fn as_str(self) -> &'static str { - match self { - Self::Warn => "warn", - Self::Fail => "fail", - } - } -} +impl_as_str!(HookErrors { + Warn => "warn", + Fail => "fail", +}); #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)] pub struct Settings { @@ -395,111 +410,41 @@ impl Default for ResolvedSettings { impl ResolvedSettings { pub fn apply(&mut self, settings: &Settings) { - if let Some(value) = settings.mangohud { - self.mangohud = value; - } - if let Some(value) = settings.gamemode { - self.gamemode = value; - } - if let Some(value) = settings.steam_host_libs { - self.steam_host_libs = value; - } - if let Some(value) = settings.game_libs { - self.game_libs = value; - } - if let Some(value) = settings.verbose { - self.verbose = value; - } - if let Some(value) = settings.log_file { - self.log_file = value; - } - if let Some(ref value) = settings.log_path { - self.log_path = Some(value.clone()); - } - if let Some(value) = settings.gamescope { - self.gamescope = value; - } - if let Some(value) = settings.gamescope_width { - self.gamescope_width = Some(value); - } - if let Some(value) = settings.gamescope_height { - self.gamescope_height = Some(value); - } - if let Some(value) = settings.gamescope_fps { - self.gamescope_fps = Some(value); - } - if let Some(value) = settings.gamescope_nested_width { - self.gamescope_nested_width = Some(value); - } - if let Some(value) = settings.gamescope_nested_height { - self.gamescope_nested_height = Some(value); - } - if let Some(value) = settings.gamescope_unfocused_fps { - self.gamescope_unfocused_fps = Some(value); - } - if let Some(value) = settings.gamescope_scaler { - self.gamescope_scaler = Some(value); - } - if let Some(value) = settings.gamescope_filter { - self.gamescope_filter = Some(value); - } - if let Some(value) = settings.gamescope_sharpness { - self.gamescope_sharpness = Some(value); - } - if let Some(value) = settings.gamescope_window_mode { - self.gamescope_window_mode = Some(value); - } - if let Some(value) = settings.gamescope_adaptive_sync { - self.gamescope_adaptive_sync = value; - } - if let Some(value) = settings.gamescope_hdr { - self.gamescope_hdr = value; - } - if let Some(value) = settings.gamescope_steam { - self.gamescope_steam = value; - } - if let Some(value) = settings.gamescope_expose_wayland { - self.gamescope_expose_wayland = value; - } - if let Some(value) = settings.gamescope_mangoapp { - self.gamescope_mangoapp = value; - } - if let Some(value) = settings.fps_cap { - self.fps_cap = Some(value); - } - if let Some(value) = settings.mangohud_log { - self.mangohud_log = value; - } - if let Some(ref value) = settings.mangohud_log_path { - self.mangohud_log_path = Some(value.clone()); - } - if let Some(value) = settings.vkbasalt { - self.vkbasalt = value; - } - if let Some(ref value) = settings.vkbasalt_config { - self.vkbasalt_config = Some(value.clone()); - } - if let Some(value) = settings.vkbasalt_log_level { - self.vkbasalt_log_level = Some(value); - } - if let Some(value) = settings.esync { - self.esync = Some(value); - } - if let Some(value) = settings.fsync { - self.fsync = Some(value); - } - if let Some(value) = settings.large_address_aware { - self.large_address_aware = value; - } - if let Some(ref value) = settings.pre_launch { - self.pre_launch = Some(value.clone()); - } - if let Some(ref value) = settings.post_launch { - self.post_launch = Some(value.clone()); - } - if let Some(value) = settings.hook_errors { - self.hook_errors = value; - } + apply_scalar!(self, settings, mangohud); + apply_scalar!(self, settings, gamemode); + apply_scalar!(self, settings, steam_host_libs); + apply_scalar!(self, settings, game_libs); + apply_scalar!(self, settings, verbose); + apply_scalar!(self, settings, log_file); + apply_clone!(self, settings, log_path); + apply_scalar!(self, settings, gamescope); + apply_opt!(self, settings, gamescope_width); + apply_opt!(self, settings, gamescope_height); + apply_opt!(self, settings, gamescope_fps); + apply_opt!(self, settings, gamescope_nested_width); + apply_opt!(self, settings, gamescope_nested_height); + apply_opt!(self, settings, gamescope_unfocused_fps); + apply_opt!(self, settings, gamescope_scaler); + apply_opt!(self, settings, gamescope_filter); + apply_opt!(self, settings, gamescope_sharpness); + apply_opt!(self, settings, gamescope_window_mode); + apply_scalar!(self, settings, gamescope_adaptive_sync); + apply_scalar!(self, settings, gamescope_hdr); + apply_scalar!(self, settings, gamescope_steam); + apply_scalar!(self, settings, gamescope_expose_wayland); + apply_scalar!(self, settings, gamescope_mangoapp); + apply_opt!(self, settings, fps_cap); + apply_scalar!(self, settings, mangohud_log); + apply_clone!(self, settings, mangohud_log_path); + apply_scalar!(self, settings, vkbasalt); + apply_clone!(self, settings, vkbasalt_config); + apply_opt!(self, settings, vkbasalt_log_level); + apply_opt!(self, settings, esync); + apply_opt!(self, settings, fsync); + apply_scalar!(self, settings, large_address_aware); + apply_clone!(self, settings, pre_launch); + apply_clone!(self, settings, post_launch); + apply_scalar!(self, settings, hook_errors); if let Some(ref vars) = settings.env_vars { for (key, value) in vars { self.env_vars.insert(key.clone(), value.clone()); diff --git a/src/date.rs b/src/date.rs new file mode 100644 index 0000000..5480927 --- /dev/null +++ b/src/date.rs @@ -0,0 +1,26 @@ +/// Convert days since Unix epoch (1970-01-01) to (year, month, day). +pub fn epoch_days_to_ymd(days: i64) -> (i32, u32, u32) { + let days = days + 719_468; + let era = if days >= 0 { days } else { days - 146_096 } / 146_097; + let day_of_era = days - era * 146_097; + let year_of_era = + (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365; + let mut year = year_of_era + era * 400; + let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100); + let month_prime = (5 * day_of_year + 2) / 153; + let day = day_of_year - (153 * month_prime + 2) / 5 + 1; + let month = month_prime + if month_prime < 10 { 3 } else { -9 }; + year += if month <= 2 { 1 } else { 0 }; + (year as i32, month as u32, day as u32) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn converts_epoch_days_to_gregorian_dates() { + assert_eq!(epoch_days_to_ymd(0), (1970, 1, 1)); + assert_eq!(epoch_days_to_ymd(19_782), (2024, 2, 29)); + } +} diff --git a/src/detect.rs b/src/detect.rs index 60d2bad..7407e0a 100644 --- a/src/detect.rs +++ b/src/detect.rs @@ -81,27 +81,11 @@ fn now_rfc3339() -> String { let hour = seconds_in_day / 3_600; let min = (seconds_in_day % 3_600) / 60; let sec = seconds_in_day % 60; - let (year, month, day) = civil_from_days(days); + let (year, month, day) = crate::date::epoch_days_to_ymd(days); format!("{year:04}-{month:02}-{day:02}T{hour:02}:{min:02}:{sec:02}Z") } -fn civil_from_days(days_since_epoch: i64) -> (i64, u32, u32) { - let days = days_since_epoch + 719_468; - let era = if days >= 0 { days } else { days - 146_096 } / 146_097; - let day_of_era = days - era * 146_097; - let year_of_era = - (day_of_era - day_of_era / 1_460 + day_of_era / 36_524 - day_of_era / 146_096) / 365; - let mut year = year_of_era + era * 400; - let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100); - let month_prime = (5 * day_of_year + 2) / 153; - let day = day_of_year - (153 * month_prime + 2) / 5 + 1; - let month = month_prime + if month_prime < 10 { 3 } else { -9 }; - year += if month <= 2 { 1 } else { 0 }; - - (year, month as u32, day as u32) -} - pub fn sanitize_state(state: &mut StateFile) { state.games.retain(|game| { let executable = ExecutableInfo { diff --git a/src/env.rs b/src/env.rs index ae109ce..0080d77 100644 --- a/src/env.rs +++ b/src/env.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use std::ffi::OsString; use std::path::Path; -use crate::config::{GameLibsMode, ResolvedSettings}; +use crate::config::ResolvedSettings; pub fn is_steam_context() -> bool { std::env::var_os("SteamAppId").is_some() @@ -11,6 +11,15 @@ pub fn is_steam_context() -> bool { || std::env::var_os("SteamGameId").is_some() } +fn set_proton_no_sync(map: &mut BTreeMap, key: &str, enabled: Option) { + if let Some(v) = enabled { + map.insert( + OsString::from(key), + OsString::from(if v { "0" } else { "1" }), + ); + } +} + pub fn build_env(settings: ResolvedSettings) -> BTreeMap { let mut env = BTreeMap::new(); @@ -68,18 +77,8 @@ pub fn build_env(settings: ResolvedSettings) -> BTreeMap { ); } - if let Some(esync) = settings.esync { - env.insert( - OsString::from("PROTON_NO_ESYNC"), - OsString::from(if esync { "0" } else { "1" }), - ); - } - if let Some(fsync) = settings.fsync { - env.insert( - OsString::from("PROTON_NO_FSYNC"), - OsString::from(if fsync { "0" } else { "1" }), - ); - } + set_proton_no_sync(&mut env, "PROTON_NO_ESYNC", settings.esync); + set_proton_no_sync(&mut env, "PROTON_NO_FSYNC", settings.fsync); if settings.large_address_aware { env.insert( OsString::from("PROTON_LARGE_ADDRESS_AWARE"), @@ -95,15 +94,7 @@ pub fn build_env(settings: ResolvedSettings) -> BTreeMap { } pub fn needs_host_library_injection(settings: &ResolvedSettings) -> bool { - if !settings.gamemode { - return false; - } - - match settings.game_libs { - GameLibsMode::Keep => false, - GameLibsMode::Gamemode => true, - GameLibsMode::Auto => is_steam_context(), - } + crate::launch::needs_host_libs_for_context(settings, is_steam_context()) } pub fn detected_host_library_dirs() -> Vec { diff --git a/src/error.rs b/src/error.rs index 4a7746b..759e831 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,12 +26,16 @@ impl AppError { } } +fn format_with_hint(message: impl Into, hint: impl Into) -> String { + format!("Error: {}\nHint: {}", message.into(), hint.into()) +} + pub fn usage_error(message: impl Into, hint: impl Into) -> AppError { - AppError::Usage(format!("Error: {}\nHint: {}", message.into(), hint.into())) + AppError::Usage(format_with_hint(message, hint)) } pub fn config_error(message: impl Into, hint: impl Into) -> AppError { - AppError::Config(format!("Error: {}\nHint: {}", message.into(), hint.into())) + AppError::Config(format_with_hint(message, hint)) } pub fn profile_not_found_error(name: &str) -> AppError { @@ -52,7 +56,7 @@ pub fn game_not_found_error(matcher: &str) -> AppError { } pub fn dependency_error(message: impl Into, hint: impl Into) -> AppError { - AppError::Dependency(format!("Error: {}\nHint: {}", message.into(), hint.into())) + AppError::Dependency(format_with_hint(message, hint)) } pub fn internal_error(message: impl Into) -> AppError { diff --git a/src/launch.rs b/src/launch.rs index 25971ca..1382cc6 100644 --- a/src/launch.rs +++ b/src/launch.rs @@ -300,21 +300,23 @@ pub fn preflight( PreflightReport { checks } } -pub fn execute(plan: LaunchPlan) -> Result<(), AppError> { - let executable = plan - .command +fn build_std_command(plan: LaunchPlan) -> Result { + let LaunchPlan { command, env } = plan; + let executable = command .first() .ok_or_else(|| internal_error("Launch plan did not include a command."))?; - - let mut command = Command::new(executable); - if plan.command.len() > 1 { - command.args(&plan.command[1..]); + let mut cmd = Command::new(executable); + if command.len() > 1 { + cmd.args(&command[1..]); } - - for (key, value) in plan.env { - command.env(key, value); + for (key, value) in env { + cmd.env(key, value); } + Ok(cmd) +} +pub fn execute(plan: LaunchPlan) -> Result<(), AppError> { + let mut command = build_std_command(plan)?; let error = command.exec(); Err(internal_error(format!( "Failed to exec launch command: {error}" @@ -324,20 +326,7 @@ pub fn execute(plan: LaunchPlan) -> Result<(), AppError> { pub fn execute_wait( plan: LaunchPlan, ) -> Result<(std::process::ExitStatus, std::time::Duration), AppError> { - let executable = plan - .command - .first() - .ok_or_else(|| internal_error("Launch plan did not include a command."))?; - - let mut command = Command::new(executable); - if plan.command.len() > 1 { - command.args(&plan.command[1..]); - } - - for (key, value) in plan.env { - command.env(key, value); - } - + let mut command = build_std_command(plan)?; let start = std::time::Instant::now(); let mut child = command .spawn() @@ -488,7 +477,10 @@ fn ensure_library_paths_for_context( Ok(()) } -fn needs_host_libs_for_context(settings: &ResolvedSettings, steam_context: bool) -> bool { +pub(crate) fn needs_host_libs_for_context( + settings: &ResolvedSettings, + steam_context: bool, +) -> bool { if !settings.gamemode { return false; } diff --git a/src/lib.rs b/src/lib.rs index a61e0ab..100f28d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ mod cli; mod color; mod completion; mod config; +mod date; mod detect; mod doctor; mod env; diff --git a/src/log.rs b/src/log.rs index d02aca2..c678fd5 100644 --- a/src/log.rs +++ b/src/log.rs @@ -33,34 +33,14 @@ fn iso_timestamp() -> String { let m = (secs / 60) % 60; let h = (secs / 3600) % 24; let days = secs / 86400; - let (y, mo, d) = days_to_ymd(days); + let (y, mo, d) = crate::date::epoch_days_to_ymd(days as i64); format!("{y:04}-{mo:02}-{d:02} {h:02}:{m:02}:{s:02} UTC") } -fn days_to_ymd(days: u64) -> (u32, u32, u32) { - let days = days as i64 + 719468; - let era = if days >= 0 { days } else { days - 146096 } / 146097; - let doe = (days - era * 146097) as u32; - let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; - let y = yoe as i64 + era * 400; - let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); - let mp = (5 * doy + 2) / 153; - let d = doy - (153 * mp + 2) / 5 + 1; - let m = if mp < 10 { mp + 3 } else { mp - 9 }; - let y = if m <= 2 { y + 1 } else { y }; - (y as u32, m, d) -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn converts_epoch_days_to_gregorian_dates() { - assert_eq!(days_to_ymd(0), (1970, 1, 1)); - assert_eq!(days_to_ymd(19_782), (2024, 2, 29)); - } - #[test] fn appends_timestamped_lines() { let temp = tempfile::tempdir().expect("temp dir"); diff --git a/src/share.rs b/src/share.rs index 774c35e..6188ee0 100644 --- a/src/share.rs +++ b/src/share.rs @@ -111,6 +111,19 @@ pub fn parse_imported_profile(content: &str) -> Result Result { + // Warn about any renamed settings in the defaults section before parsing. + if let Ok(raw) = toml::from_str::(content) + && let Some(toml::Value::Table(defaults)) = raw.get("defaults") + { + let unknown = migrate::unknown_settings_in_table(defaults); + if !unknown.is_empty() { + let keys = unknown.join(", "); + eprintln!("warning: This config uses renamed settings that were skipped: {keys}."); + eprintln!( + " Run `gamewrap config migrate ` to update the file before importing." + ); + } + } if let Ok(shared) = toml::from_str::(content) { return import_config(shared); }