Skip to content

Commit 86c10e0

Browse files
committed
feat: add command hbox config
1 parent 2d26ec8 commit 86c10e0

File tree

4 files changed

+109
-2
lines changed

4 files changed

+109
-2
lines changed

ROADMAP.md

-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ List of ideas I want to implement in the future.
3030
- Add version to config files to specify their formats to allow check for future compatibility
3131
- [Experimental] Allow forced flags in docker run, based on pattern matching
3232
- New commands
33-
- Add `hbox config` to support all `config.json` options
34-
- example: `hbox config logs.enabled true`
3533
- Add auto update `hbox upgrade`
3634
- Add `hbox update` to update index
3735
- Add `hbox register` to register a package, even with custom image

src/cli.rs

+11
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,16 @@ enum Commands {
7171
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
7272
subcommand: Vec<String>,
7373
},
74+
75+
/// Configure settings
76+
#[command(alias = "configure")]
77+
Config {
78+
/// Configuration path
79+
path: String,
80+
81+
/// Configuration value
82+
value: Option<String>,
83+
},
7484
}
7585

7686
pub fn run() {
@@ -97,6 +107,7 @@ pub fn run() {
97107
Commands::Remove { name, version } => remove_package(name.clone(), version.clone()),
98108
Commands::Use { name, version } => use_package_version(name.clone(), version.clone()),
99109
Commands::Run { name, subcommand } => run_package(name.clone(), subcommand.clone()),
110+
Commands::Config { path, value } => configure_setting(path.clone(), value.clone()),
100111
};
101112

102113
if let Err(e) = result {

src/commands.rs

+10
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,16 @@ pub fn run_package(name: String, subcommand: Vec<String>) -> Result<(), Box<dyn
179179
}
180180
}
181181

182+
pub fn configure_setting(path: String, value: Option<String>) -> Result<(), Box<dyn Error>> {
183+
if let Some(value) = value {
184+
UserConfig::write_config_value(&path, &value)?;
185+
} else {
186+
UserConfig::read_config_value(&path)?;
187+
}
188+
189+
Ok(())
190+
}
191+
182192
fn do_add_package(name: &String, version: &String, package: Package) -> Result<(), Box<dyn Error>> {
183193
let mut new_package = package.clone();
184194
new_package.versions.current = version.clone();

src/configs/user.rs

+88
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use log::LevelFilter;
22
use serde::{Deserialize, Serialize};
3+
use serde_json::Value;
34
use std::error::Error;
45
use std::str::FromStr;
56

@@ -19,6 +20,81 @@ impl UserConfig {
1920
Ok(root)
2021
}
2122
}
23+
24+
pub fn save(root: Root) -> Result<(), Box<dyn Error>> {
25+
let config = AppConfig::load();
26+
save_json(&root, &config.config_file_path())?;
27+
Ok(())
28+
}
29+
30+
pub fn write_config_value(path: &str, value: &str) -> Result<(), Box<dyn Error>> {
31+
let mut root: Value = serde_json::to_value(Self::load()?)?;
32+
let (current, last_part) = Self::traverse_path(&mut root, path)?;
33+
34+
let parsed_value =
35+
if value.eq_ignore_ascii_case("true") || value.eq_ignore_ascii_case("false") {
36+
Value::Bool(value.parse::<bool>()?)
37+
} else if let Ok(int_value) = value.parse::<i64>() {
38+
Value::Number(int_value.into())
39+
} else {
40+
Value::String(value.to_string())
41+
};
42+
43+
match current {
44+
Value::Object(map) => {
45+
if map.contains_key(&last_part) {
46+
map.insert(last_part, parsed_value);
47+
} else {
48+
return Err(format!("Invalid configuration key: {}", path).into());
49+
}
50+
}
51+
_ => return Err("Invalid configuration path".into()),
52+
}
53+
54+
let updated_root: Root = serde_json::from_value(root)?;
55+
Self::save(updated_root)?;
56+
57+
Ok(())
58+
}
59+
60+
pub fn read_config_value(path: &str) -> Result<(), Box<dyn Error>> {
61+
let mut root: Value = serde_json::to_value(Self::load()?)?;
62+
let (current, last_part) = Self::traverse_path(&mut root, path)?;
63+
64+
match current.get(&last_part) {
65+
Some(value) => {
66+
if value.is_object() {
67+
println!("{}", serde_json::to_string_pretty(&value)?);
68+
} else {
69+
println!("{}", value);
70+
}
71+
}
72+
None => return Err(format!("Invalid configuration key: {}", path).into()),
73+
}
74+
75+
Ok(())
76+
}
77+
78+
fn traverse_path<'a>(
79+
root: &'a mut Value,
80+
path: &str,
81+
) -> Result<(&'a mut Value, String), Box<dyn Error>> {
82+
let parts: Vec<&str> = path.split('.').collect();
83+
if parts.is_empty() {
84+
return Err("Invalid configuration path".into());
85+
}
86+
87+
let mut current = root;
88+
for part in parts.iter().take(parts.len() - 1) {
89+
current = current.get_mut(*part).ok_or("Invalid configuration path")?;
90+
}
91+
92+
let last_part = parts
93+
.last()
94+
.ok_or("Invalid configuration path")?
95+
.to_string();
96+
Ok((current, last_part))
97+
}
2298
}
2399

24100
#[derive(Serialize, Deserialize, Debug, Default)]
@@ -50,6 +126,18 @@ impl Engine {
50126
}
51127
}
52128

129+
impl FromStr for Engine {
130+
type Err = ();
131+
132+
fn from_str(input: &str) -> Result<Engine, Self::Err> {
133+
match input.to_lowercase().as_str() {
134+
"docker" => Ok(Engine::Docker),
135+
"podman" => Ok(Engine::Podman),
136+
_ => Err(()),
137+
}
138+
}
139+
}
140+
53141
#[derive(Serialize, Deserialize, Debug)]
54142
pub struct Logs {
55143
#[serde(default)]

0 commit comments

Comments
 (0)