1#![recursion_limit = "2048"]
3
4use mlua::prelude::*;
5
6#[cfg(not(feature = "static"))]
7use mlua::chunk;
8use std::env;
9use std::path::PathBuf;
10
11#[cfg(feature = "cli")]
12pub mod cli;
13
14#[cfg(feature = "static")]
15pub mod embed;
16
17pub mod types;
18
19pub type Result<T> = anyhow::Result<T>;
20
21pub fn start_luavm() -> crate::Result<Lua> {
22 let mut lua = unsafe { Lua::unsafe_new() };
23 #[cfg(feature = "static")]
24 {
25 lua = embed::inject_embedded_loaders(lua)?;
26 }
27 {
29 let rt = std::env::args()
30 .next()
31 .unwrap_or_else(|| "sile".to_string());
32 let args: Vec<String> = std::env::args().skip(1).collect();
33 let arg_table = lua.create_table()?;
34 for (i, arg) in args.iter().enumerate() {
35 arg_table.set(i + 1, arg.clone())?;
36 }
37 arg_table.set(0, rt)?;
42 lua.globals().set("arg", arg_table)?;
43 }
44 lua = inject_paths(lua)?;
45 lua = load_sile(lua)?;
46 lua = inject_version(lua)?;
47 Ok(lua)
48}
49
50pub fn inject_paths(lua: Lua) -> crate::Result<Lua> {
51 #[cfg(feature = "static")]
56 lua.load(r#"require("core.pathsetup")"#)
57 .set_name("=[C]")
58 .exec()?;
59 #[cfg(not(feature = "static"))]
60 {
61 let datadir = env!("CONFIGURE_DATADIR").to_string();
62 let sile_path = match env::var("SILE_PATH") {
63 Ok(val) => format!("{datadir};{val}"),
64 Err(_) => datadir,
65 };
66 let sile_path: LuaString = lua.create_string(&sile_path)?;
67 lua.load(chunk! {
68 local status
69 for path in string.gmatch($sile_path, "[^;]+") do
70 status = pcall(dofile, path .. "/core/pathsetup.lua")
71 if status then break end
72 end
73 if not status then
74 dofile("./core/pathsetup.lua")
75 end
76 })
77 .set_name("=[C]")
78 .exec()?;
79 }
80 Ok(lua)
81}
82
83pub fn get_rusile_exports(lua: &Lua) -> LuaResult<LuaTable> {
84 let exports = lua.create_table()?;
85 exports.set("semver", LuaFunction::wrap_raw(types::semver::semver))?;
86 exports.set("setenv", LuaFunction::wrap_raw(setenv))?;
87 Ok(exports)
88}
89
90fn setenv(key: String, value: String) {
91 env::set_var(key, value);
92}
93
94pub fn inject_version(lua: Lua) -> crate::Result<Lua> {
95 let sile: LuaTable = lua.globals().get("SILE")?;
96 let mut full_version: String = sile.get("full_version")?;
97 full_version.push_str(" [Rust]");
98 sile.set("full_version", full_version)?;
99 Ok(lua)
100}
101
102pub fn load_sile(lua: Lua) -> crate::Result<Lua> {
103 let entry: LuaString = lua.create_string("core.sile")?;
104 let require: LuaFunction = lua.globals().get("require")?;
105 require.call::<LuaTable>(entry)?;
106 Ok(lua)
107}
108
109pub fn version() -> crate::Result<String> {
110 let lua = start_luavm()?;
111 let sile: LuaTable = lua.globals().get("SILE")?;
112 let full_version: String = sile.get("full_version")?;
113 Ok(full_version)
114}
115
116#[allow(clippy::too_many_arguments)]
120pub fn run(
121 inputs: Option<Vec<PathBuf>>,
122 backend: Option<String>,
123 class: Option<String>,
124 debugs: Option<Vec<String>>,
125 evaluates: Option<Vec<String>>,
126 evaluate_afters: Option<Vec<String>>,
127 fontmanager: Option<String>,
128 luarocks_tree: Option<Vec<PathBuf>>,
129 makedeps: Option<PathBuf>,
130 output: Option<PathBuf>,
131 options: Option<Vec<String>>,
132 preambles: Option<Vec<PathBuf>>,
133 postambles: Option<Vec<PathBuf>>,
134 uses: Option<Vec<String>>,
135 quiet: bool,
136 traceback: bool,
137) -> crate::Result<()> {
138 let lua = start_luavm()?;
139 let sile: LuaTable = lua.globals().get("SILE")?;
140 sile.set("traceback", traceback)?;
141 sile.set("quiet", quiet)?;
142 let mut has_input_filename = false;
143 if let Some(flags) = debugs {
144 let debug_flags: LuaTable = sile.get("debugFlags")?;
145 for flag in flags {
146 debug_flags.set(flag, true)?;
147 }
148 }
149 let full_version: String = sile.get("full_version")?;
150 let sile_input: LuaTable = sile.get("input")?;
151 if let Some(expressions) = evaluates {
152 sile_input.set("evaluates", expressions)?;
153 }
154 if let Some(expressions) = evaluate_afters {
155 sile_input.set("evaluateAfters", expressions)?;
156 }
157 if let Some(backend) = backend {
158 sile_input.set("backend", backend)?;
159 }
160 if let Some(fontmanager) = fontmanager {
161 sile_input.set("fontmanager", fontmanager)?;
162 }
163 if let Some(trees) = luarocks_tree {
164 sile_input.set("luarocksTrees", trees)?;
165 }
166 if let Some(class) = class {
167 sile_input.set("class", class)?;
168 }
169 if let Some(paths) = preambles {
170 sile_input.set("preambles", paths_to_strings(paths))?;
171 }
172 if let Some(paths) = postambles {
173 sile_input.set("postambles", paths_to_strings(paths))?;
174 }
175 if let Some(path) = makedeps {
176 sile_input.set("makedeps", path_to_string(&path))?;
177 }
178 if let Some(path) = output {
179 sile.set("outputFilename", path_to_string(&path))?;
180 has_input_filename = true;
181 }
182 if let Some(options) = options {
183 let parameters: LuaAnyUserData = sile.get::<LuaTable>("parserBits")?.get("parameters")?;
184 let input_options: LuaTable = sile_input.get("options")?;
185 for option in options.iter() {
186 let parameters: LuaTable = parameters
187 .call_method("match", lua.create_string(option)?)
188 .context("failed to call `parameters:match()`")?;
189 for parameter in parameters.pairs::<LuaValue, LuaValue>() {
190 let (key, value) = parameter?;
191 let _ = input_options.set(key, value);
192 }
193 }
194 }
195 if let Some(modules) = uses {
196 let cliuse: LuaAnyUserData = sile.get::<LuaTable>("parserBits")?.get("cliuse")?;
197 let input_uses: LuaTable = sile_input.get("uses")?;
198 for module in modules.iter() {
199 let module = lua.create_string(module)?;
200 let spec: LuaTable = cliuse
201 .call_method::<_>("match", module)
202 .context("failed to call `cliuse:match()`")?;
203 let _ = input_uses.push(spec);
204 }
205 }
206 if !quiet {
207 eprintln!("{full_version}");
208 }
209 let init: LuaFunction = sile.get("init")?;
210 init.call::<LuaValue>(())?;
211 if let Some(inputs) = inputs {
212 let input_filenames: LuaTable = lua.create_table()?;
213 for input in inputs.iter() {
214 let path = &path_to_string(input);
215 if !has_input_filename && path != "-" {
216 has_input_filename = true;
217 }
218 input_filenames.push(lua.create_string(path)?)?;
219 }
220 if !has_input_filename {
221 panic!(
222 "\nUnable to derive an output filename (perhaps because input is a STDIO stream)\nPlease use --output to set one explicitly."
223 );
224 }
225 sile_input.set("filenames", input_filenames)?;
226 let input_uses: LuaTable = sile_input.get("uses")?;
227 let r#use: LuaFunction = sile.get("use")?;
228 for spec in input_uses.sequence_values::<LuaTable>() {
229 let spec = spec?;
230 let module: LuaString = spec.get("module")?;
231 let options: LuaTable = spec.get("options")?;
232 r#use.call::<LuaValue>((module, options))?;
233 }
234 let input_filenames: LuaTable = sile_input.get("filenames")?;
235 let process_file: LuaFunction = sile.get("processFile")?;
236 for file in input_filenames.sequence_values::<LuaString>() {
237 process_file.call::<LuaValue>(file?)?;
238 }
239 let finish: LuaFunction = sile.get("finish")?;
240 finish.call::<LuaValue>(())?;
241 } else {
242 let repl_module: LuaString = lua.create_string("core.repl")?;
243 let require: LuaFunction = lua.globals().get("require")?;
244 let repl: LuaTable = require.call::<LuaTable>(repl_module)?;
245 repl.call_method::<LuaValue>("enter", ())?;
246 }
247 Ok(())
248}
249
250fn path_to_string(path: &PathBuf) -> String {
251 path.clone().into_os_string().into_string().unwrap()
252}
253
254fn paths_to_strings(paths: Vec<PathBuf>) -> Vec<String> {
255 paths.iter().map(path_to_string).collect()
256}