release/0.4.2 #11
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -853,7 +853,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mould"
|
name = "mould"
|
||||||
version = "0.4.0"
|
version = "0.4.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
16
Cargo.toml
16
Cargo.toml
@@ -1,9 +1,3 @@
|
|||||||
[package]
|
|
||||||
edition = "2024"
|
|
||||||
name = "mould"
|
|
||||||
version = "0.4.0"
|
|
||||||
authors = ["Nils Pukropp <nils@narl.io>"]
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "mould"
|
name = "mould"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
@@ -22,12 +16,18 @@ toml = "1.0.6"
|
|||||||
tui-input = "0.15.0"
|
tui-input = "0.15.0"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4.6.0"
|
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
version = "4.6.0"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0.228"
|
|
||||||
features = ["derive"]
|
features = ["derive"]
|
||||||
|
version = "1.0.228"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.27.0"
|
tempfile = "3.27.0"
|
||||||
|
|
||||||
|
[package]
|
||||||
|
authors = ["Nils Pukropp <nils@narl.io>"]
|
||||||
|
edition = "2024"
|
||||||
|
name = "mould"
|
||||||
|
version = "0.4.1"
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ mould config.template.json -o config.json
|
|||||||
- `gg`: Jump to the top
|
- `gg`: Jump to the top
|
||||||
- `G`: Jump to the bottom
|
- `G`: Jump to the bottom
|
||||||
- `i`: Edit the value of the currently selected key (Enter Insert Mode)
|
- `i`: Edit the value of the currently selected key (Enter Insert Mode)
|
||||||
|
- `o`: Append a new item to the current array
|
||||||
|
- `O`: Prepend a new item to the current array
|
||||||
- `a`: Add missing value from template to active config
|
- `a`: Add missing value from template to active config
|
||||||
- `/`: Search for configuration keys (Jump to matches)
|
- `/`: Search for configuration keys (Jump to matches)
|
||||||
- `n`: Jump to the next search match
|
- `n`: Jump to the next search match
|
||||||
|
|||||||
@@ -39,3 +39,5 @@ next_match = "n"
|
|||||||
previous_match = "N"
|
previous_match = "N"
|
||||||
jump_top = "gg"
|
jump_top = "gg"
|
||||||
jump_bottom = "G"
|
jump_bottom = "G"
|
||||||
|
append_item = "o"
|
||||||
|
prepend_item = "O"
|
||||||
|
|||||||
71
src/app.rs
71
src/app.rs
@@ -164,4 +164,75 @@ impl App {
|
|||||||
self.commit_input();
|
self.commit_input();
|
||||||
self.mode = Mode::Normal;
|
self.mode = Mode::Normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds a new item to an array if the selected item is part of one.
|
||||||
|
pub fn add_array_item(&mut self, after: bool) {
|
||||||
|
if self.vars.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (base, idx, depth) = {
|
||||||
|
let selected_item = &self.vars[self.selected];
|
||||||
|
if selected_item.is_group {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let path = &selected_item.path;
|
||||||
|
if let Some((base, idx)) = parse_index(path) {
|
||||||
|
(base.to_string(), idx, selected_item.depth)
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_idx = if after { idx + 1 } else { idx };
|
||||||
|
let insert_pos = if after {
|
||||||
|
self.selected + 1
|
||||||
|
} else {
|
||||||
|
self.selected
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Shift all items in this array that have index >= new_idx
|
||||||
|
for var in self.vars.iter_mut() {
|
||||||
|
if var.path.starts_with(&base) {
|
||||||
|
if let Some((b, i)) = parse_index(&var.path) {
|
||||||
|
if b == base && i >= new_idx {
|
||||||
|
var.path = format!("{}[{}]", base, i + 1);
|
||||||
|
// Also update key if it was just the index
|
||||||
|
if var.key == format!("[{}]", i) {
|
||||||
|
var.key = format!("[{}]", i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Insert new item
|
||||||
|
let new_item = ConfigItem {
|
||||||
|
key: format!("[{}]", new_idx),
|
||||||
|
path: format!("{}[{}]", base, new_idx),
|
||||||
|
value: Some("".to_string()),
|
||||||
|
template_value: None,
|
||||||
|
default_value: None,
|
||||||
|
depth,
|
||||||
|
is_group: false,
|
||||||
|
status: crate::format::ItemStatus::Modified,
|
||||||
|
value_type: crate::format::ValueType::String,
|
||||||
|
};
|
||||||
|
self.vars.insert(insert_pos, new_item);
|
||||||
|
self.selected = insert_pos;
|
||||||
|
self.sync_input_with_selected();
|
||||||
|
self.mode = Mode::Insert;
|
||||||
|
self.status_message = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_index(path: &str) -> Option<(&str, usize)> {
|
||||||
|
if path.ends_with(']') {
|
||||||
|
if let Some(start) = path.rfind('[') {
|
||||||
|
if let Ok(idx) = path[start + 1..path.len() - 1].parse::<usize>() {
|
||||||
|
return Some((&path[..start], idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,6 +114,8 @@ pub struct KeybindsConfig {
|
|||||||
pub previous_match: String,
|
pub previous_match: String,
|
||||||
pub jump_top: String,
|
pub jump_top: String,
|
||||||
pub jump_bottom: String,
|
pub jump_bottom: String,
|
||||||
|
pub append_item: String,
|
||||||
|
pub prepend_item: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for KeybindsConfig {
|
impl Default for KeybindsConfig {
|
||||||
@@ -130,6 +132,8 @@ impl Default for KeybindsConfig {
|
|||||||
previous_match: "N".to_string(),
|
previous_match: "N".to_string(),
|
||||||
jump_top: "gg".to_string(),
|
jump_top: "gg".to_string(),
|
||||||
jump_bottom: "G".to_string(),
|
jump_bottom: "G".to_string(),
|
||||||
|
append_item: "o".to_string(),
|
||||||
|
prepend_item: "O".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{ConfigItem, FormatHandler, ItemStatus};
|
use super::{ConfigItem, FormatHandler, ItemStatus, ValueType};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -27,6 +27,7 @@ impl FormatHandler for EnvHandler {
|
|||||||
depth: 0,
|
depth: 0,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::String,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +66,7 @@ impl FormatHandler for EnvHandler {
|
|||||||
depth: 0,
|
depth: 0,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::MissingFromTemplate,
|
status: ItemStatus::MissingFromTemplate,
|
||||||
|
value_type: ValueType::String,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,6 +161,7 @@ mod tests {
|
|||||||
depth: 0,
|
depth: 0,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::String,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
let handler = EnvHandler;
|
let handler = EnvHandler;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{ConfigItem, FormatHandler, FormatType, ItemStatus};
|
use super::{ConfigItem, FormatHandler, FormatType, ItemStatus, ValueType};
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
@@ -58,6 +58,8 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
key_name.to_string()
|
key_name.to_string()
|
||||||
} else if key_name.is_empty() {
|
} else if key_name.is_empty() {
|
||||||
prefix.to_string()
|
prefix.to_string()
|
||||||
|
} else if key_name.starts_with('[') {
|
||||||
|
format!("{}{}", prefix, key_name)
|
||||||
} else {
|
} else {
|
||||||
format!("{}.{}", prefix, key_name)
|
format!("{}.{}", prefix, key_name)
|
||||||
};
|
};
|
||||||
@@ -74,6 +76,7 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
depth,
|
depth,
|
||||||
is_group: true,
|
is_group: true,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::Null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let next_depth = if path.is_empty() { depth } else { depth + 1 };
|
let next_depth = if path.is_empty() { depth } else { depth + 1 };
|
||||||
@@ -92,6 +95,7 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
depth,
|
depth,
|
||||||
is_group: true,
|
is_group: true,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::Null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let next_depth = if path.is_empty() { depth } else { depth + 1 };
|
let next_depth = if path.is_empty() { depth } else { depth + 1 };
|
||||||
@@ -110,6 +114,7 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
depth,
|
depth,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::String,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Value::Number(n) => {
|
Value::Number(n) => {
|
||||||
@@ -123,6 +128,7 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
depth,
|
depth,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::Number,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Value::Bool(b) => {
|
Value::Bool(b) => {
|
||||||
@@ -136,6 +142,7 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
depth,
|
depth,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::Bool,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Value::Null => {
|
Value::Null => {
|
||||||
@@ -148,6 +155,7 @@ fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut
|
|||||||
depth,
|
depth,
|
||||||
is_group: false,
|
is_group: false,
|
||||||
status: ItemStatus::Present,
|
status: ItemStatus::Present,
|
||||||
|
value_type: ValueType::Null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,14 +213,14 @@ impl FormatHandler for HierarchicalHandler {
|
|||||||
let val = var.value.as_deref()
|
let val = var.value.as_deref()
|
||||||
.or(var.template_value.as_deref())
|
.or(var.template_value.as_deref())
|
||||||
.unwrap_or("");
|
.unwrap_or("");
|
||||||
insert_into_value(&mut root, &var.path, val);
|
insert_into_value(&mut root, &var.path, val, var.value_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.write_value(path, &root)
|
self.write_value(path, &root)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_into_value(root: &mut Value, path: &str, new_val_str: &str) {
|
fn insert_into_value(root: &mut Value, path: &str, new_val_str: &str, value_type: ValueType) {
|
||||||
let mut parts = path.split('.');
|
let mut parts = path.split('.');
|
||||||
let last_part = match parts.next_back() {
|
let last_part = match parts.next_back() {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
@@ -255,13 +263,30 @@ fn insert_into_value(root: &mut Value, path: &str, new_val_str: &str) {
|
|||||||
}
|
}
|
||||||
let map = current.as_object_mut().unwrap();
|
let map = current.as_object_mut().unwrap();
|
||||||
|
|
||||||
// Attempt basic type inference
|
// Use the preserved ValueType instead of aggressive inference
|
||||||
let final_val = if let Ok(n) = new_val_str.parse::<i64>() {
|
let final_val = match value_type {
|
||||||
|
ValueType::Number => {
|
||||||
|
if let Ok(n) = new_val_str.parse::<i64>() {
|
||||||
Value::Number(n.into())
|
Value::Number(n.into())
|
||||||
} else if let Ok(b) = new_val_str.parse::<bool>() {
|
} else if let Ok(f) = new_val_str.parse::<f64>() {
|
||||||
|
if let Some(n) = serde_json::Number::from_f64(f) {
|
||||||
|
Value::Number(n)
|
||||||
|
} else {
|
||||||
|
Value::String(new_val_str.to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::String(new_val_str.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueType::Bool => {
|
||||||
|
if let Ok(b) = new_val_str.parse::<bool>() {
|
||||||
Value::Bool(b)
|
Value::Bool(b)
|
||||||
} else {
|
} else {
|
||||||
Value::String(new_val_str.to_string())
|
Value::String(new_val_str.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValueType::Null if new_val_str.is_empty() => Value::Null,
|
||||||
|
_ => Value::String(new_val_str.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(i) = final_idx {
|
if let Some(i) = final_idx {
|
||||||
@@ -316,7 +341,7 @@ mod tests {
|
|||||||
let mut root = Value::Object(Map::new());
|
let mut root = Value::Object(Map::new());
|
||||||
for var in vars {
|
for var in vars {
|
||||||
if !var.is_group {
|
if !var.is_group {
|
||||||
insert_into_value(&mut root, &var.path, var.value.as_deref().unwrap_or(""));
|
insert_into_value(&mut root, &var.path, var.value.as_deref().unwrap_or(""), var.value_type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,14 @@ pub enum ItemStatus {
|
|||||||
Modified,
|
Modified,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ValueType {
|
||||||
|
String,
|
||||||
|
Number,
|
||||||
|
Bool,
|
||||||
|
Null,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ConfigItem {
|
pub struct ConfigItem {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
@@ -22,6 +30,7 @@ pub struct ConfigItem {
|
|||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub is_group: bool,
|
pub is_group: bool,
|
||||||
pub status: ItemStatus,
|
pub status: ItemStatus,
|
||||||
|
pub value_type: ValueType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ where
|
|||||||
(&self.config.keybinds.previous_match, "previous_match"),
|
(&self.config.keybinds.previous_match, "previous_match"),
|
||||||
(&self.config.keybinds.jump_top, "jump_top"),
|
(&self.config.keybinds.jump_top, "jump_top"),
|
||||||
(&self.config.keybinds.jump_bottom, "jump_bottom"),
|
(&self.config.keybinds.jump_bottom, "jump_bottom"),
|
||||||
|
(&self.config.keybinds.append_item, "append_item"),
|
||||||
|
(&self.config.keybinds.prepend_item, "prepend_item"),
|
||||||
(&"a".to_string(), "add_missing"),
|
(&"a".to_string(), "add_missing"),
|
||||||
(&":".to_string(), "command"),
|
(&":".to_string(), "command"),
|
||||||
(&"q".to_string(), "quit"),
|
(&"q".to_string(), "quit"),
|
||||||
@@ -154,6 +156,8 @@ where
|
|||||||
"previous_match" => self.app.jump_previous_match(),
|
"previous_match" => self.app.jump_previous_match(),
|
||||||
"jump_top" => self.app.jump_top(),
|
"jump_top" => self.app.jump_top(),
|
||||||
"jump_bottom" => self.app.jump_bottom(),
|
"jump_bottom" => self.app.jump_bottom(),
|
||||||
|
"append_item" => self.app.add_array_item(true),
|
||||||
|
"prepend_item" => self.app.add_array_item(false),
|
||||||
"add_missing" => self.add_missing_item(),
|
"add_missing" => self.add_missing_item(),
|
||||||
"command" => {
|
"command" => {
|
||||||
self.command_buffer.push(':');
|
self.command_buffer.push(':');
|
||||||
|
|||||||
Reference in New Issue
Block a user