fixed ui and themes for tree view + neovim plugin wrapper
All checks were successful
Version Check / check-version (pull_request) Successful in 4s
All checks were successful
Version Check / check-version (pull_request) Successful in 4s
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use super::{EnvVar, FormatHandler};
|
||||
use super::{ConfigItem, FormatHandler, ItemStatus};
|
||||
use std::fs;
|
||||
use std::io::{self, Write};
|
||||
use std::path::Path;
|
||||
@@ -6,7 +6,7 @@ use std::path::Path;
|
||||
pub struct EnvHandler;
|
||||
|
||||
impl FormatHandler for EnvHandler {
|
||||
fn parse(&self, path: &Path) -> io::Result<Vec<EnvVar>> {
|
||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> {
|
||||
let content = fs::read_to_string(path)?;
|
||||
let mut vars = Vec::new();
|
||||
|
||||
@@ -18,10 +18,15 @@ impl FormatHandler for EnvHandler {
|
||||
|
||||
if let Some((key, val)) = line.split_once('=') {
|
||||
let parsed_val = val.trim().trim_matches('"').trim_matches('\'').to_string();
|
||||
vars.push(EnvVar {
|
||||
vars.push(ConfigItem {
|
||||
key: key.trim().to_string(),
|
||||
value: parsed_val.clone(),
|
||||
default_value: parsed_val,
|
||||
path: key.trim().to_string(),
|
||||
value: Some(parsed_val.clone()),
|
||||
template_value: Some(parsed_val.clone()),
|
||||
default_value: Some(parsed_val),
|
||||
depth: 0,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -29,7 +34,7 @@ impl FormatHandler for EnvHandler {
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
fn merge(&self, path: &Path, vars: &mut Vec<EnvVar>) -> io::Result<()> {
|
||||
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -46,24 +51,44 @@ impl FormatHandler for EnvHandler {
|
||||
let parsed_val = val.trim().trim_matches('"').trim_matches('\'').to_string();
|
||||
|
||||
if let Some(var) = vars.iter_mut().find(|v| v.key == key) {
|
||||
var.value = parsed_val;
|
||||
if var.value.as_deref() != Some(&parsed_val) {
|
||||
var.value = Some(parsed_val);
|
||||
var.status = ItemStatus::Modified;
|
||||
}
|
||||
} else {
|
||||
vars.push(EnvVar {
|
||||
vars.push(ConfigItem {
|
||||
key: key.to_string(),
|
||||
value: parsed_val.clone(),
|
||||
default_value: String::new(),
|
||||
path: key.to_string(),
|
||||
value: Some(parsed_val),
|
||||
template_value: None,
|
||||
default_value: None,
|
||||
depth: 0,
|
||||
is_group: false,
|
||||
status: ItemStatus::MissingFromTemplate,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark missing from active
|
||||
for var in vars.iter_mut() {
|
||||
if var.status == ItemStatus::Present && var.value.is_none() {
|
||||
var.status = ItemStatus::MissingFromActive;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, path: &Path, vars: &[EnvVar]) -> io::Result<()> {
|
||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
|
||||
let mut file = fs::File::create(path)?;
|
||||
for var in vars {
|
||||
writeln!(file, "{}={}", var.key, var.value)?;
|
||||
if !var.is_group {
|
||||
let val = var.value.as_deref()
|
||||
.or(var.template_value.as_deref())
|
||||
.unwrap_or("");
|
||||
writeln!(file, "{}={}", var.key, val)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -88,14 +113,13 @@ mod tests {
|
||||
let vars = handler.parse(file.path()).unwrap();
|
||||
assert_eq!(vars.len(), 4);
|
||||
assert_eq!(vars[0].key, "KEY1");
|
||||
assert_eq!(vars[0].value, "value1");
|
||||
assert_eq!(vars[0].default_value, "value1");
|
||||
assert_eq!(vars[0].value.as_deref(), Some("value1"));
|
||||
assert_eq!(vars[1].key, "KEY2");
|
||||
assert_eq!(vars[1].value, "value2");
|
||||
assert_eq!(vars[1].value.as_deref(), Some("value2"));
|
||||
assert_eq!(vars[2].key, "KEY3");
|
||||
assert_eq!(vars[2].value, "value3");
|
||||
assert_eq!(vars[2].value.as_deref(), Some("value3"));
|
||||
assert_eq!(vars[3].key, "EMPTY");
|
||||
assert_eq!(vars[3].value, "");
|
||||
assert_eq!(vars[3].value.as_deref(), Some(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -112,25 +136,29 @@ mod tests {
|
||||
|
||||
assert_eq!(vars.len(), 3);
|
||||
assert_eq!(vars[0].key, "KEY1");
|
||||
assert_eq!(vars[0].value, "custom1");
|
||||
assert_eq!(vars[0].default_value, "default1");
|
||||
assert_eq!(vars[0].value.as_deref(), Some("custom1"));
|
||||
assert_eq!(vars[0].status, ItemStatus::Modified);
|
||||
|
||||
assert_eq!(vars[1].key, "KEY2");
|
||||
assert_eq!(vars[1].value, "default2");
|
||||
assert_eq!(vars[1].default_value, "default2");
|
||||
|
||||
assert_eq!(vars[1].value.as_deref(), Some("default2"));
|
||||
|
||||
assert_eq!(vars[2].key, "KEY3");
|
||||
assert_eq!(vars[2].value, "custom3");
|
||||
assert_eq!(vars[2].default_value, "");
|
||||
assert_eq!(vars[2].value.as_deref(), Some("custom3"));
|
||||
assert_eq!(vars[2].status, ItemStatus::MissingFromTemplate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_env() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let vars = vec![EnvVar {
|
||||
let vars = vec![ConfigItem {
|
||||
key: "KEY1".to_string(),
|
||||
value: "value1".to_string(),
|
||||
default_value: "def".to_string(),
|
||||
path: "KEY1".to_string(),
|
||||
value: Some("value1".to_string()),
|
||||
template_value: None,
|
||||
default_value: None,
|
||||
depth: 0,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
}];
|
||||
|
||||
let handler = EnvHandler;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{EnvVar, FormatHandler, FormatType};
|
||||
use super::{ConfigItem, FormatHandler, FormatType, ItemStatus};
|
||||
use serde_json::{Map, Value};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
@@ -53,87 +53,160 @@ impl HierarchicalHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn flatten(value: &Value, prefix: &str, vars: &mut Vec<EnvVar>) {
|
||||
fn flatten(value: &Value, prefix: &str, depth: usize, key_name: &str, vars: &mut Vec<ConfigItem>) {
|
||||
let path = if prefix.is_empty() {
|
||||
key_name.to_string()
|
||||
} else if key_name.is_empty() {
|
||||
prefix.to_string()
|
||||
} else {
|
||||
format!("{}.{}", prefix, key_name)
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
if !path.is_empty() {
|
||||
vars.push(ConfigItem {
|
||||
key: key_name.to_string(),
|
||||
path: path.clone(),
|
||||
value: None,
|
||||
template_value: None,
|
||||
default_value: None,
|
||||
depth,
|
||||
is_group: true,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
let next_depth = if path.is_empty() { depth } else { depth + 1 };
|
||||
for (k, v) in map {
|
||||
let new_prefix = if prefix.is_empty() {
|
||||
k.clone()
|
||||
} else {
|
||||
format!("{}.{}", prefix, k)
|
||||
};
|
||||
flatten(v, &new_prefix, vars);
|
||||
flatten(v, &path, next_depth, k, vars);
|
||||
}
|
||||
}
|
||||
Value::Array(arr) => {
|
||||
if !path.is_empty() {
|
||||
vars.push(ConfigItem {
|
||||
key: key_name.to_string(),
|
||||
path: path.clone(),
|
||||
value: None,
|
||||
template_value: None,
|
||||
default_value: None,
|
||||
depth,
|
||||
is_group: true,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
let next_depth = if path.is_empty() { depth } else { depth + 1 };
|
||||
for (i, v) in arr.iter().enumerate() {
|
||||
let new_prefix = format!("{}[{}]", prefix, i);
|
||||
flatten(v, &new_prefix, vars);
|
||||
let array_key = format!("[{}]", i);
|
||||
flatten(v, &path, next_depth, &array_key, vars);
|
||||
}
|
||||
}
|
||||
Value::String(s) => {
|
||||
vars.push(EnvVar {
|
||||
key: prefix.to_string(),
|
||||
value: s.clone(),
|
||||
default_value: s.clone(),
|
||||
vars.push(ConfigItem {
|
||||
key: key_name.to_string(),
|
||||
path: path.clone(),
|
||||
value: Some(s.clone()),
|
||||
template_value: Some(s.clone()),
|
||||
default_value: Some(s.clone()),
|
||||
depth,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
Value::Number(n) => {
|
||||
let s = n.to_string();
|
||||
vars.push(EnvVar {
|
||||
key: prefix.to_string(),
|
||||
value: s.clone(),
|
||||
default_value: s.clone(),
|
||||
vars.push(ConfigItem {
|
||||
key: key_name.to_string(),
|
||||
path: path.clone(),
|
||||
value: Some(s.clone()),
|
||||
template_value: Some(s.clone()),
|
||||
default_value: Some(s.clone()),
|
||||
depth,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
Value::Bool(b) => {
|
||||
let s = b.to_string();
|
||||
vars.push(EnvVar {
|
||||
key: prefix.to_string(),
|
||||
value: s.clone(),
|
||||
default_value: s.clone(),
|
||||
vars.push(ConfigItem {
|
||||
key: key_name.to_string(),
|
||||
path: path.clone(),
|
||||
value: Some(s.clone()),
|
||||
template_value: Some(s.clone()),
|
||||
default_value: Some(s.clone()),
|
||||
depth,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
Value::Null => {
|
||||
vars.push(EnvVar {
|
||||
key: prefix.to_string(),
|
||||
value: "".to_string(),
|
||||
default_value: "".to_string(),
|
||||
vars.push(ConfigItem {
|
||||
key: key_name.to_string(),
|
||||
path: path.clone(),
|
||||
value: Some("".to_string()),
|
||||
template_value: Some("".to_string()),
|
||||
default_value: Some("".to_string()),
|
||||
depth,
|
||||
is_group: false,
|
||||
status: ItemStatus::Present,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removed unused update_leaf and update_leaf_value functions
|
||||
|
||||
impl FormatHandler for HierarchicalHandler {
|
||||
fn parse(&self, path: &Path) -> io::Result<Vec<EnvVar>> {
|
||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>> {
|
||||
let value = self.read_value(path)?;
|
||||
let mut vars = Vec::new();
|
||||
flatten(&value, "", &mut vars);
|
||||
flatten(&value, "", 0, "", &mut vars);
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
fn merge(&self, path: &Path, vars: &mut Vec<EnvVar>) -> io::Result<()> {
|
||||
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()> {
|
||||
if !path.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
let existing_value = self.read_value(path)?;
|
||||
let mut existing_vars = Vec::new();
|
||||
flatten(&existing_value, "", &mut existing_vars);
|
||||
flatten(&existing_value, "", 0, "", &mut existing_vars);
|
||||
|
||||
for var in vars.iter_mut() {
|
||||
if let Some(existing) = existing_vars.iter().find(|v| v.key == var.key) {
|
||||
var.value = existing.value.clone();
|
||||
if let Some(existing) = existing_vars.iter().find(|v| v.path == var.path) {
|
||||
if var.value != existing.value {
|
||||
var.value = existing.value.clone();
|
||||
var.status = ItemStatus::Modified;
|
||||
}
|
||||
} else {
|
||||
var.status = ItemStatus::MissingFromActive;
|
||||
}
|
||||
}
|
||||
|
||||
// Find keys in active that are not in template
|
||||
let mut to_add = Vec::new();
|
||||
for existing in existing_vars {
|
||||
if !vars.iter().any(|v| v.path == existing.path) {
|
||||
let mut new_item = existing.clone();
|
||||
new_item.status = ItemStatus::MissingFromTemplate;
|
||||
new_item.template_value = None;
|
||||
new_item.default_value = None;
|
||||
to_add.push(new_item);
|
||||
}
|
||||
}
|
||||
|
||||
// Basic insertion logic for extra keys (could be improved to insert at correct depth/position)
|
||||
vars.extend(to_add);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&self, path: &Path, vars: &[EnvVar]) -> io::Result<()> {
|
||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()> {
|
||||
let mut root = Value::Object(Map::new());
|
||||
for var in vars {
|
||||
insert_into_value(&mut root, &var.key, &var.value);
|
||||
if !var.is_group {
|
||||
let val = var.value.as_deref()
|
||||
.or(var.template_value.as_deref())
|
||||
.unwrap_or("");
|
||||
insert_into_value(&mut root, &var.path, val);
|
||||
}
|
||||
}
|
||||
self.write_value(path, &root)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,24 @@ use std::path::Path;
|
||||
pub mod env;
|
||||
pub mod hierarchical;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ItemStatus {
|
||||
Present,
|
||||
MissingFromActive,
|
||||
MissingFromTemplate,
|
||||
Modified,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnvVar {
|
||||
pub struct ConfigItem {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
pub default_value: String,
|
||||
pub path: String,
|
||||
pub value: Option<String>,
|
||||
pub template_value: Option<String>,
|
||||
pub default_value: Option<String>,
|
||||
pub depth: usize,
|
||||
pub is_group: bool,
|
||||
pub status: ItemStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -20,9 +33,9 @@ pub enum FormatType {
|
||||
}
|
||||
|
||||
pub trait FormatHandler {
|
||||
fn parse(&self, path: &Path) -> io::Result<Vec<EnvVar>>;
|
||||
fn merge(&self, path: &Path, vars: &mut Vec<EnvVar>) -> io::Result<()>;
|
||||
fn write(&self, path: &Path, vars: &[EnvVar]) -> io::Result<()>;
|
||||
fn parse(&self, path: &Path) -> io::Result<Vec<ConfigItem>>;
|
||||
fn merge(&self, path: &Path, vars: &mut Vec<ConfigItem>) -> io::Result<()>;
|
||||
fn write(&self, path: &Path, vars: &[ConfigItem]) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub fn detect_format(path: &Path, override_format: Option<String>) -> FormatType {
|
||||
|
||||
Reference in New Issue
Block a user