use serde_json::Value;
pub struct JStr(pub String);
impl JStr {
pub fn join<I, T>(&self, iterable: I) -> String
where
I: IntoIterator<Item = T>,
T: std::fmt::Display,
{
let parts: Vec<String> = iterable.into_iter().map(|x| x.to_string()).collect();
parts.join(&self.0)
}
}
#[allow(non_upper_case_globals)]
pub fn key_sep() -> JStr {
JStr("/".to_string()) }
pub fn list_themes(
data: &serde_json::Map<String, Value>,
context: &Context,
) -> Vec<(String, Value)> {
let theme_type = data
.get("theme_type")
.and_then(|v| v.as_str())
.unwrap_or("");
let ext = data.get("ext").and_then(|v| v.as_str()).unwrap_or("");
let main_theme_name = data
.get("main_config")
.and_then(|v| v.get("ext"))
.and_then(|v| v.get(ext))
.and_then(|v| v.get("theme"))
.and_then(|v| v.as_str());
let cur_theme = data.get("theme").and_then(|v| v.as_str());
let is_main_theme = cur_theme.is_some() && cur_theme == main_theme_name;
if theme_type == "top" {
let mut out = Vec::new();
if let Some(tc) = data.get("theme_configs").and_then(|v| v.as_object()) {
for (theme_ext, theme_configs) in tc {
if let Some(theme_obj) = theme_configs.as_object() {
for theme in theme_obj.values() {
out.push((theme_ext.clone(), theme.clone()));
}
}
}
}
out
} else if theme_type == "main" || is_main_theme {
let mut out = Vec::new();
if let Some(ext_themes) = data.get("ext_theme_configs").and_then(|v| v.as_object()) {
for theme in ext_themes.values() {
out.push((ext.to_string(), theme.clone()));
}
}
out
} else {
if context.0.is_empty() {
Vec::new()
} else {
let (_k, v) = context.0.last().unwrap().clone();
vec![(ext.to_string(), v)]
}
}
}
#[derive(Debug, Clone)]
pub struct Context(pub Vec<(String, Value)>);
impl Context {
pub fn new() -> Self {
Context(Vec::new())
}
pub fn enter(&self, context_key: String, context_value: Value) -> Self {
let mut entries = self.0.clone();
entries.push((context_key, context_value));
Context(entries)
}
pub fn key(&self) -> String {
key_sep().join(self.0.iter().map(|(k, _)| k.clone()))
}
pub fn enter_key(&self, value: &Value, key: &str) -> Self {
let inner = value.get(key).cloned().unwrap_or(Value::Null);
self.enter(key.to_string(), inner)
}
pub fn enter_item(&self, name: &str, item: &Value) -> Self {
self.enter(name.to_string(), item.clone())
}
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn jstr_joins_with_separator() {
let sep = JStr("/".to_string());
assert_eq!(sep.join(vec!["a", "b", "c"]), "a/b/c");
assert_eq!(sep.join(vec![1, 2, 3]), "1/2/3");
}
#[test]
fn key_sep_is_slash() {
assert_eq!(key_sep().0, "/");
}
#[test]
fn context_starts_empty() {
let c = Context::new();
assert!(c.0.is_empty());
assert_eq!(c.key(), "");
}
#[test]
fn context_enter_appends_one_entry() {
let c = Context::new();
let c1 = c.enter("ext".into(), json!("shell"));
assert_eq!(c1.0.len(), 1);
assert_eq!(c1.key(), "ext");
let c2 = c1.enter("theme".into(), json!("default"));
assert_eq!(c2.0.len(), 2);
assert_eq!(c2.key(), "ext/theme");
}
#[test]
fn context_enter_does_not_mutate_parent() {
let c = Context::new();
let _c1 = c.enter("ext".into(), json!("shell"));
assert!(c.0.is_empty(), "parent context should be unchanged");
}
#[test]
fn context_enter_key_pulls_value_from_obj() {
let c = Context::new();
let v = json!({"colorscheme": "default"});
let c1 = c.enter_key(&v, "colorscheme");
assert_eq!(c1.0.last().unwrap().0, "colorscheme");
assert_eq!(c1.0.last().unwrap().1, json!("default"));
}
#[test]
fn list_themes_top_returns_every_theme() {
let mut data = serde_json::Map::new();
data.insert("theme_type".into(), json!("top"));
data.insert("ext".into(), json!("shell"));
data.insert("theme".into(), json!("default"));
data.insert("main_config".into(), json!({}));
data.insert(
"theme_configs".into(),
json!({
"shell": {"default": {"k": "v1"}, "alt": {"k": "v2"}},
"tmux": {"default": {"k": "v3"}}
}),
);
let c = Context::new();
let r = list_themes(&data, &c);
assert_eq!(r.len(), 3);
}
#[test]
fn list_themes_main_returns_ext_themes() {
let mut data = serde_json::Map::new();
data.insert("theme_type".into(), json!("main"));
data.insert("ext".into(), json!("shell"));
data.insert("theme".into(), json!("default"));
data.insert("main_config".into(), json!({}));
data.insert(
"ext_theme_configs".into(),
json!({"default": {"k": "v"}, "alt": {"k": "w"}}),
);
let c = Context::new();
let r = list_themes(&data, &c);
assert_eq!(r.len(), 2);
assert!(r.iter().all(|(ext, _)| ext == "shell"));
}
}