use crate::ported::lib::dict::mergedicts;
use serde_json::{Map, Value};
#[derive(Debug, Clone, Default)]
pub struct ShellArgs {
pub ext: Vec<String>,
pub renderer_module: Option<String>,
pub config_override: Option<Map<String, Value>>,
pub theme_override: Option<Map<String, Value>>,
pub config_path: Vec<String>,
}
pub struct ShellPowerline {
pub args: ShellArgs,
}
impl ShellPowerline {
pub fn init(args: ShellArgs) -> (Self, String, Option<String>) {
let ext_first = args.ext.first().cloned().unwrap_or_default();
let renderer_module = args.renderer_module.clone();
(Self { args }, ext_first, renderer_module)
}
pub fn load_main_config(&self, base: &mut Map<String, Value>) {
if let Some(overlay) = &self.args.config_override {
mergedicts(base, overlay.clone(), false);
}
}
pub fn load_theme_config(&self, name: &str, base: &mut Map<String, Value>) {
if let Some(themes) = &self.args.theme_override {
if let Some(overlay) = themes.get(name).and_then(|v| v.as_object()) {
mergedicts(base, overlay.clone(), false);
}
}
}
pub fn get_config_paths(&self) -> Option<&[String]> {
if self.args.config_path.is_empty() {
None
} else {
Some(&self.args.config_path)
}
}
pub fn get_local_themes(&self, local_themes: &Map<String, Value>) -> Map<String, Value> {
if local_themes.is_empty() {
return Map::new();
}
let mut out = Map::new();
for (key, val) in local_themes {
let theme_name = val.as_str().unwrap_or("");
let mut config = Map::new();
self.load_theme_config(theme_name, &mut config);
let mut entry = Map::new();
entry.insert("config".to_string(), Value::Object(config));
out.insert(key.clone(), Value::Object(entry));
}
out
}
pub fn do_setup(&self, obj: &mut Map<String, Value>) {
obj.insert(
"powerline".to_string(),
Value::String("<ShellPowerline>".into()),
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn init_stores_args_and_returns_ext_first() {
let args = ShellArgs {
ext: vec!["shell".to_string()],
renderer_module: Some("bash".to_string()),
..Default::default()
};
let (sp, ext, renderer) = ShellPowerline::init(args);
assert_eq!(sp.args.ext, vec!["shell".to_string()]);
assert_eq!(ext, "shell");
assert_eq!(renderer, Some("bash".to_string()));
}
#[test]
fn init_empty_ext_returns_empty_string() {
let args = ShellArgs::default();
let (_sp, ext, renderer) = ShellPowerline::init(args);
assert_eq!(ext, "");
assert_eq!(renderer, None);
}
#[test]
fn load_main_config_overlays_config_override() {
let mut override_map = Map::new();
override_map.insert("foo".to_string(), Value::from(1));
let args = ShellArgs {
ext: vec!["shell".to_string()],
config_override: Some(override_map),
..Default::default()
};
let (sp, _, _) = ShellPowerline::init(args);
let mut base: Map<String, Value> = Map::new();
base.insert("bar".to_string(), Value::from(2));
sp.load_main_config(&mut base);
assert_eq!(base.get("foo"), Some(&Value::from(1)));
assert_eq!(base.get("bar"), Some(&Value::from(2)));
}
#[test]
fn load_main_config_no_override_leaves_base() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
let mut base: Map<String, Value> = Map::new();
base.insert("k".to_string(), Value::from(7));
sp.load_main_config(&mut base);
assert_eq!(base.get("k"), Some(&Value::from(7)));
assert_eq!(base.len(), 1);
}
#[test]
fn load_theme_config_overlays_matching_theme() {
let mut overlay = Map::new();
overlay.insert("foo".to_string(), Value::String("bar".into()));
let mut themes = Map::new();
themes.insert("default".to_string(), Value::Object(overlay));
let args = ShellArgs {
theme_override: Some(themes),
..Default::default()
};
let (sp, _, _) = ShellPowerline::init(args);
let mut base: Map<String, Value> = Map::new();
sp.load_theme_config("default", &mut base);
assert_eq!(base.get("foo"), Some(&Value::String("bar".into())));
}
#[test]
fn load_theme_config_ignores_non_matching_theme() {
let mut overlay = Map::new();
overlay.insert("foo".to_string(), Value::String("bar".into()));
let mut themes = Map::new();
themes.insert("default".to_string(), Value::Object(overlay));
let args = ShellArgs {
theme_override: Some(themes),
..Default::default()
};
let (sp, _, _) = ShellPowerline::init(args);
let mut base: Map<String, Value> = Map::new();
sp.load_theme_config("other", &mut base);
assert!(base.get("foo").is_none());
}
#[test]
fn load_theme_config_no_overrides_leaves_base() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
let mut base: Map<String, Value> = Map::new();
base.insert("untouched".to_string(), Value::from(1));
sp.load_theme_config("any", &mut base);
assert_eq!(base.get("untouched"), Some(&Value::from(1)));
}
#[test]
fn get_config_paths_returns_args_value_when_set() {
let args = ShellArgs {
config_path: vec!["/etc/powerline".to_string(), "/usr/share".to_string()],
..Default::default()
};
let (sp, _, _) = ShellPowerline::init(args);
let paths = sp.get_config_paths().unwrap();
assert_eq!(
paths,
&["/etc/powerline".to_string(), "/usr/share".to_string()]
);
}
#[test]
fn get_config_paths_returns_none_when_args_empty() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
assert!(sp.get_config_paths().is_none());
}
#[test]
fn get_local_themes_empty_input_returns_empty() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
let empty = Map::new();
let result = sp.get_local_themes(&empty);
assert!(result.is_empty());
}
#[test]
fn get_local_themes_wraps_each_value_in_config_key() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
let mut input = Map::new();
input.insert("matcher_a".to_string(), Value::String("theme_a".into()));
input.insert("matcher_b".to_string(), Value::String("theme_b".into()));
let result = sp.get_local_themes(&input);
assert_eq!(result.len(), 2);
for (_k, v) in &result {
assert!(v.as_object().unwrap().contains_key("config"));
}
}
#[test]
fn get_local_themes_loaded_config_picks_up_theme_override() {
let mut overlay = Map::new();
overlay.insert("seg".to_string(), Value::String("custom".into()));
let mut themes = Map::new();
themes.insert("theme_a".to_string(), Value::Object(overlay));
let args = ShellArgs {
theme_override: Some(themes),
..Default::default()
};
let (sp, _, _) = ShellPowerline::init(args);
let mut input = Map::new();
input.insert("matcher_a".to_string(), Value::String("theme_a".into()));
let result = sp.get_local_themes(&input);
let entry = result.get("matcher_a").unwrap().as_object().unwrap();
let config = entry.get("config").unwrap().as_object().unwrap();
assert_eq!(config.get("seg"), Some(&Value::String("custom".into())));
}
#[test]
fn do_setup_assigns_powerline_attribute() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
let mut obj = Map::new();
sp.do_setup(&mut obj);
assert!(obj.contains_key("powerline"));
}
#[test]
fn shell_args_default_has_empty_fields() {
let a = ShellArgs::default();
assert!(a.ext.is_empty());
assert!(a.renderer_module.is_none());
assert!(a.config_override.is_none());
assert!(a.theme_override.is_none());
assert!(a.config_path.is_empty());
}
#[test]
fn load_main_config_overlay_recurses_into_nested_dict() {
let mut inner_override = Map::new();
inner_override.insert("nested".to_string(), Value::from(99));
let mut override_map = Map::new();
override_map.insert("outer".to_string(), Value::Object(inner_override));
let args = ShellArgs {
config_override: Some(override_map),
..Default::default()
};
let (sp, _, _) = ShellPowerline::init(args);
let mut base_inner = Map::new();
base_inner.insert("other".to_string(), Value::from(1));
let mut base: Map<String, Value> = Map::new();
base.insert("outer".to_string(), Value::Object(base_inner));
sp.load_main_config(&mut base);
let merged_outer = base.get("outer").unwrap().as_object().unwrap();
assert_eq!(merged_outer.get("other"), Some(&Value::from(1)));
assert_eq!(merged_outer.get("nested"), Some(&Value::from(99)));
}
#[test]
fn local_theme_input_with_non_string_value_uses_empty_theme_name() {
let args = ShellArgs::default();
let (sp, _, _) = ShellPowerline::init(args);
let mut input = Map::new();
input.insert("matcher".to_string(), json!(42));
let result = sp.get_local_themes(&input);
assert!(result.contains_key("matcher"));
}
}