pub mod pre_5;
pub mod since_5;
pub mod since_7;
use serde_json::{Map, Value};
pub struct IPythonRenderer {
pub segment_info: Map<String, Value>,
pub theme: Value,
pub local_themes: std::collections::HashMap<String, Map<String, Value>>,
pub theme_config: Value,
pub theme_kwargs: Map<String, Value>,
pub shutdown_called: std::sync::Mutex<Vec<String>>,
}
impl Default for IPythonRenderer {
fn default() -> Self {
Self::new()
}
}
impl IPythonRenderer {
pub fn new() -> Self {
Self {
segment_info: Map::new(),
theme: Value::Null,
local_themes: std::collections::HashMap::new(),
theme_config: Value::Null,
theme_kwargs: Map::new(),
shutdown_called: std::sync::Mutex::new(Vec::new()),
}
}
pub fn get_segment_info(&self, segment_info: &Value) -> Map<String, Value> {
let mut r = self.segment_info.clone();
r.insert("ipython".to_string(), segment_info.clone());
r
}
pub fn get_theme(&mut self, matcher_info: &str) -> Value {
if matcher_info == "in" {
return self.theme.clone();
}
let match_entry = match self.local_themes.get(matcher_info) {
Some(m) => m.clone(),
None => return Value::Null,
};
if let Some(t) = match_entry.get("theme") {
return t.clone();
}
let constructed = serde_json::json!({
"theme_config": match_entry.get("config").cloned().unwrap_or(Value::Null),
"main_theme_config": self.theme_config.clone(),
"theme_kwargs": Value::Object(self.theme_kwargs.clone()),
});
if let Some(m) = self.local_themes.get_mut(matcher_info) {
m.insert("theme".to_string(), constructed.clone());
}
constructed
}
pub fn shutdown(&self) {
let mut log = self
.shutdown_called
.lock()
.unwrap_or_else(|e| e.into_inner());
log.push("theme".to_string());
for (name, match_entry) in &self.local_themes {
if match_entry.contains_key("theme") {
log.push(name.clone());
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn get_segment_info_patches_ipython_key() {
let mut r = IPythonRenderer::new();
r.segment_info
.insert("color_scheme".into(), json!("monokai"));
let payload = json!({"prompt_count": 42});
let out = r.get_segment_info(&payload);
assert_eq!(out["ipython"]["prompt_count"], 42);
assert_eq!(out["color_scheme"], "monokai");
}
#[test]
fn get_theme_in_returns_self_theme() {
let mut r = IPythonRenderer::new();
r.theme = json!({"name": "default"});
let t = r.get_theme("in");
assert_eq!(t["name"], "default");
}
#[test]
fn get_theme_returns_existing_local_theme() {
let mut r = IPythonRenderer::new();
let mut entry = Map::new();
entry.insert("theme".to_string(), json!({"name": "ipython-out"}));
r.local_themes.insert("out".to_string(), entry);
let t = r.get_theme("out");
assert_eq!(t["name"], "ipython-out");
}
#[test]
fn get_theme_constructs_lazy_theme_from_config() {
let mut r = IPythonRenderer::new();
r.theme_config = json!({"colorscheme": "default"});
r.theme_kwargs
.insert("extra".to_string(), json!("kw_value"));
let mut entry = Map::new();
entry.insert("config".to_string(), json!({"segments": []}));
r.local_themes.insert("rewrite".to_string(), entry);
let t = r.get_theme("rewrite");
assert_eq!(t["theme_config"]["segments"], json!([]));
assert_eq!(t["main_theme_config"]["colorscheme"], "default");
assert_eq!(t["theme_kwargs"]["extra"], "kw_value");
}
#[test]
fn get_theme_caches_constructed_theme() {
let mut r = IPythonRenderer::new();
let mut entry = Map::new();
entry.insert("config".to_string(), json!({"a": 1}));
r.local_themes.insert("rewrite".to_string(), entry);
let _ = r.get_theme("rewrite");
let cached = r
.local_themes
.get("rewrite")
.and_then(|m| m.get("theme"))
.cloned();
assert!(cached.is_some(), "constructed theme not cached");
}
#[test]
fn shutdown_records_main_theme_first() {
let r = IPythonRenderer::new();
r.shutdown();
let log = r.shutdown_called.lock().unwrap();
assert_eq!(log[0], "theme");
}
#[test]
fn shutdown_walks_local_themes_with_theme_key() {
let mut r = IPythonRenderer::new();
let mut with_theme = Map::new();
with_theme.insert("theme".to_string(), json!({}));
let no_theme: Map<String, Value> = Map::new();
r.local_themes.insert("ready".to_string(), with_theme);
r.local_themes.insert("not_ready".to_string(), no_theme);
r.shutdown();
let log = r.shutdown_called.lock().unwrap();
assert!(log.contains(&"ready".to_string()));
assert!(!log.contains(&"not_ready".to_string()));
}
#[test]
fn get_theme_missing_matcher_returns_null() {
let mut r = IPythonRenderer::new();
let t = r.get_theme("nonexistent");
assert_eq!(t, Value::Null);
}
}