use crate::ported::lib::dict::mergedicts;
use serde_json::{Map, Value};
#[derive(Debug, Clone)]
pub struct IPythonInfo {
pub execution_count: u64,
}
impl IPythonInfo {
pub fn new(execution_count: u64) -> Self {
Self { execution_count }
}
pub fn prompt_count(&self) -> u64 {
self.execution_count
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RewriteResult {
pub prompt: String,
}
impl RewriteResult {
pub fn new(prompt: impl Into<String>) -> Self {
Self {
prompt: prompt.into(),
}
}
pub fn add(&self, s: &str) -> RewriteResult {
RewriteResult::new(format!("{}{}", self.prompt, s))
}
}
impl std::fmt::Display for RewriteResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.prompt)
}
}
pub struct IPythonPowerline {
pub config_paths: Vec<String>,
pub config_overrides: Option<Map<String, Value>>,
pub theme_overrides: Map<String, Value>,
}
impl Default for IPythonPowerline {
fn default() -> Self {
Self::new()
}
}
impl IPythonPowerline {
pub fn new() -> Self {
Self {
config_paths: Vec::new(),
config_overrides: None,
theme_overrides: Map::new(),
}
}
pub fn init() -> (&'static str, bool) {
("ipython", true)
}
pub fn get_config_paths(&self) -> Option<&[String]> {
if self.config_paths.is_empty() {
None
} else {
Some(&self.config_paths)
}
}
pub fn get_local_themes(&self, local_themes: &Map<String, Value>) -> Map<String, Value> {
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 load_main_config(&self, base: &mut Map<String, Value>) {
if let Some(overlay) = &self.config_overrides {
mergedicts(base, overlay.clone(), false);
}
}
pub fn load_theme_config(&self, name: &str, base: &mut Map<String, Value>) {
if let Some(overlay) = self.theme_overrides.get(name).and_then(|v| v.as_object()) {
mergedicts(base, overlay.clone(), false);
}
}
pub fn do_setup(&self, wrefs: &mut [Option<Map<String, Value>>]) {
for wref in wrefs.iter_mut() {
if let Some(obj) = wref.as_mut() {
obj.insert(
"powerline".to_string(),
Value::String("<IPythonPowerline>".into()),
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn ipython_info_prompt_count_returns_execution_count() {
let i = IPythonInfo::new(42);
assert_eq!(i.prompt_count(), 42);
}
#[test]
fn ipython_info_construct_from_zero() {
let i = IPythonInfo::new(0);
assert_eq!(i.prompt_count(), 0);
}
#[test]
fn rewrite_result_constructs_from_string() {
let r = RewriteResult::new("hello");
assert_eq!(r.prompt, "hello");
}
#[test]
fn rewrite_result_str_returns_prompt() {
let r = RewriteResult::new("hi");
assert_eq!(format!("{}", r), "hi");
}
#[test]
fn rewrite_result_add_concatenates() {
let r = RewriteResult::new("hello ");
let r2 = r.add("world");
assert_eq!(r2.prompt, "hello world");
assert_eq!(r.prompt, "hello ");
}
#[test]
fn rewrite_result_add_chain() {
let r = RewriteResult::new("a").add("b").add("c");
assert_eq!(r.prompt, "abc");
}
#[test]
fn ipython_powerline_init_pins_ext_and_daemon_threads() {
let (ext, daemon) = IPythonPowerline::init();
assert_eq!(ext, "ipython");
assert!(daemon);
}
#[test]
fn ipython_powerline_new_has_empty_attrs() {
let p = IPythonPowerline::new();
assert!(p.config_paths.is_empty());
assert!(p.config_overrides.is_none());
assert!(p.theme_overrides.is_empty());
}
#[test]
fn get_config_paths_returns_some_when_set() {
let mut p = IPythonPowerline::new();
p.config_paths.push("/etc/powerline".to_string());
let paths = p.get_config_paths().unwrap();
assert_eq!(paths, &["/etc/powerline".to_string()]);
}
#[test]
fn get_config_paths_returns_none_when_empty() {
let p = IPythonPowerline::new();
assert!(p.get_config_paths().is_none());
}
#[test]
fn load_main_config_overlays_config_overrides() {
let mut p = IPythonPowerline::new();
let mut overlay = Map::new();
overlay.insert("k".to_string(), Value::from(1));
p.config_overrides = Some(overlay);
let mut base = Map::new();
base.insert("orig".to_string(), Value::from(0));
p.load_main_config(&mut base);
assert_eq!(base.get("k"), Some(&Value::from(1)));
assert_eq!(base.get("orig"), Some(&Value::from(0)));
}
#[test]
fn load_main_config_no_overrides_passes_through() {
let p = IPythonPowerline::new();
let mut base = Map::new();
base.insert("k".to_string(), Value::from(7));
p.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_name() {
let mut p = IPythonPowerline::new();
let mut overlay = Map::new();
overlay.insert("seg".to_string(), Value::String("custom".into()));
p.theme_overrides
.insert("default".to_string(), Value::Object(overlay));
let mut base = Map::new();
p.load_theme_config("default", &mut base);
assert_eq!(base.get("seg"), Some(&Value::String("custom".into())));
}
#[test]
fn load_theme_config_ignores_non_matching_name() {
let mut p = IPythonPowerline::new();
let mut overlay = Map::new();
overlay.insert("seg".to_string(), Value::String("x".into()));
p.theme_overrides
.insert("default".to_string(), Value::Object(overlay));
let mut base = Map::new();
p.load_theme_config("other", &mut base);
assert!(base.get("seg").is_none());
}
#[test]
fn get_local_themes_wraps_each_value_in_config_key() {
let p = IPythonPowerline::new();
let mut input = Map::new();
input.insert("type_a".to_string(), Value::String("theme_a".into()));
let result = p.get_local_themes(&input);
let entry = result.get("type_a").unwrap().as_object().unwrap();
assert!(entry.contains_key("config"));
}
#[test]
fn get_local_themes_empty_input_returns_empty() {
let p = IPythonPowerline::new();
let empty = Map::new();
let result = p.get_local_themes(&empty);
assert!(result.is_empty());
}
#[test]
fn get_local_themes_applies_theme_override() {
let mut p = IPythonPowerline::new();
let mut overlay = Map::new();
overlay.insert("seg".to_string(), Value::String("v".into()));
p.theme_overrides
.insert("theme_a".to_string(), Value::Object(overlay));
let mut input = Map::new();
input.insert("matcher".to_string(), Value::String("theme_a".into()));
let result = p.get_local_themes(&input);
let entry = result.get("matcher").unwrap().as_object().unwrap();
let config = entry.get("config").unwrap().as_object().unwrap();
assert_eq!(config.get("seg"), Some(&Value::String("v".into())));
}
#[test]
fn do_setup_assigns_powerline_to_each_live_wref() {
let p = IPythonPowerline::new();
let mut wrefs: Vec<Option<Map<String, Value>>> =
vec![Some(Map::new()), None, Some(Map::new())];
p.do_setup(&mut wrefs);
assert!(wrefs[0].as_ref().unwrap().contains_key("powerline"));
assert!(wrefs[2].as_ref().unwrap().contains_key("powerline"));
assert!(wrefs[1].is_none());
}
#[test]
fn rewrite_result_eq_compares_prompt() {
assert_eq!(RewriteResult::new("a"), RewriteResult::new("a"));
assert_ne!(RewriteResult::new("a"), RewriteResult::new("b"));
}
#[test]
fn get_local_themes_non_string_value_uses_empty_theme_name() {
let p = IPythonPowerline::new();
let mut input = Map::new();
input.insert("k".to_string(), json!(42));
let result = p.get_local_themes(&input);
assert!(result.contains_key("k"));
}
}