use mlua::prelude::*;
use std::ops::Range;
use crate::Sort;
use crate::block;
use crate::category;
use crate::corpus::{
self, FIELD_GLYPH, Idx, category_of, codepoint, entry_category, entry_icon_set, entry_name,
entry_source, entry_str, icon_set_description, list_icon_sets, list_sources, lookup_name,
num_entries,
};
use crate::frecency::Frecency;
use crate::search;
fn entry_to_table(lua: &Lua, m: &search::Match) -> LuaResult<LuaTable> {
let t = lua.create_table()?;
t.set("codepoint", codepoint(m.idx))?;
t.set("glyph", entry_str(m.idx, FIELD_GLYPH))?;
t.set("name", entry_name(m.idx))?;
t.set("source", entry_source(m.idx))?;
t.set("category", entry_category(m.idx))?;
t.set("block", corpus::entry_block(m.idx))?;
t.set("icon_set", corpus::entry_icon_set(m.idx))?;
t.set("score", m.score)?;
t.set("freq", m.freq)?;
Ok(t)
}
fn split_csv(s: &str) -> Vec<&str> {
s.split(',')
.map(|p| p.trim())
.filter(|p| !p.is_empty())
.collect()
}
fn do_search(lua: &Lua, (query, opts): (String, LuaTable)) -> LuaResult<LuaTable> {
let limit: usize = opts.get("limit").unwrap_or(50);
let max_typos: Option<u16> = opts.get("max_typos").unwrap_or_default();
let sort_str: String = opts.get("sort").unwrap_or("relevance".to_string());
let sort = match sort_str.as_str() {
"relevance" | "score" => Sort::Relevance,
"name" => Sort::Name,
"codepoint" => Sort::Codepoint,
_ => Sort::Relevance,
};
let block_names: Option<String> = opts.get("block").ok();
let cat_pats: Option<String> = opts.get("category").ok();
let raw_range: Option<(u32, u32)> = opts
.get::<LuaTable>("range")
.ok()
.and_then(|t| Some((t.get(1).ok()?, t.get(2).ok()?)));
let sources: Option<String> = opts.get("source").ok();
let icon_sets: Option<String> = opts.get("icon_set").ok();
let has_filter = block_names.is_some()
|| cat_pats.is_some()
|| raw_range.is_some()
|| sources.is_some()
|| icon_sets.is_some();
let frecency = Frecency::load();
let results = if has_filter {
let mut ranges: Vec<Range<u32>> = Vec::new();
if let Some(ref names) = block_names {
for name in split_csv(names) {
let b = block::by_name(name.trim())
.ok_or_else(|| LuaError::external(format!("unknown block '{name}'")))?;
ranges.push(b.range.clone());
}
}
if let Some((start, end)) = raw_range {
ranges.push(start..end + 1);
}
let mut cats: Vec<&str> = Vec::new();
if let Some(ref pats) = cat_pats {
for pat in split_csv(pats) {
let resolved = category::resolve(pat.trim()).map_err(LuaError::external)?;
cats.extend(resolved);
}
}
let source_list: Vec<&str> = sources.as_ref().map(|s| split_csv(s)).unwrap_or_default();
let icon_set_list: Vec<&str> = icon_sets.as_ref().map(|s| split_csv(s)).unwrap_or_default();
let base: Box<dyn Iterator<Item = Idx>> = if ranges.is_empty() {
Box::new((0..num_entries()).map(|i| Idx(i as u32)))
} else {
Box::new(
ranges
.iter()
.flat_map(|r| block::entry_range(r).map(|i| Idx(i as u32))),
)
};
let pool: Vec<Idx> = base
.filter(|&idx| {
(cats.is_empty() || cats.contains(&entry_category(idx)))
&& (source_list.is_empty() || source_list.contains(&entry_source(idx)))
&& (icon_set_list.is_empty() || icon_set_list.contains(&entry_icon_set(idx)))
})
.collect();
if pool.is_empty() {
return lua.create_table();
}
let names: Vec<&str> = pool.iter().map(|&idx| entry_name(idx)).collect();
search::search_in(&query, &pool, &names, &frecency, limit, max_typos, sort)
} else {
search::search_all(&query, &frecency, limit, max_typos, sort)
};
let out = lua.create_table()?;
for (i, m) in results.iter().enumerate() {
out.set(i + 1, entry_to_table(lua, m)?)?;
}
Ok(out)
}
fn lookup(lua: &Lua, query: String) -> LuaResult<Option<LuaTable>> {
let frecency = Frecency::load();
match corpus::lookup_str(&query) {
Some(idx) => {
let freq = u32::min(frecency.get(codepoint(idx)), u16::MAX as u32) as u16;
let m = search::Match {
idx,
score: 0,
freq,
};
Ok(Some(entry_to_table(lua, &m)?))
}
None => Ok(None),
}
}
fn record(_lua: &Lua, codepoint: u32) -> LuaResult<()> {
let mut frecency = Frecency::load();
frecency.record(codepoint);
frecency.flush().map_err(LuaError::external)
}
fn frecency_get(_lua: &Lua, codepoint: u32) -> LuaResult<u32> {
let frecency = Frecency::load();
Ok(frecency.get(codepoint))
}
fn frecency_path(_lua: &Lua, _: ()) -> LuaResult<String> {
let path = Frecency::path();
Ok(path.to_string_lossy().to_string())
}
fn blocks(_lua: &Lua, _: ()) -> LuaResult<LuaTable> {
let t = _lua.create_table()?;
for (i, b) in block::all().iter().enumerate() {
let btab = _lua.create_table()?;
btab.set("name", b.name)?;
btab.set("start", b.range.start)?;
btab.set("end", b.range.end - 1)?;
t.set(i + 1, btab)?;
}
Ok(t)
}
fn block_of(_lua: &Lua, cp: u32) -> LuaResult<Option<&'static str>> {
Ok(block::block_of(cp))
}
fn categories(_lua: &Lua, _: ()) -> LuaResult<LuaTable> {
let t = _lua.create_table()?;
for (i, c) in category::list().iter().enumerate() {
let ctab = _lua.create_table()?;
ctab.set("code", c.code)?;
ctab.set("desc", c.desc)?;
t.set(i + 1, ctab)?;
}
Ok(t)
}
fn sources(_lua: &Lua, _: ()) -> LuaResult<LuaTable> {
let t = _lua.create_table()?;
for (i, s) in list_sources().iter().enumerate() {
t.set(i + 1, *s)?;
}
Ok(t)
}
fn icon_sets(_lua: &Lua, _: ()) -> LuaResult<LuaTable> {
let t = _lua.create_table()?;
for (i, s) in list_icon_sets().iter().enumerate() {
let stab = _lua.create_table()?;
let code: &str = s;
stab.set("code", code)?;
stab.set("desc", icon_set_description(s))?;
t.set(i + 1, stab)?;
}
Ok(t)
}
fn name_lookup(_lua: &Lua, cp: u32) -> LuaResult<Option<&'static str>> {
Ok(lookup_name(cp))
}
fn category_lookup(_lua: &Lua, cp: u32) -> LuaResult<Option<&'static str>> {
Ok(category_of(cp))
}
#[mlua::lua_module]
fn glyf_core(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("search", lua.create_function(do_search)?)?;
exports.set("lookup", lua.create_function(lookup)?)?;
exports.set("record", lua.create_function(record)?)?;
exports.set("frecency_get", lua.create_function(frecency_get)?)?;
exports.set("frecency_path", lua.create_function(frecency_path)?)?;
exports.set("blocks", lua.create_function(blocks)?)?;
exports.set("block_of", lua.create_function(block_of)?)?;
exports.set("categories", lua.create_function(categories)?)?;
exports.set("sources", lua.create_function(sources)?)?;
exports.set("icon_sets", lua.create_function(icon_sets)?)?;
exports.set("lookup_name", lua.create_function(name_lookup)?)?;
exports.set("category_of", lua.create_function(category_lookup)?)?;
Ok(exports)
}