1use std::fs;
2use std::io::{self, Write};
3use std::path::Path;
4use std::sync::mpsc;
5use std::time::SystemTime;
6
7use anyhow::{Context, Result};
8
9use crate::cli::{AliasAction, CacheAction, Command, ExecutionSpec, InputSource};
10use crate::engine::{
11 ExecutionPayload, LanguageRegistry, build_install_command, default_language,
12 detect_language_for_source, ensure_known_language, perf_reset, perf_snapshot,
13};
14use crate::language::LanguageSpec;
15use crate::output;
16use crate::repl;
17use crate::version;
18
19pub fn run(command: Command) -> Result<i32> {
20 match command {
21 Command::ShowVersion => {
22 println!("{}", version::describe());
23 Ok(0)
24 }
25 Command::PerfReport => {
26 print_perf_report();
27 Ok(0)
28 }
29 Command::PerfReset => {
30 perf_reset();
31 eprintln!("\x1b[2m[perf] counters reset\x1b[0m");
32 Ok(0)
33 }
34 Command::Cache { action } => cache_command(action),
35 Command::Alias { action } => alias_command(action),
36 other => run_with_registry(other),
37 }
38}
39
40fn run_with_registry(command: Command) -> Result<i32> {
41 let registry = LanguageRegistry::bootstrap();
42
43 match command {
44 Command::Execute(spec) => execute_once(spec, ®istry),
45 Command::Repl {
46 initial_language,
47 detect_language,
48 } => {
49 let language = resolve_language(initial_language, detect_language, None, ®istry)?;
50 repl::run_repl(language, registry, detect_language)
51 }
52 Command::ShowVersion => unreachable!("handled before registry bootstrap"),
53 Command::CheckToolchains => check_toolchains(®istry),
54 Command::ShowVersions { language } => show_versions(®istry, language),
55 Command::Install { language, package } => {
56 let lang = language.unwrap_or_else(|| LanguageSpec::new(default_language()));
57 install_package(&lang, &package)
58 }
59 Command::Bench { spec, iterations } => bench_run(spec, ®istry, iterations),
60 Command::Watch { spec } => watch_run(spec, ®istry),
61 Command::WatchFile {
62 path,
63 language,
64 args,
65 } => watch_run(
66 ExecutionSpec {
67 language,
68 source: InputSource::File(path),
69 detect_language: true,
70 args,
71 json: false,
72 },
73 ®istry,
74 ),
75 Command::Format { path } => format_file(&path),
76 Command::Snippet {
77 language,
78 name,
79 list,
80 } => snippet_command(language, name, list),
81 Command::Doctor => doctor(®istry),
82 Command::Cache { .. } => unreachable!("handled before registry bootstrap"),
83 Command::Alias { .. } => unreachable!("handled before registry bootstrap"),
84 Command::Share { path, port } => share_file(&path, port, ®istry),
85 Command::PerfReport | Command::PerfReset => {
86 unreachable!("handled before registry bootstrap")
87 }
88 }
89}
90
91fn print_perf_report() {
92 let rows = perf_snapshot();
93 if rows.is_empty() {
94 println!("perf_counter,count");
95 return;
96 }
97 println!("perf_counter,count");
98 for (key, value) in rows {
99 println!("{key},{value}");
100 }
101}
102
103fn check_toolchains(registry: &LanguageRegistry) -> Result<i32> {
104 println!("Checking language toolchains...\n");
105
106 let mut available = 0u32;
107 let mut missing = 0u32;
108
109 let mut languages: Vec<_> = registry.known_languages();
110 languages.sort();
111
112 for lang_id in &languages {
113 let spec = LanguageSpec::new(lang_id.to_string());
114 if let Some(engine) = registry.resolve(&spec) {
115 let status = match engine.validate() {
116 Ok(()) => {
117 available += 1;
118 "\x1b[32m OK \x1b[0m"
119 }
120 Err(_) => {
121 missing += 1;
122 "\x1b[31mMISS\x1b[0m"
123 }
124 };
125 println!(" [{status}] {:<14} {}", engine.display_name(), lang_id);
126 }
127 }
128
129 println!();
130 println!(
131 " {} available, {} missing, {} total",
132 available,
133 missing,
134 available + missing
135 );
136
137 if missing > 0 {
138 println!("\n Tip: Install missing toolchains to enable those languages.");
139 }
140
141 Ok(0)
142}
143
144fn show_versions(registry: &LanguageRegistry, language: Option<LanguageSpec>) -> Result<i32> {
145 println!("Language toolchain versions...\n");
146
147 let mut available = 0u32;
148 let mut missing = 0u32;
149
150 let mut languages: Vec<String> = if let Some(lang) = language {
151 vec![lang.canonical_id().to_string()]
152 } else {
153 registry
154 .known_languages()
155 .into_iter()
156 .map(|value| value.to_string())
157 .collect()
158 };
159 languages.sort();
160
161 for lang_id in &languages {
162 let spec = LanguageSpec::new(lang_id.to_string());
163 if let Some(engine) = registry.resolve(&spec) {
164 match engine.toolchain_version() {
165 Ok(Some(version)) => {
166 available += 1;
167 println!(
168 " [\x1b[32m OK \x1b[0m] {:<14} {} - {}",
169 engine.display_name(),
170 lang_id,
171 version
172 );
173 }
174 Ok(None) => {
175 available += 1;
176 println!(
177 " [\x1b[33m ?? \x1b[0m] {:<14} {} - unknown",
178 engine.display_name(),
179 lang_id
180 );
181 }
182 Err(_) => {
183 missing += 1;
184 println!(
185 " [\x1b[31mMISS\x1b[0m] {:<14} {}",
186 engine.display_name(),
187 lang_id
188 );
189 }
190 }
191 }
192 }
193
194 println!();
195 println!(
196 " {} available, {} missing, {} total",
197 available,
198 missing,
199 available + missing
200 );
201
202 if missing > 0 {
203 println!("\n Tip: Install missing toolchains to enable those languages.");
204 }
205
206 Ok(0)
207}
208
209fn execute_once(spec: ExecutionSpec, registry: &LanguageRegistry) -> Result<i32> {
210 let payload = ExecutionPayload::from_input_source(&spec.source, &spec.args)
211 .context("failed to materialize execution payload")?;
212 let language = resolve_language(
213 spec.language,
214 spec.detect_language,
215 Some(&payload),
216 registry,
217 )?;
218
219 let engine = registry
220 .resolve(&language)
221 .context("failed to resolve language engine")?;
222
223 if let Err(e) = engine.validate() {
224 let display = engine.display_name();
225 let id = engine.id();
226 eprintln!(
227 "Warning: {display} ({id}) toolchain not found: {e:#}\n\
228 Install the required toolchain and ensure it is on your PATH."
229 );
230 return Err(e.context(format!("{display} is not available")));
231 }
232
233 let outcome = match engine.execute(&payload) {
234 Ok(outcome) => outcome,
235 Err(err) if err.to_string().contains("Execution timed out") => {
236 eprintln!(
237 "[run] Execution timed out after {}s",
238 crate::runtime::timeout_secs()
239 );
240 return Ok(124);
241 }
242 Err(err) => return Err(err),
243 };
244
245 if spec.json {
246 let exit_code = outcome
247 .exit_code
248 .unwrap_or(if outcome.success() { 0 } else { 1 });
249 let version = engine
250 .toolchain_version()
251 .ok()
252 .flatten()
253 .unwrap_or_default();
254 let envelope = serde_json::json!({
255 "language": engine.id(),
256 "stdout": outcome.stdout,
257 "stderr": outcome.stderr,
258 "exit_code": exit_code,
259 "duration_ms": outcome.duration.as_millis(),
260 "toolchain_version": version,
261 });
262 println!("{}", serde_json::to_string(&envelope)?);
263 return Ok(exit_code);
264 }
265
266 if !outcome.stdout.is_empty() {
267 print!("{}", outcome.stdout);
268 io::stdout().flush().ok();
269 }
270 if !outcome.stderr.is_empty() {
271 let formatted =
272 output::format_stderr(engine.display_name(), &outcome.stderr, outcome.success());
273 eprint!("{formatted}");
274 io::stderr().flush().ok();
275 }
276
277 let show_timing = crate::runtime::timing_enabled();
279 if show_timing || outcome.duration.as_millis() > 1000 {
280 eprintln!(
281 "\x1b[2m[{} {}ms]\x1b[0m",
282 engine.display_name(),
283 outcome.duration.as_millis()
284 );
285 }
286
287 if std::env::var("RUN_PERF_REPORT").is_ok_and(|v| v == "1" || v == "true") {
288 eprintln!("\x1b[2m[perf]\x1b[0m");
289 for (key, value) in perf_snapshot() {
290 eprintln!("\x1b[2m {key}={value}\x1b[0m");
291 }
292 }
293
294 Ok(outcome
295 .exit_code
296 .unwrap_or(if outcome.success() { 0 } else { 1 }))
297}
298
299fn install_package(language: &LanguageSpec, package: &str) -> Result<i32> {
300 let lang_id = language.canonical_id();
301 let override_key = format!("RUN_INSTALL_COMMAND_{}", lang_id.to_ascii_uppercase());
302 let override_value = std::env::var(&override_key).ok();
303
304 let Some(mut cmd) = build_install_command(lang_id, package) else {
305 if override_value.is_some() {
306 eprintln!(
307 "\x1b[31mError:\x1b[0m {override_key} is set but could not be parsed.\n\
308 Provide a valid command, e.g. {override_key}=\"uv pip install {{package}}\""
309 );
310 return Ok(1);
311 }
312 eprintln!(
313 "\x1b[31mError:\x1b[0m No package manager available for '{lang_id}'.\n\
314 This language doesn't have a standard CLI package manager.\n\
315 Tip: You can override with {override_key}=\"<cmd> {{package}}\"",
316 );
317 return Ok(1);
318 };
319
320 eprintln!("\x1b[36m[run]\x1b[0m Installing '{package}' for {lang_id}...");
321
322 let result = cmd
323 .stdin(std::process::Stdio::inherit())
324 .stdout(std::process::Stdio::inherit())
325 .stderr(std::process::Stdio::inherit())
326 .status();
327
328 match result {
329 Ok(status) if status.success() => {
330 eprintln!("\x1b[32m[run]\x1b[0m Successfully installed '{package}' for {lang_id}");
331 Ok(0)
332 }
333 Ok(status) => {
334 eprintln!("\x1b[31m[run]\x1b[0m Failed to install '{package}' for {lang_id}");
335 Ok(status.code().unwrap_or(1))
336 }
337 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
338 let program = cmd.get_program().to_string_lossy();
339 eprintln!("\x1b[31m[run]\x1b[0m Package manager not found: {program}");
340 eprintln!("Tip: install it or set {override_key}=\"<cmd> {{package}}\"");
341 Ok(1)
342 }
343 Err(err) => {
344 Err(err).with_context(|| format!("failed to run package manager for {lang_id}"))
345 }
346 }
347}
348
349fn cache_command(action: CacheAction) -> Result<i32> {
350 match action {
351 CacheAction::Stats => {
352 let stats = crate::cache::stats()?;
353 println!("Cache: {}", crate::cache::root_dir().display());
354 println!("entries: {}", stats.entries);
355 println!("bytes: {}", stats.total_bytes);
356 for (lang, count) in stats.by_language {
357 println!("{lang}: {count}");
358 }
359 Ok(0)
360 }
361 CacheAction::Clear => {
362 crate::cache::clear()?;
363 println!("[run] cache cleared");
364 Ok(0)
365 }
366 CacheAction::ClearLang(lang) => {
367 crate::cache::clear_lang(&lang)?;
368 println!("[run] cache cleared for {lang}");
369 Ok(0)
370 }
371 }
372}
373
374fn snippet_command(language: LanguageSpec, name: Option<String>, list: bool) -> Result<i32> {
375 let language = language.canonical_id().to_string();
376 let names = crate::templates::names_for_language(&language);
377 if list {
378 if names.is_empty() {
379 eprintln!("[run] No snippets available for {language}");
380 return Ok(2);
381 }
382 for name in names {
383 println!("{name}");
384 }
385 return Ok(0);
386 }
387
388 let Some(name) = name else {
389 eprintln!(
390 "[run] snippet requires a template name. Available: {}",
391 names.join(", ")
392 );
393 return Ok(2);
394 };
395
396 if let Some(template) = crate::templates::find(&language, &name) {
397 print!("{}", template.source);
398 io::stdout().flush().ok();
399 Ok(0)
400 } else {
401 eprintln!(
402 "[run] Unknown snippet '{name}' for {language}. Available: {}",
403 names.join(", ")
404 );
405 Ok(2)
406 }
407}
408
409fn alias_command(action: AliasAction) -> Result<i32> {
410 match action {
411 AliasAction::List => {
412 println!("{:<16} Language", "Alias");
413 println!("────────────────────────");
414 for (alias, language) in crate::language::known_language_aliases() {
415 println!("{alias:<16} {language}");
416 }
417
418 let custom = crate::language::known_custom_language_aliases();
419 if !custom.is_empty() {
420 println!();
421 println!(
422 "Custom aliases ({})",
423 crate::aliases::config_path().display()
424 );
425 println!("{:<16} Language", "Alias");
426 println!("────────────────────────");
427 for (alias, language) in custom {
428 println!("{alias:<16} {language}");
429 }
430 }
431 Ok(0)
432 }
433 AliasAction::Add { alias, language } => {
434 let registry = LanguageRegistry::bootstrap();
435 crate::aliases::add_alias(&alias, &language, ®istry)?;
436 Ok(0)
437 }
438 AliasAction::Remove { alias } => {
439 crate::aliases::remove_alias(&alias)?;
440 Ok(0)
441 }
442 }
443}
444
445fn format_file(path: &Path) -> Result<i32> {
446 if !path.is_file() {
447 eprintln!("[run] File not found: {}", path.display());
448 return Ok(1);
449 }
450
451 let Some(lang) = language_from_path(path) else {
452 eprintln!("[run] No formatter available for unknown");
453 return Ok(2);
454 };
455 let candidates: &[(&str, &[&str])] = match lang {
456 "python" => &[("black", &[]), ("autopep8", &["-i"])],
457 "javascript" | "typescript" => &[("prettier", &["--write"])],
458 "rust" => &[("rustfmt", &[])],
459 "go" => &[("gofmt", &["-w"])],
460 "c" | "cpp" => &[("clang-format", &["-i"])],
461 "java" => &[("google-java-format", &["-i"])],
462 _ => &[],
463 };
464 if candidates.is_empty() {
465 eprintln!("[run] No formatter available for {lang}");
466 return Ok(2);
467 }
468
469 for (program, args) in candidates {
470 let Ok(binary) = which::which(program) else {
471 continue;
472 };
473 let status = std::process::Command::new(binary)
474 .args(*args)
475 .arg(path)
476 .status()
477 .with_context(|| format!("failed to run formatter {program}"))?;
478 return Ok(if status.success() {
479 0
480 } else {
481 eprintln!("[run] formatter {program} failed");
482 1
483 });
484 }
485
486 eprintln!("[run] Formatter not found for {lang}");
487 Ok(2)
488}
489
490fn doctor(registry: &LanguageRegistry) -> Result<i32> {
491 println!(
492 "{:<12} {:<16} {:<24} Status",
493 "Language", "Toolchain", "Version"
494 );
495 println!("────────────────────────────────────────────────────────────");
496 let mut missing = 0;
497 let mut languages = registry.known_languages();
498 languages.sort();
499 for lang in languages {
500 let spec = LanguageSpec::new(lang.clone());
501 if let Some(engine) = registry.resolve(&spec) {
502 let toolchain = toolchain_name(engine.id());
503 match engine.validate() {
504 Ok(()) => {
505 let version = engine
506 .toolchain_version()
507 .ok()
508 .flatten()
509 .unwrap_or_else(|| "unknown".to_string());
510 let status = if version == "unknown" {
511 "⚠ Unknown"
512 } else {
513 "✓ OK"
514 };
515 println!(
516 "{:<12} {:<16} {:<24} {}",
517 engine.display_name(),
518 toolchain,
519 version.lines().next().unwrap_or("unknown"),
520 status
521 );
522 }
523 Err(_) => {
524 missing += 1;
525 println!(
526 "{:<12} {:<16} {:<24} ✗ MISSING",
527 engine.display_name(),
528 toolchain,
529 "✗ Not found"
530 );
531 }
532 }
533 }
534 }
535 Ok(if missing == 0 { 0 } else { 1 })
536}
537
538fn share_file(path: &Path, port: Option<u16>, registry: &LanguageRegistry) -> Result<i32> {
539 if !path.is_file() {
540 eprintln!("[run] File not found: {}", path.display());
541 return Ok(1);
542 }
543 let address = format!("127.0.0.1:{}", port.unwrap_or(0));
544 let server = tiny_http::Server::http(&address)
545 .map_err(|err| anyhow::anyhow!("failed to start share server: {err}"))?;
546 let url = format!("http://{}", server.server_addr());
547 println!("Sharing at {url} (Ctrl-C to stop)");
548
549 let lang = language_from_path(path).unwrap_or("text");
550 let spec = ExecutionSpec {
551 language: (lang != "text").then(|| LanguageSpec::new(lang.to_string())),
552 source: InputSource::File(path.to_path_buf()),
553 detect_language: true,
554 args: Vec::new(),
555 json: false,
556 };
557 let output = execute_capture(spec, registry).unwrap_or_default();
558 for request in server.incoming_requests() {
559 let route = request.url().to_string();
560 if route == "/raw" {
561 let text = fs::read_to_string(path).unwrap_or_default();
562 let _ = request.respond(tiny_http::Response::from_string(text));
563 continue;
564 }
565 let body = render_share_html(path, lang, &output);
566 let response = tiny_http::Response::from_string(body).with_header(
567 tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/html; charset=utf-8"[..])
568 .unwrap(),
569 );
570 let _ = request.respond(response);
571 }
572 Ok(0)
573}
574
575fn bench_run(spec: ExecutionSpec, registry: &LanguageRegistry, iterations: u32) -> Result<i32> {
576 let payload = ExecutionPayload::from_input_source(&spec.source, &spec.args)
577 .context("failed to materialize execution payload")?;
578 let language = resolve_language(
579 spec.language,
580 spec.detect_language,
581 Some(&payload),
582 registry,
583 )?;
584
585 let engine = registry
586 .resolve(&language)
587 .context("failed to resolve language engine")?;
588
589 engine
590 .validate()
591 .with_context(|| format!("{} is not available", engine.display_name()))?;
592
593 eprintln!(
594 "\x1b[1mBenchmark:\x1b[0m {} — {} iteration{}",
595 engine.display_name(),
596 iterations,
597 if iterations == 1 { "" } else { "s" }
598 );
599
600 let warmup = engine.execute(&payload)?;
602 if !warmup.success() {
603 eprintln!("\x1b[31mError:\x1b[0m Code failed during warmup run");
604 if !warmup.stderr.is_empty() {
605 eprint!("{}", warmup.stderr);
606 }
607 return Ok(1);
608 }
609 eprintln!("\x1b[2m warmup: {}ms\x1b[0m", warmup.duration.as_millis());
610
611 let mut times: Vec<f64> = Vec::with_capacity(iterations as usize);
612
613 for i in 0..iterations {
614 let outcome = engine.execute(&payload)?;
615 let ms = outcome.duration.as_secs_f64() * 1000.0;
616 times.push(ms);
617
618 if i < 3 || i == iterations - 1 || (i + 1) % 10 == 0 {
619 eprintln!("\x1b[2m run {}: {:.2}ms\x1b[0m", i + 1, ms);
620 }
621 }
622
623 times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
624 let total: f64 = times.iter().sum();
625 let avg = total / times.len() as f64;
626 let min = times.first().copied().unwrap_or(0.0);
627 let max = times.last().copied().unwrap_or(0.0);
628 let median = if times.len().is_multiple_of(2) && times.len() >= 2 {
629 (times[times.len() / 2 - 1] + times[times.len() / 2]) / 2.0
630 } else {
631 times[times.len() / 2]
632 };
633
634 let variance: f64 = times.iter().map(|t| (t - avg).powi(2)).sum::<f64>() / times.len() as f64;
636 let stddev = variance.sqrt();
637
638 eprintln!();
639 eprintln!("\x1b[1mResults ({} runs):\x1b[0m", iterations);
640 eprintln!(" min: \x1b[32m{:.2}ms\x1b[0m", min);
641 eprintln!(" max: \x1b[33m{:.2}ms\x1b[0m", max);
642 eprintln!(" avg: \x1b[36m{:.2}ms\x1b[0m", avg);
643 eprintln!(" median: \x1b[36m{:.2}ms\x1b[0m", median);
644 eprintln!(" stddev: {:.2}ms", stddev);
645
646 if !warmup.stdout.is_empty() {
647 print!("{}", warmup.stdout);
648 io::stdout().flush().ok();
649 }
650
651 Ok(0)
652}
653
654fn watch_run(spec: ExecutionSpec, registry: &LanguageRegistry) -> Result<i32> {
655 use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
656
657 let file_path = match &spec.source {
658 InputSource::File(p) => p.clone(),
659 _ => anyhow::bail!("--watch requires a file path (use -f or pass a file as argument)"),
660 };
661
662 if !file_path.exists() {
663 anyhow::bail!("File not found: {}", file_path.display());
664 }
665
666 let payload = ExecutionPayload::from_input_source(&spec.source, &spec.args)
667 .context("failed to materialize execution payload")?;
668 let language = resolve_language(
669 spec.language.clone(),
670 spec.detect_language,
671 Some(&payload),
672 registry,
673 )?;
674
675 let engine = registry
676 .resolve(&language)
677 .context("failed to resolve language engine")?;
678
679 engine
680 .validate()
681 .with_context(|| format!("{} is not available", engine.display_name()))?;
682
683 println!(
684 "[run watch] watching {} ({}) — Ctrl-C to stop",
685 file_path.display(),
686 engine.display_name()
687 );
688
689 let mut run_count = 0u32;
690
691 run_count += 1;
692 print!("\x1b[2J\x1b[H");
693 println!("[run watch] run #{run_count}");
694 run_file_once(&file_path, engine, &spec.args);
695
696 let (tx, rx) = mpsc::channel();
697 let mut watcher = RecommendedWatcher::new(
698 move |res| {
699 let _ = tx.send(res);
700 },
701 Config::default(),
702 )?;
703 watcher.watch(&file_path, RecursiveMode::NonRecursive)?;
704
705 loop {
706 match rx.recv() {
707 Ok(Ok(_event)) => {
708 while rx
709 .recv_timeout(std::time::Duration::from_millis(150))
710 .is_ok()
711 {}
712 run_count += 1;
713 print!("\x1b[2J\x1b[H");
714 let now = SystemTime::now()
715 .duration_since(SystemTime::UNIX_EPOCH)
716 .map(|duration| duration.as_secs())
717 .unwrap_or(0);
718 println!("[run watch] run #{run_count} at {now}");
719 run_file_once(&file_path, engine, &spec.args);
720 }
721 Ok(Err(err)) => eprintln!("[run] watch error: {err}"),
722 Err(err) => anyhow::bail!("[run] watch channel closed: {err}"),
723 }
724 }
725}
726
727fn run_file_once(file_path: &Path, engine: &dyn crate::engine::LanguageEngine, args: &[String]) {
728 let payload = ExecutionPayload::File {
729 path: file_path.to_path_buf(),
730 args: args.to_vec(),
731 };
732 match engine.execute(&payload) {
733 Ok(outcome) => {
734 if !outcome.stdout.is_empty() {
735 print!("{}", outcome.stdout);
736 io::stdout().flush().ok();
737 }
738 if !outcome.stderr.is_empty() {
739 eprint!("\x1b[31m{}\x1b[0m", outcome.stderr);
740 io::stderr().flush().ok();
741 }
742 let ms = outcome.duration.as_millis();
743 let status = if outcome.success() {
744 "\x1b[32mOK\x1b[0m"
745 } else {
746 "\x1b[31mFAIL\x1b[0m"
747 };
748 eprintln!("\x1b[2m[{status} {ms}ms]\x1b[0m");
749 }
750 Err(e) => {
751 eprintln!("\x1b[31mError:\x1b[0m {e:#}");
752 }
753 }
754}
755
756fn execute_capture(spec: ExecutionSpec, registry: &LanguageRegistry) -> Result<String> {
757 let payload = ExecutionPayload::from_input_source(&spec.source, &spec.args)
758 .context("failed to materialize execution payload")?;
759 let language = resolve_language(
760 spec.language,
761 spec.detect_language,
762 Some(&payload),
763 registry,
764 )?;
765 let engine = registry
766 .resolve(&language)
767 .context("failed to resolve language engine")?;
768 let outcome = engine.execute(&payload)?;
769 let mut output = outcome.stdout;
770 output.push_str(&outcome.stderr);
771 Ok(output)
772}
773
774fn render_share_html(path: &Path, language: &str, output: &str) -> String {
775 let code = fs::read_to_string(path).unwrap_or_default();
776 let syntax_set = syntect::parsing::SyntaxSet::load_defaults_newlines();
777 let theme_set = syntect::highlighting::ThemeSet::load_defaults();
778 let syntax = syntax_set
779 .find_syntax_by_extension(path.extension().and_then(|ext| ext.to_str()).unwrap_or(""))
780 .unwrap_or_else(|| syntax_set.find_syntax_plain_text());
781 let rendered_code = theme_set
782 .themes
783 .get("base16-ocean.dark")
784 .and_then(|theme| {
785 syntect::html::highlighted_html_for_string(&code, &syntax_set, syntax, theme).ok()
786 })
787 .unwrap_or_else(|| format!("<pre>{}</pre>", html_escape(&code)));
788 format!(
789 "<!doctype html><meta charset=\"utf-8\"><title>{}</title>\
790 <style>body{{font-family:system-ui;margin:2rem;background:#111;color:#eee}}pre{{padding:1rem;overflow:auto;background:#1b1b1b}}.out{{white-space:pre-wrap}}</style>\
791 <h1>{}</h1><p>Language: {}</p>{}<h2>Last output</h2><pre class=\"out\">{}</pre>",
792 html_escape(&path.display().to_string()),
793 html_escape(&path.display().to_string()),
794 html_escape(language),
795 rendered_code,
796 html_escape(output)
797 )
798}
799
800fn html_escape(text: &str) -> String {
801 text.replace('&', "&")
802 .replace('<', "<")
803 .replace('>', ">")
804 .replace('"', """)
805}
806
807fn language_from_path(path: &Path) -> Option<&'static str> {
808 let ext = path.extension()?.to_str()?;
809 crate::engine::language_from_extension(&ext.to_ascii_lowercase())
810}
811
812fn toolchain_name(language: &str) -> &'static str {
813 match language {
814 "python" => "python3",
815 "javascript" => "node",
816 "typescript" => "deno",
817 "rust" => "rustc",
818 "go" => "go",
819 "c" => "cc",
820 "cpp" => "c++",
821 "java" => "javac/java",
822 "kotlin" => "kotlinc",
823 "csharp" => "dotnet",
824 "bash" => "bash",
825 "ruby" => "ruby",
826 "lua" => "lua",
827 "php" => "php",
828 "r" => "Rscript",
829 "dart" => "dart",
830 "swift" => "swift",
831 "perl" => "perl",
832 "julia" => "julia",
833 "haskell" => "runghc",
834 "elixir" => "elixir",
835 "crystal" => "crystal",
836 "zig" => "zig",
837 "nim" => "nim",
838 "groovy" => "groovy",
839 _ => "unknown",
840 }
841}
842
843fn resolve_language(
844 explicit: Option<LanguageSpec>,
845 allow_detect: bool,
846 payload: Option<&ExecutionPayload>,
847 registry: &LanguageRegistry,
848) -> Result<LanguageSpec> {
849 if let Some(spec) = explicit {
850 ensure_known_language(&spec, registry)?;
851 return Ok(spec);
852 }
853
854 if allow_detect
855 && let Some(payload) = payload
856 && let Some(detected) = detect_language_for_source(payload, registry)
857 {
858 return Ok(detected);
859 }
860
861 let default = LanguageSpec::new(default_language());
862 ensure_known_language(&default, registry)?;
863 Ok(default)
864}