Skip to main content

o_/
lib.rs

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}