use mlua::{Lua, MultiValue, Table, Value};
use retex_kpath::{Access, FileFormat};
use crate::with_engine;
pub fn format_by_name(s: &str) -> Option<FileFormat> {
Some(match s {
"tex" => FileFormat::Tex,
"other text files" => FileFormat::Text,
"lua" => FileFormat::Lua,
"opentype fonts" => FileFormat::OpenType,
"truetype fonts" => FileFormat::TrueType,
"type1 fonts" => FileFormat::Type1,
"tfm" => FileFormat::Tfm,
"map" => FileFormat::Map,
"enc files" | "enc" => FileFormat::Enc,
"vf" => FileFormat::Vf,
"fmt" => FileFormat::Fmt,
"bib" => FileFormat::Bib,
"bst" => FileFormat::Bst,
"graphic/figure" => FileFormat::Pict,
_ => return None,
})
}
fn find(lua: &Lua, name: &str, fmt: FileFormat) -> mlua::Result<Option<String>> {
with_engine(lua, |e| {
let in_output = e
.files
.output_dir
.as_ref()
.filter(|_| std::path::Path::new(name).is_relative())
.map(|d| d.join(name))
.filter(|p| p.is_file());
let found = in_output.or_else(|| {
e.kpse
.as_ref()
.and_then(|k| k.borrow_mut().find_file(name, fmt))
});
e.record_probe(name, found.as_deref());
found.map(|p| p.to_string_lossy().into_owned())
})
}
const CHECKED_IO: &str = r#"
local find, open, lines = string.find, io.open, io.lines
local out_ok, in_ok = kpse.out_name_ok_silent_extended, kpse.in_name_ok_silent_extended
function io.open(name, mode)
if type(name) == "string" then
if type(mode) == "string" and find(mode, "[wa+]") then
if not out_ok(name) then return nil end
elseif not in_ok(name) then
return nil
end
end
local f = open(name, mode)
if f then return f end
return nil
end
function io.lines(name, ...)
if type(name) == "string" and not in_ok(name) then error() end
return lines(name, ...)
end
"#;
pub fn check_io_names(lua: &Lua) -> mlua::Result<()> {
lua.load(CHECKED_IO).set_name("=[retex io checks]").exec()
}
pub fn install(lua: &Lua) -> mlua::Result<()> {
let t = lua.create_table()?;
t.set(
"find_file",
lua.create_function(|lua, args: MultiValue| {
let mut it = args.into_iter();
let name = match it.next() {
Some(Value::String(s)) => s.to_str()?.to_owned(),
_ => return Ok(Value::Nil),
};
let fmt = match it.next() {
Some(Value::String(s)) => format_by_name(&s.to_str()?).unwrap_or(FileFormat::Tex),
_ => FileFormat::Tex,
};
match find(lua, &name, fmt)? {
Some(p) => Ok(Value::String(lua.create_string(&p)?)),
None => {
with_engine(lua, |e| e.record_absent_file(&name))?;
Ok(Value::Nil)
}
}
})?,
)?;
t.set(
"var_value",
lua.create_function(|lua, name: String| {
with_engine(lua, |e| {
e.kpse.as_ref().and_then(|k| k.borrow().var_value(&name))
})
})?,
)?;
t.set(
"expand_var",
lua.create_function(|lua, s: String| {
let name = s.trim_start_matches('$').to_owned();
with_engine(lua, |e| {
e.kpse
.as_ref()
.and_then(|k| k.borrow().var_value(&name))
.unwrap_or_default()
})
})?,
)?;
t.set(
"show_path",
lua.create_function(|lua, fmt: String| {
let f = format_by_name(&fmt).unwrap_or(FileFormat::Tex);
with_engine(lua, |e| {
e.kpse
.as_ref()
.map(|k| k.borrow_mut().search_path(f).join(":"))
.unwrap_or_default()
})
})?,
)?;
for (field, access, extended) in [
("in_name_ok", Access::Read, false),
("out_name_ok", Access::Write, false),
("in_name_ok_silent_extended", Access::Read, true),
("out_name_ok_silent_extended", Access::Write, true),
] {
t.set(
field,
lua.create_function(move |lua, name: String| {
with_engine(lua, |e| {
if extended {
e.kpse
.as_ref()
.is_none_or(|k| k.borrow().name_ok(&name, access, true).is_ok())
} else {
e.name_ok(&name, access)
}
})
})?,
)?;
}
t.set(
"set_program_name",
lua.create_function(|_, _a: MultiValue| Ok(()))?,
)?;
t.set(
"version",
lua.create_function(|_, ()| Ok("kpathsea version 6.4.1"))?,
)?;
t.set(
"new",
lua.create_function(|lua, _a: MultiValue| lua.globals().get::<Table>("kpse"))?,
)?;
lua.globals().set("kpse", t)?;
let package: Table = lua.globals().get("package")?;
let searchers: Table = package.get("searchers")?;
let searcher = lua.create_function(|lua, name: String| {
let file = name.replace('.', "/");
let found = find(lua, &file, FileFormat::Lua)?.or(find(lua, &name, FileFormat::Lua)?);
match found {
Some(path) => {
let mut code = std::fs::read(&path).map_err(mlua::Error::external)?;
if code.first() == Some(&b'#') {
let end = code.iter().position(|&b| b == b'\n').unwrap_or(code.len());
code.drain(..end);
}
let chunk_name = format!("@{path}");
let f = lua.load(code).set_name(chunk_name).into_function()?;
Ok(MultiValue::from_iter([
Value::Function(f),
Value::String(lua.create_string(&path)?),
]))
}
None => Ok(MultiValue::from_iter([Value::String(lua.create_string(
format!("\n\t[kpse lua searcher] file not found: '{name}'"),
)?)])),
}
})?;
let n = searchers.raw_len();
for i in (2..=n).rev() {
let v: Value = searchers.raw_get(i)?;
searchers.raw_set(i + 1, v)?;
}
searchers.raw_set(2, searcher)?;
Ok(())
}