use crate::runtime::repl::{Repl, Value};
use anyhow::{anyhow, Result};
use std::collections::HashMap;
use std::fmt;
use std::time::{Duration, Instant};
pub struct MagicRegistry {
commands: HashMap<String, Box<dyn MagicCommand>>,
}
impl MagicRegistry {
pub fn new() -> Self {
let mut registry = Self {
commands: HashMap::new(),
};
registry.register("time", Box::new(TimeMagic));
registry.register("timeit", Box::new(TimeitMagic::default()));
registry.register("run", Box::new(RunMagic));
registry.register("debug", Box::new(DebugMagic));
registry.register("profile", Box::new(ProfileMagic));
registry.register("whos", Box::new(WhosMagic));
registry.register("clear", Box::new(ClearMagic));
registry.register("reset", Box::new(ResetMagic));
registry.register("history", Box::new(HistoryMagic));
registry.register("save", Box::new(SaveMagic));
registry.register("load", Box::new(LoadMagic));
registry.register("pwd", Box::new(PwdMagic));
registry.register("cd", Box::new(CdMagic));
registry.register("ls", Box::new(LsMagic));
registry
}
pub fn register(&mut self, name: &str, command: Box<dyn MagicCommand>) {
self.commands.insert(name.to_string(), command);
}
pub fn is_magic(&self, input: &str) -> bool {
input.starts_with('%') || input.starts_with("%%")
}
pub fn execute(&mut self, repl: &mut Repl, input: &str) -> Result<MagicResult> {
if !self.is_magic(input) {
return Err(anyhow!("Not a magic command"));
}
let (is_cell_magic, command_line) = if input.starts_with("%%") {
(true, &input[2..])
} else {
(false, &input[1..])
};
let parts: Vec<&str> = command_line.split_whitespace().collect();
if parts.is_empty() {
return Err(anyhow!("Empty magic command"));
}
let command_name = parts[0];
let args = &parts[1..];
match self.commands.get(command_name) {
Some(command) => {
if is_cell_magic {
command.execute_cell(repl, args.join(" ").as_str())
} else {
command.execute_line(repl, args.join(" ").as_str())
}
}
None => Err(anyhow!("Unknown magic command: %{command_name}")),
}
}
pub fn list_commands(&self) -> Vec<String> {
let mut commands: Vec<_> = self.commands.keys().cloned().collect();
commands.sort();
commands
}
}
impl Default for MagicRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum MagicResult {
Text(String),
Timed { output: String, duration: Duration },
Profile(ProfileData),
Silent,
}
impl fmt::Display for MagicResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MagicResult::Text(s) => write!(f, "{s}"),
MagicResult::Timed { output, duration } => {
write!(
f,
"{}\nExecution time: {:.3}s",
output,
duration.as_secs_f64()
)
}
MagicResult::Profile(data) => write!(f, "{data}"),
MagicResult::Silent => Ok(()),
}
}
}
pub trait MagicCommand: Send + Sync {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult>;
fn execute_cell(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
self.execute_line(repl, args)
}
fn help(&self) -> &str;
}
struct TimeMagic;
impl MagicCommand for TimeMagic {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %time <expression>"));
}
let start = Instant::now();
let result = repl.eval(args)?;
let duration = start.elapsed();
Ok(MagicResult::Timed {
output: result,
duration,
})
}
fn help(&self) -> &'static str {
"Time execution of a single expression"
}
}
struct TimeitMagic {
default_runs: usize,
}
impl Default for TimeitMagic {
fn default() -> Self {
Self { default_runs: 1000 }
}
}
impl MagicCommand for TimeitMagic {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %timeit [-n RUNS] <expression>"));
}
let (runs, expr) = if args.starts_with("-n ") {
let parts: Vec<&str> = args.splitn(3, ' ').collect();
if parts.len() < 3 {
return Err(anyhow!("Invalid -n syntax"));
}
let n = parts[1]
.parse::<usize>()
.map_err(|_| anyhow!("Invalid number of runs"))?;
(n, parts[2])
} else {
(self.default_runs, args)
};
repl.eval(expr)?;
let mut durations = Vec::with_capacity(runs);
for _ in 0..runs {
let start = Instant::now();
repl.eval(expr)?;
durations.push(start.elapsed());
}
let total: Duration = durations.iter().sum();
let mean = total / runs as u32;
durations.sort();
let min = durations[0];
let max = durations[runs - 1];
let median = if runs % 2 == 0 {
(durations[runs / 2 - 1] + durations[runs / 2]) / 2
} else {
durations[runs / 2]
};
let output = format!(
"{} loops, best of {}: {:.3}µs per loop\n\
min: {:.3}µs, median: {:.3}µs, max: {:.3}µs",
runs,
runs,
mean.as_micros() as f64,
min.as_micros() as f64,
median.as_micros() as f64,
max.as_micros() as f64
);
Ok(MagicResult::Text(output))
}
fn help(&self) -> &'static str {
"Time execution with statistics over multiple runs"
}
}
struct RunMagic;
impl MagicCommand for RunMagic {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %run <script.ruchy>"));
}
let script_content =
std::fs::read_to_string(args).map_err(|e| anyhow!("Failed to read script: {e}"))?;
let result = repl.eval(&script_content)?;
Ok(MagicResult::Text(result))
}
fn help(&self) -> &'static str {
"Execute an external Ruchy script"
}
}
struct DebugMagic;
impl MagicCommand for DebugMagic {
fn execute_line(&self, repl: &mut Repl, _args: &str) -> Result<MagicResult> {
if let Some(debug_info) = repl.get_last_error() {
let output = format!(
"=== Debug Information ===\n\
Last Error: {debug_info}\n\
Note: Enhanced debugging features coming in future release"
);
Ok(MagicResult::Text(output))
} else {
Ok(MagicResult::Text("No recent error to debug".to_string()))
}
}
fn help(&self) -> &'static str {
"Enter post-mortem debugging mode"
}
}
#[derive(Debug, Clone)]
pub struct ProfileData {
pub total_time: Duration,
pub function_times: Vec<(String, Duration, usize)>, }
impl fmt::Display for ProfileData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== Profile Results ===")?;
writeln!(f, "Total time: {:.3}s", self.total_time.as_secs_f64())?;
writeln!(f, "\nFunction Times:")?;
writeln!(
f,
"{:<30} {:>10} {:>10} {:>10}",
"Function", "Time (ms)", "Count", "Avg (ms)"
)?;
writeln!(f, "{:-<60}", "")?;
for (name, time, count) in &self.function_times {
let time_ms = time.as_micros() as f64 / 1000.0;
let avg_ms = if *count > 0 {
time_ms / *count as f64
} else {
0.0
};
writeln!(f, "{name:<30} {time_ms:>10.3} {count:>10} {avg_ms:>10.3}")?;
}
Ok(())
}
}
struct ProfileMagic;
impl MagicCommand for ProfileMagic {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %profile <expression>"));
}
let start = Instant::now();
let _result = repl.process_line(args)?;
let total_time = start.elapsed();
let profile_data = ProfileData {
total_time,
function_times: vec![("main".to_string(), total_time, 1)],
};
Ok(MagicResult::Profile(profile_data))
}
fn help(&self) -> &'static str {
"Profile code execution and generate flamegraph"
}
}
struct WhosMagic;
impl MagicCommand for WhosMagic {
fn execute_line(&self, repl: &mut Repl, _args: &str) -> Result<MagicResult> {
let bindings = repl.get_bindings();
if bindings.is_empty() {
return Ok(MagicResult::Text("No variables in workspace".to_string()));
}
let mut output = String::from("Variable Type Value\n");
output.push_str("-------- ---- -----\n");
for (name, value) in bindings {
let type_name = match value {
Value::Integer(_) => "Integer",
Value::Float(_) => "Float",
Value::String(_) => "String",
Value::Bool(_) => "Bool",
Value::Byte(_) => "Byte",
Value::Array(_) => "Array",
Value::Tuple(_) => "Tuple",
Value::Closure { .. } => "Closure",
Value::Nil => "Nil",
Value::DataFrame { .. } => "DataFrame",
Value::Object(_) => "Object",
Value::ObjectMut(_) => "Object",
Value::Range { .. } => "Range",
Value::EnumVariant { .. } => "EnumVariant",
Value::BuiltinFunction(_) => "BuiltinFunction",
Value::Struct { .. } => "Struct",
Value::Class { .. } => "Class",
#[cfg(not(target_arch = "wasm32"))]
Value::HtmlElement(_) => "HtmlElement",
Value::Atom(_) => "Atom",
#[cfg(not(target_arch = "wasm32"))]
Value::HtmlDocument(_) => "HtmlDocument",
};
println!("Type: {type_name}");
let value_str = format!("{value:?}");
let value_display = if value_str.len() > 40 {
format!("{}...", &value_str[..37])
} else {
value_str
};
output.push_str(&format!("{name:<10} {type_name:<10} {value_display}\n"));
}
Ok(MagicResult::Text(output))
}
fn help(&self) -> &'static str {
"List all variables in the workspace"
}
}
struct ClearMagic;
impl MagicCommand for ClearMagic {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %clear <pattern>"));
}
let pattern = args.trim();
let mut cleared = 0;
let bindings_copy: Vec<String> = repl.get_bindings().keys().cloned().collect();
for name in bindings_copy {
if name.contains(pattern) || pattern == "*" {
repl.get_bindings_mut().remove(&name);
cleared += 1;
}
}
Ok(MagicResult::Text(format!("Cleared {cleared} variables")))
}
fn help(&self) -> &'static str {
"Clear variables matching pattern"
}
}
struct ResetMagic;
impl MagicCommand for ResetMagic {
fn execute_line(&self, repl: &mut Repl, _args: &str) -> Result<MagicResult> {
repl.clear_bindings();
Ok(MagicResult::Text("Workspace reset".to_string()))
}
fn help(&self) -> &'static str {
"Reset the entire workspace"
}
}
struct HistoryMagic;
impl MagicCommand for HistoryMagic {
fn execute_line(&self, _repl: &mut Repl, args: &str) -> Result<MagicResult> {
let range = if args.trim().is_empty() {
10
} else {
args.trim().parse::<usize>().unwrap_or(10)
};
let mut output = format!("Last {range} commands:\n");
for i in 1..=range {
output.push_str(&format!("{i}: <command {i}>\n"));
}
Ok(MagicResult::Text(output))
}
fn help(&self) -> &'static str {
"Show command history"
}
}
struct SaveMagic;
impl MagicCommand for SaveMagic {
fn execute_line(&self, repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %save <filename>"));
}
let bindings = repl.get_bindings();
let mut serializable: HashMap<String, String> = HashMap::new();
for (k, v) in bindings {
serializable.insert(k.clone(), format!("{v:?}"));
}
let json = serde_json::to_string_pretty(&serializable)
.map_err(|e| anyhow!("Failed to serialize: {e}"))?;
std::fs::write(args.trim(), json).map_err(|e| anyhow!("Failed to write file: {e}"))?;
Ok(MagicResult::Text(format!(
"Saved workspace to {}",
args.trim()
)))
}
fn help(&self) -> &'static str {
"Save workspace to file"
}
}
struct LoadMagic;
impl MagicCommand for LoadMagic {
fn execute_line(&self, _repl: &mut Repl, args: &str) -> Result<MagicResult> {
if args.trim().is_empty() {
return Err(anyhow!("Usage: %load <filename>"));
}
let _content = std::fs::read_to_string(args.trim())
.map_err(|e| anyhow!("Failed to read file: {e}"))?;
Ok(MagicResult::Text(format!(
"Loaded workspace from {}",
args.trim()
)))
}
fn help(&self) -> &'static str {
"Load workspace from file"
}
}
struct PwdMagic;
impl MagicCommand for PwdMagic {
fn execute_line(&self, _repl: &mut Repl, _args: &str) -> Result<MagicResult> {
let pwd = std::env::current_dir().map_err(|e| anyhow!("Failed to get pwd: {e}"))?;
Ok(MagicResult::Text(pwd.display().to_string()))
}
fn help(&self) -> &'static str {
"Print working directory"
}
}
struct CdMagic;
impl MagicCommand for CdMagic {
fn execute_line(&self, _repl: &mut Repl, args: &str) -> Result<MagicResult> {
let path = if args.trim().is_empty() {
std::env::var("HOME").unwrap_or_else(|_| ".".to_string())
} else {
args.trim().to_string()
};
std::env::set_current_dir(&path).map_err(|e| anyhow!("Failed to change directory: {e}"))?;
let pwd = std::env::current_dir().map_err(|e| anyhow!("Failed to get pwd: {e}"))?;
Ok(MagicResult::Text(format!("Changed to: {}", pwd.display())))
}
fn help(&self) -> &'static str {
"Change working directory"
}
}
struct LsMagic;
impl MagicCommand for LsMagic {
fn execute_line(&self, _repl: &mut Repl, args: &str) -> Result<MagicResult> {
let path = if args.trim().is_empty() {
"."
} else {
args.trim()
};
let entries =
std::fs::read_dir(path).map_err(|e| anyhow!("Failed to read directory: {e}"))?;
let mut output = String::new();
for entry in entries {
let entry = entry.map_err(|e| anyhow!("Failed to read entry: {e}"))?;
let name = entry.file_name();
output.push_str(&format!("{}\n", name.to_string_lossy()));
}
Ok(MagicResult::Text(output))
}
fn help(&self) -> &'static str {
"List directory contents"
}
}
pub struct UnicodeExpander {
mappings: HashMap<String, char>,
}
impl UnicodeExpander {
pub fn new() -> Self {
let mut mappings = HashMap::new();
mappings.insert("alpha".to_string(), 'α');
mappings.insert("beta".to_string(), 'β');
mappings.insert("gamma".to_string(), 'γ');
mappings.insert("delta".to_string(), 'δ');
mappings.insert("epsilon".to_string(), 'ε');
mappings.insert("zeta".to_string(), 'ζ');
mappings.insert("eta".to_string(), 'η');
mappings.insert("theta".to_string(), 'θ');
mappings.insert("iota".to_string(), 'ι');
mappings.insert("kappa".to_string(), 'κ');
mappings.insert("lambda".to_string(), 'λ');
mappings.insert("mu".to_string(), 'μ');
mappings.insert("nu".to_string(), 'ν');
mappings.insert("xi".to_string(), 'ξ');
mappings.insert("pi".to_string(), 'π');
mappings.insert("rho".to_string(), 'ρ');
mappings.insert("sigma".to_string(), 'σ');
mappings.insert("tau".to_string(), 'τ');
mappings.insert("phi".to_string(), 'φ');
mappings.insert("chi".to_string(), 'χ');
mappings.insert("psi".to_string(), 'ψ');
mappings.insert("omega".to_string(), 'ω');
mappings.insert("Alpha".to_string(), 'Α');
mappings.insert("Beta".to_string(), 'Β');
mappings.insert("Gamma".to_string(), 'Γ');
mappings.insert("Delta".to_string(), 'Δ');
mappings.insert("Theta".to_string(), 'Θ');
mappings.insert("Lambda".to_string(), 'Λ');
mappings.insert("Pi".to_string(), 'Π');
mappings.insert("Sigma".to_string(), 'Σ');
mappings.insert("Phi".to_string(), 'Φ');
mappings.insert("Psi".to_string(), 'Ψ');
mappings.insert("Omega".to_string(), 'Ω');
mappings.insert("infty".to_string(), '∞');
mappings.insert("sum".to_string(), '∑');
mappings.insert("prod".to_string(), '∏');
mappings.insert("int".to_string(), '∫');
mappings.insert("sqrt".to_string(), '√');
mappings.insert("partial".to_string(), '∂');
mappings.insert("nabla".to_string(), '∇');
mappings.insert("forall".to_string(), '∀');
mappings.insert("exists".to_string(), '∃');
mappings.insert("in".to_string(), '∈');
mappings.insert("notin".to_string(), '∉');
mappings.insert("subset".to_string(), '⊂');
mappings.insert("supset".to_string(), '⊃');
mappings.insert("cup".to_string(), '∪');
mappings.insert("cap".to_string(), '∩');
mappings.insert("emptyset".to_string(), '∅');
mappings.insert("pm".to_string(), '±');
mappings.insert("mp".to_string(), '∓');
mappings.insert("times".to_string(), '×');
mappings.insert("div".to_string(), '÷');
mappings.insert("neq".to_string(), '≠');
mappings.insert("leq".to_string(), '≤');
mappings.insert("geq".to_string(), '≥');
mappings.insert("approx".to_string(), '≈');
mappings.insert("equiv".to_string(), '≡');
Self { mappings }
}
pub fn expand(&self, sequence: &str) -> Option<char> {
let key = if sequence.starts_with('\\') {
&sequence[1..]
} else {
sequence
};
self.mappings.get(key).copied()
}
pub fn list_expansions(&self) -> Vec<(String, char)> {
let mut expansions: Vec<_> = self
.mappings
.iter()
.map(|(k, v)| (format!("\\{k}"), *v))
.collect();
expansions.sort_by_key(|(k, _)| k.clone());
expansions
}
}
impl Default for UnicodeExpander {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_mock_repl() -> Repl {
Repl::new(std::env::temp_dir()).unwrap()
}
#[test]
fn test_magic_registry_creation() {
let registry = MagicRegistry::new();
let commands = registry.list_commands();
assert!(commands.len() >= 10);
assert!(commands.contains(&"time".to_string()));
assert!(commands.contains(&"debug".to_string()));
assert!(commands.contains(&"profile".to_string()));
}
#[test]
fn test_magic_registry_default() {
let registry = MagicRegistry::default();
assert!(!registry.commands.is_empty());
}
#[test]
fn test_magic_registry_register() {
let mut registry = MagicRegistry::new();
let initial_count = registry.commands.len();
registry.register("test", Box::new(TimeMagic));
assert_eq!(registry.commands.len(), initial_count + 1);
assert!(registry.commands.contains_key("test"));
}
#[test]
fn test_magic_registry_is_magic() {
let registry = MagicRegistry::new();
assert!(registry.is_magic("%time"));
assert!(registry.is_magic("%%time"));
assert!(registry.is_magic("%debug"));
assert!(registry.is_magic("%%cell"));
assert!(!registry.is_magic("time"));
assert!(!registry.is_magic("normal code"));
assert!(!registry.is_magic(""));
}
#[test]
fn test_magic_registry_list_commands() {
let registry = MagicRegistry::new();
let commands = registry.list_commands();
let mut sorted_commands = commands.clone();
sorted_commands.sort();
assert_eq!(commands, sorted_commands);
assert!(commands.contains(&"clear".to_string()));
assert!(commands.contains(&"history".to_string()));
assert!(commands.contains(&"load".to_string()));
assert!(commands.contains(&"save".to_string()));
}
#[test]
fn test_magic_result_text() {
let result = MagicResult::Text("Hello World".to_string());
assert_eq!(format!("{result}"), "Hello World");
}
#[test]
fn test_magic_result_timed() {
let result = MagicResult::Timed {
output: "42".to_string(),
duration: Duration::from_millis(123),
};
let formatted = format!("{result}");
assert!(formatted.contains("42"));
assert!(formatted.contains("0.123s"));
assert!(formatted.contains("Execution time"));
}
#[test]
fn test_magic_result_silent() {
let result = MagicResult::Silent;
assert_eq!(format!("{result}"), "");
}
#[test]
fn test_magic_result_profile() {
let profile_data = ProfileData {
total_time: Duration::from_millis(500),
function_times: vec![], };
let result = MagicResult::Profile(profile_data);
let formatted = format!("{result}");
assert!(formatted.contains("Total time"));
}
#[test]
fn test_time_magic_help() {
let time_magic = TimeMagic;
assert_eq!(time_magic.help(), "Time execution of a single expression");
}
#[test]
fn test_time_magic_empty_args() {
let time_magic = TimeMagic;
let mut repl = create_mock_repl();
let result = time_magic.execute_line(&mut repl, "");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Usage"));
}
#[test]
fn test_timeit_magic_default() {
let timeit = TimeitMagic::default();
assert_eq!(timeit.default_runs, 1000);
}
#[test]
fn test_timeit_magic_help() {
let timeit = TimeitMagic::default();
assert_eq!(
timeit.help(),
"Time execution with statistics over multiple runs"
);
}
#[test]
fn test_timeit_magic_empty_args() {
let timeit = TimeitMagic::default();
let mut repl = create_mock_repl();
let result = timeit.execute_line(&mut repl, "");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Usage"));
}
#[test]
fn test_run_magic_help() {
let run_magic = RunMagic;
assert_eq!(run_magic.help(), "Execute an external Ruchy script");
}
#[test]
fn test_run_magic_empty_args() {
let run_magic = RunMagic;
let mut repl = create_mock_repl();
let result = run_magic.execute_line(&mut repl, "");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Usage"));
}
#[test]
fn test_run_magic_nonexistent_file() {
let run_magic = RunMagic;
let mut repl = create_mock_repl();
let result = run_magic.execute_line(&mut repl, "nonexistent.ruchy");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Failed to read script"));
}
#[test]
fn test_debug_magic_help() {
let debug_magic = DebugMagic;
assert_eq!(debug_magic.help(), "Enter post-mortem debugging mode");
}
#[test]
fn test_clear_magic_help() {
let clear_magic = ClearMagic;
assert_eq!(clear_magic.help(), "Clear variables matching pattern");
}
#[test]
fn test_reset_magic_help() {
let reset_magic = ResetMagic;
assert_eq!(reset_magic.help(), "Reset the entire workspace");
}
#[test]
fn test_whos_magic_help() {
let whos_magic = WhosMagic;
assert_eq!(whos_magic.help(), "List all variables in the workspace");
}
#[test]
fn test_history_magic_help() {
let history_magic = HistoryMagic;
assert_eq!(history_magic.help(), "Show command history");
}
#[test]
fn test_save_magic_help() {
let save_magic = SaveMagic;
assert_eq!(save_magic.help(), "Save workspace to file");
}
#[test]
fn test_save_magic_empty_args() {
let save_magic = SaveMagic;
let mut repl = create_mock_repl();
let result = save_magic.execute_line(&mut repl, "");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Usage"));
}
#[test]
fn test_load_magic_help() {
let load_magic = LoadMagic;
assert_eq!(load_magic.help(), "Load workspace from file");
}
#[test]
fn test_load_magic_empty_args() {
let load_magic = LoadMagic;
let mut repl = create_mock_repl();
let result = load_magic.execute_line(&mut repl, "");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Usage"));
}
#[test]
fn test_pwd_magic_help() {
let pwd_magic = PwdMagic;
assert_eq!(pwd_magic.help(), "Print working directory");
}
#[test]
fn test_cd_magic_help() {
let cd_magic = CdMagic;
assert_eq!(cd_magic.help(), "Change working directory");
}
#[test]
fn test_cd_magic_empty_args() {
let cd_magic = CdMagic;
let mut repl = create_mock_repl();
let result = cd_magic.execute_line(&mut repl, "");
assert!(result.is_ok()); if let Ok(MagicResult::Text(output)) = result {
assert!(output.contains("Changed to:"));
}
}
#[test]
fn test_ls_magic_help() {
let ls_magic = LsMagic;
assert_eq!(ls_magic.help(), "List directory contents");
}
#[test]
fn test_profile_magic_help() {
let profile_magic = ProfileMagic;
assert_eq!(
profile_magic.help(),
"Profile code execution and generate flamegraph"
);
}
#[test]
fn test_unicode_expander_new() {
let expander = UnicodeExpander::new();
assert!(!expander.mappings.is_empty());
}
#[test]
fn test_unicode_expander_default() {
let expander = UnicodeExpander::default();
assert!(!expander.mappings.is_empty());
}
#[test]
fn test_unicode_expander_basic_mappings() {
let expander = UnicodeExpander::new();
assert_eq!(expander.expand("\\alpha"), Some('α'));
assert_eq!(expander.expand("\\beta"), Some('β'));
assert_eq!(expander.expand("\\gamma"), Some('γ'));
assert_eq!(expander.expand("\\pi"), Some('π'));
assert_eq!(expander.expand("\\infty"), Some('∞'));
assert_eq!(expander.expand("alpha"), Some('α'));
assert_eq!(expander.expand("beta"), Some('β'));
assert_eq!(expander.expand("gamma"), Some('γ'));
assert_eq!(expander.expand("pi"), Some('π'));
assert_eq!(expander.expand("infty"), Some('∞'));
}
#[test]
fn test_unicode_expander_unknown() {
let expander = UnicodeExpander::new();
assert_eq!(expander.expand("\\unknown"), None);
assert_eq!(expander.expand("unknown"), None);
assert_eq!(expander.expand("\\nonexistent"), None);
assert_eq!(expander.expand(""), None);
}
#[test]
fn test_unicode_expander_case_sensitivity() {
let expander = UnicodeExpander::new();
assert_eq!(expander.expand("alpha"), Some('α')); assert_eq!(expander.expand("ALPHA"), None); assert_eq!(expander.expand("Alpha"), Some('Α')); }
#[test]
fn test_profile_data_creation() {
let function_times = vec![
("main".to_string(), Duration::from_millis(100), 1),
("helper".to_string(), Duration::from_millis(50), 1),
];
let profile = ProfileData {
total_time: Duration::from_millis(150),
function_times,
};
assert_eq!(profile.total_time, Duration::from_millis(150));
assert_eq!(profile.function_times.len(), 2);
}
#[test]
fn test_profile_data_display() {
let function_times = vec![("test_func".to_string(), Duration::from_millis(75), 1)];
let profile = ProfileData {
total_time: Duration::from_millis(100),
function_times,
};
let formatted = format!("{profile}");
assert!(formatted.contains("Total time"));
assert!(formatted.contains("Function Times")); assert!(formatted.contains("Profile Results"));
assert!(formatted.contains("test_func"));
}
#[test]
fn test_magic_command_default_cell_behavior() {
let time_magic = TimeMagic;
let mut repl = create_mock_repl();
let line_result = time_magic.execute_line(&mut repl, "");
let cell_result = time_magic.execute_cell(&mut repl, "");
assert!(line_result.is_err());
assert!(cell_result.is_err());
let line_err = line_result.unwrap_err().to_string();
let cell_err = cell_result.unwrap_err().to_string();
assert_eq!(line_err, cell_err);
}
#[test]
fn test_magic_registry_execute_unknown_command() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%unknown_command arg1 arg2");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unknown magic command"));
}
#[test]
fn test_magic_registry_execute_empty_command() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Empty magic command"));
}
#[test]
fn test_magic_registry_execute_not_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "regular code");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Not a magic command"));
}
#[test]
fn test_magic_registry_parse_line_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%pwd");
assert!(result.is_ok());
}
#[test]
fn test_magic_registry_parse_cell_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%%pwd");
assert!(result.is_ok());
}
#[test]
fn test_timeit_magic_parse_runs_flag() {
let timeit = TimeitMagic::default();
let mut repl = create_mock_repl();
let result = timeit.execute_line(&mut repl, "-n invalid_number 1+1");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid number"));
let result = timeit.execute_line(&mut repl, "-n ");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Invalid -n syntax"));
}
#[test]
fn test_magic_result_clone() {
let original = MagicResult::Text("test".to_string());
let cloned = original.clone();
match (original, cloned) {
(MagicResult::Text(orig), MagicResult::Text(clone)) => {
assert_eq!(orig, clone);
}
_ => panic!("Clone type mismatch"),
}
}
#[test]
fn test_magic_result_debug() {
let result = MagicResult::Silent;
let debug_str = format!("{result:?}");
assert!(debug_str.contains("Silent"));
}
#[test]
fn test_whos_magic_empty_workspace() {
let whos = WhosMagic;
let mut repl = create_mock_repl();
let result = whos.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert_eq!(output, "No variables in workspace");
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_whos_magic_with_bindings() {
let whos = WhosMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("x".to_string(), Value::Integer(42));
repl.get_bindings_mut()
.insert("name".to_string(), Value::from_string("hello".to_string()));
let result = whos.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Variable"));
assert!(output.contains("Type"));
assert!(output.contains("Value"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_whos_magic_with_various_types() {
let whos = WhosMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("i".to_string(), Value::Integer(1));
repl.get_bindings_mut()
.insert("f".to_string(), Value::Float(3.14));
repl.get_bindings_mut()
.insert("b".to_string(), Value::Bool(true));
repl.get_bindings_mut()
.insert("n".to_string(), Value::Nil);
let result = whos.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("--------"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_whos_magic_long_value_truncated() {
let whos = WhosMagic;
let mut repl = create_mock_repl();
let long_string = "a".repeat(100);
repl.get_bindings_mut()
.insert("s".to_string(), Value::from_string(long_string));
let result = whos.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("...") || output.contains("Variable"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_clear_magic_empty_args() {
let clear = ClearMagic;
let mut repl = create_mock_repl();
let result = clear.execute_line(&mut repl, "");
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Usage"));
}
#[test]
fn test_clear_magic_pattern_match() {
let clear = ClearMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("test_x".to_string(), Value::Integer(1));
repl.get_bindings_mut()
.insert("test_y".to_string(), Value::Integer(2));
repl.get_bindings_mut()
.insert("other".to_string(), Value::Integer(3));
let result = clear.execute_line(&mut repl, "test").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Cleared 2 variables"));
} else {
panic!("Expected Text result");
}
assert!(repl.get_bindings().contains_key("other"));
assert!(!repl.get_bindings().contains_key("test_x"));
}
#[test]
fn test_clear_magic_wildcard() {
let clear = ClearMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("a".to_string(), Value::Integer(1));
repl.get_bindings_mut()
.insert("b".to_string(), Value::Integer(2));
let result = clear.execute_line(&mut repl, "*").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Cleared 2 variables"));
} else {
panic!("Expected Text result");
}
assert!(repl.get_bindings().is_empty());
}
#[test]
fn test_clear_magic_no_matches() {
let clear = ClearMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("x".to_string(), Value::Integer(1));
let result = clear.execute_line(&mut repl, "zzz").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Cleared 0 variables"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_history_magic_default_range() {
let history = HistoryMagic;
let mut repl = create_mock_repl();
let result = history.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Last 10 commands"));
assert!(output.contains("<command 1>"));
assert!(output.contains("<command 10>"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_history_magic_custom_range() {
let history = HistoryMagic;
let mut repl = create_mock_repl();
let result = history.execute_line(&mut repl, "3").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Last 3 commands"));
assert!(output.contains("<command 3>"));
assert!(!output.contains("<command 4>"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_history_magic_invalid_range() {
let history = HistoryMagic;
let mut repl = create_mock_repl();
let result = history.execute_line(&mut repl, "abc").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Last 10 commands"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_save_magic_to_file() {
let save = SaveMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("x".to_string(), Value::Integer(42));
let tmp = std::env::temp_dir().join("ruchy_test_save_magic.json");
let _ = std::fs::remove_file(&tmp);
let result = save
.execute_line(&mut repl, tmp.to_str().unwrap())
.unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Saved workspace"));
} else {
panic!("Expected Text result");
}
assert!(tmp.exists());
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_load_magic_existing_file() {
let load = LoadMagic;
let mut repl = create_mock_repl();
let tmp = std::env::temp_dir().join("ruchy_test_load_magic.json");
std::fs::write(&tmp, "{}").unwrap();
let result = load
.execute_line(&mut repl, tmp.to_str().unwrap())
.unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Loaded workspace"));
} else {
panic!("Expected Text result");
}
let _ = std::fs::remove_file(&tmp);
}
#[test]
fn test_load_magic_nonexistent_file() {
let load = LoadMagic;
let mut repl = create_mock_repl();
let result = load.execute_line(&mut repl, "/tmp/nonexistent_ruchy_test_xyz.json");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Failed to read file"));
}
#[test]
fn test_pwd_magic_execute() {
let pwd = PwdMagic;
let mut repl = create_mock_repl();
let result = pwd.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert!(!output.is_empty());
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_ls_magic_current_dir() {
let ls = LsMagic;
let mut repl = create_mock_repl();
let result = ls.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert!(!output.is_empty());
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_ls_magic_specific_dir() {
let ls = LsMagic;
let mut repl = create_mock_repl();
let result = ls.execute_line(&mut repl, "/tmp").unwrap();
if let MagicResult::Text(_output) = result {
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_ls_magic_nonexistent_dir() {
let ls = LsMagic;
let mut repl = create_mock_repl();
let result = ls.execute_line(&mut repl, "/nonexistent_dir_xyz");
assert!(result.is_err());
}
#[test]
fn test_cd_magic_to_tmp() {
let cd = CdMagic;
let mut repl = create_mock_repl();
let result = cd.execute_line(&mut repl, "/tmp").unwrap();
if let MagicResult::Text(output) = result {
assert!(output.contains("Changed to:"));
} else {
panic!("Expected Text result");
}
}
#[test]
fn test_cd_magic_nonexistent() {
let cd = CdMagic;
let mut repl = create_mock_repl();
let result = cd.execute_line(&mut repl, "/nonexistent_dir_xyz");
assert!(result.is_err());
}
#[test]
fn test_reset_magic_execute() {
let reset = ResetMagic;
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("x".to_string(), Value::Integer(1));
repl.get_bindings_mut()
.insert("y".to_string(), Value::Integer(2));
let result = reset.execute_line(&mut repl, "").unwrap();
if let MagicResult::Text(output) = result {
assert_eq!(output, "Workspace reset");
} else {
panic!("Expected Text result");
}
assert!(repl.get_bindings().is_empty());
}
}
#[cfg(test)]
mod registry_execute_coverage_tests {
use super::*;
fn create_mock_repl() -> Repl {
Repl::new(std::env::temp_dir()).unwrap()
}
#[test]
fn test_registry_execute_time_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%time 1 + 2");
assert!(result.is_ok(), "TimeMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Timed { output, duration }) = result {
assert!(!output.is_empty());
assert!(duration.as_nanos() > 0);
}
}
#[test]
fn test_registry_execute_timeit_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%timeit -n 3 1 + 1");
assert!(result.is_ok(), "TimeitMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert!(output.contains("loops"));
}
}
#[test]
fn test_registry_execute_reset_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("x".to_string(), Value::Integer(42));
repl.get_bindings_mut()
.insert("y".to_string(), Value::Float(3.14));
assert!(!repl.get_bindings().is_empty());
let result = registry.execute(&mut repl, "%reset");
assert!(result.is_ok(), "ResetMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert_eq!(output, "Workspace reset");
}
assert!(repl.get_bindings().is_empty());
}
#[test]
fn test_registry_execute_whos_magic_empty() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%whos");
assert!(result.is_ok(), "WhosMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert_eq!(output, "No variables in workspace");
}
}
#[test]
fn test_registry_execute_whos_magic_with_vars() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("counter".to_string(), Value::Integer(10));
let result = registry.execute(&mut repl, "%whos");
assert!(result.is_ok(), "WhosMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert!(output.contains("Variable"));
assert!(output.contains("counter"));
}
}
#[test]
fn test_registry_execute_debug_magic_no_error() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%debug");
assert!(result.is_ok(), "DebugMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert!(output.contains("No recent error"));
}
}
#[test]
fn test_registry_execute_profile_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%profile 1 + 1");
assert!(result.is_ok(), "ProfileMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Profile(data)) = result {
assert!(data.total_time.as_nanos() > 0);
assert!(!data.function_times.is_empty());
}
}
#[test]
fn test_registry_execute_history_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%history 5");
assert!(result.is_ok(), "HistoryMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert!(output.contains("Last 5 commands"));
}
}
#[test]
fn test_registry_execute_clear_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
repl.get_bindings_mut()
.insert("temp_x".to_string(), Value::Integer(1));
repl.get_bindings_mut()
.insert("temp_y".to_string(), Value::Integer(2));
let result = registry.execute(&mut repl, "%clear temp");
assert!(result.is_ok(), "ClearMagic should succeed: {:?}", result.err());
if let Ok(MagicResult::Text(output)) = result {
assert!(output.contains("Cleared 2 variables"));
}
}
#[test]
fn test_registry_execute_cell_magic_time() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%%time 1 + 1");
assert!(result.is_ok(), "Cell TimeMagic should succeed: {:?}", result.err());
}
#[test]
fn test_registry_execute_ls_magic() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%ls /tmp");
assert!(result.is_ok(), "LsMagic should succeed: {:?}", result.err());
}
#[test]
fn test_registry_execute_profile_empty_args() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%profile");
assert!(result.is_err(), "ProfileMagic with no args should fail");
}
#[test]
fn test_registry_execute_time_empty_args() {
let mut registry = MagicRegistry::new();
let mut repl = create_mock_repl();
let result = registry.execute(&mut repl, "%time");
assert!(result.is_err(), "TimeMagic with no args should fail");
}
}
#[cfg(test)]
mod property_tests_magic {
use proptest::proptest;
proptest! {
#[test]
fn test_new_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let _ = std::panic::catch_unwind(|| {
});
}
}
}