1pub mod app_error;
2pub mod args;
3pub mod binengine;
4pub mod conf;
5pub mod lock;
6pub mod pm;
7pub mod report;
8pub mod toolchain;
9pub mod x;
10
11pub use app_error::AppError;
12use args::Commands;
13use home::home_dir;
14pub use o_core::engine;
15use o_core::engine::JSEngine;
16pub use o_core::error;
17#[cfg(target_os = "macos")]
18use o_toolchain_javascriptcore::JavaScriptCore;
19use o_toolchain_spidermonkey::SpiderMonkey;
20use o_toolchain_v8::V8Engine;
21use std::fs;
22use std::path::PathBuf;
23
24use crate::args::ToolChainCommand;
25use crate::binengine::BinEngine;
26use crate::pm::global_install;
27use crate::report::Report;
28
29pub fn process(args: Commands, toolchain: &str) -> Result<(), AppError> {
30 match args {
31 Commands::Run { path } => {
32 let selected_toolchain = select_toolchain(toolchain)?;
33 run(&path, selected_toolchain);
34 Ok(())
35 }
36 Commands::Toolchain { command } => {
37 let report = run_toolchain(command)?;
38 report::print(&report);
39 Ok(())
40 }
41 Commands::Install { global, package } => {
42 if global {
43 let report =
44 global_install(package.as_deref()).map_err(AppError::PackageManager)?;
45 report::print(&report);
46 Ok(())
47 } else {
48 let report = pm::install().map_err(AppError::PackageManager)?;
49 report::print(&report);
50 Ok(())
51 }
52 }
53 Commands::Uninstall { name } => {
54 let report = pm::uninstall(&name).map_err(AppError::PackageManager)?;
55 report::print(&report);
56 Ok(())
57 }
58 }
59}
60
61fn run_toolchain(command: ToolChainCommand) -> Result<Report, AppError> {
62 match command {
63 ToolChainCommand::Add { user, repo } => {
64 let mut installed =
65 toolchain::install("github.com", &user, &repo).map_err(|source| {
66 AppError::InstallToolchain {
67 user: user.clone(),
68 repo: repo.clone(),
69 source: source.to_string(),
70 }
71 })?;
72 installed.push("bin");
73 installed.push(&repo);
74 let mut target = home_dir().ok_or(AppError::HomeDirUnavailable)?;
75 target.push(".config");
76 target.push("o-");
77 target.push("toolchains");
78 target.push("bin");
79 target.push(&repo);
80 if let Some(parent) = target.parent() {
81 fs::create_dir_all(parent).map_err(|source| AppError::CreateDir {
82 path: parent.to_path_buf(),
83 source,
84 })?;
85 }
86 fs::rename(&installed, &target).map_err(|source| AppError::MoveToolchain {
87 from: installed.clone(),
88 to: target.clone(),
89 source,
90 })?;
91
92 Ok(Report::new(format!("installed toolchain `{repo}`"))
93 .detail(format!("source: {user}/{repo}"))
94 .detail(format!("binary: {}", target.display())))
95 }
96 ToolChainCommand::Remove { toolchain } => {
97 let mut target = home::home_dir().ok_or(AppError::HomeDirUnavailable)?;
98 target.push(".config");
99 target.push("o-");
100 target.push("toolchains");
101 target.push("bin");
102 target.push(&toolchain);
103 fs::remove_file(&target).map_err(|source| AppError::RemoveToolchain {
104 path: target.clone(),
105 source,
106 })?;
107
108 Ok(Report::new(format!("removed toolchain `{toolchain}`"))
109 .detail(format!("path: {}", target.display())))
110 }
111 }
112}
113
114fn resolve_toolchain(name: &str) -> Result<String, AppError> {
115 let mut toolchain: PathBuf = home::home_dir().ok_or(AppError::HomeDirUnavailable)?;
116 toolchain.push(".config");
117 toolchain.push("o-");
118 toolchain.push("toolchains");
119 toolchain.push(name);
120 Ok(toolchain.to_string_lossy().into_owned())
121}
122
123fn select_toolchain(toolchain: &str) -> Result<Box<dyn JSEngine>, AppError> {
124 let engine: Box<dyn JSEngine> = match toolchain.trim() {
125 #[cfg(target_os = "macos")]
126 "javascriptcore" | "jsc" => Box::new(JavaScriptCore::new()),
127 #[cfg(not(target_os = "macos"))]
128 "javascriptcore" | "jsc" => {
129 return Err(AppError::UnsupportedToolchain {
130 toolchain: "javascriptcore".to_string(),
131 detail: "the published Linux build excludes JavaScriptCore to avoid linker conflicts with V8",
132 });
133 }
134 "v8" => Box::new(V8Engine::new()),
135 "spidermonkey" | "" => Box::new(SpiderMonkey::new()),
136 other => Box::new(BinEngine::new(resolve_toolchain(other)?)),
137 };
138 Ok(engine)
139}
140
141fn run(path: &str, toolchain: Box<dyn JSEngine>) {
142 let file = match fs::read_to_string(path) {
143 Ok(file) => file,
144 Err(source) => {
145 let error = AppError::ReadScript {
146 path: PathBuf::from(path),
147 source,
148 };
149 report::print_error(&error.report());
150 return;
151 }
152 };
153
154 if let Err(err) = toolchain.run(&file, path) {
155 eprintln!("{err}");
156 }
157}