use crate::app::VERSION;
use crate::config::NodeTypesConfig;
use crate::explorer;
use crate::lua;
use crate::msg::in_::external::ExplorerConfig;
use crate::node::Node;
use crate::path;
use crate::path::RelativityConfig;
use crate::permissions::Octal;
use crate::permissions::Permissions;
use crate::ui;
use crate::ui::Layout;
use crate::ui::Style;
use crate::ui::WrapOptions;
use anyhow::Result;
use lazy_static::lazy_static;
use lscolors::LsColors;
use mlua::Error as LuaError;
use mlua::Lua;
use mlua::LuaSerdeExt;
use mlua::Table;
use mlua::Value;
use path_absolutize::*;
use serde::de::Error;
use serde::{Deserialize, Serialize};
use serde_json as json;
use serde_yaml as yaml;
use std::borrow::Cow;
use std::path::PathBuf;
use std::process::Command;
lazy_static! {
static ref LS_COLORS: LsColors = LsColors::from_env().unwrap_or_default();
}
pub fn version(util: Table, lua: &Lua) -> Result<Table> {
#[derive(Debug, Default, Serialize, Deserialize)]
struct Version {
major: u16,
minor: u16,
patch: u16,
}
let func = lua.create_function(|lua, ()| {
let (major, minor, patch, _) =
lua::parse_version(VERSION).map_err(LuaError::custom)?;
let version = Version {
major,
minor,
patch,
};
let res = lua::serialize(lua, &version).map_err(LuaError::custom)?;
Ok(res)
})?;
util.set("version", func)?;
Ok(util)
}
pub fn debug(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|_, value: Value| {
let log = format!("{value:#?}");
println!("{log}");
Ok(log)
})?;
util.set("debug", func)?;
Ok(util)
}
pub fn clone(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(move |lua, value: Value| {
lua::serialize(lua, &value).map_err(LuaError::custom)
})?;
util.set("clone", func)?;
Ok(util)
}
pub fn exists(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).exists()))?;
util.set("exists", func)?;
Ok(util)
}
pub fn is_dir(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_dir()))?;
util.set("is_dir", func)?;
Ok(util)
}
pub fn is_file(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?;
util.set("is_file", func)?;
Ok(util)
}
pub fn is_symlink(util: Table, lua: &Lua) -> Result<Table> {
let func = lua
.create_function(move |_, path: String| Ok(PathBuf::from(path).is_symlink()))?;
util.set("is_symlink", func)?;
Ok(util)
}
pub fn is_absolute(util: Table, lua: &Lua) -> Result<Table> {
let func = lua
.create_function(move |_, path: String| Ok(PathBuf::from(path).is_absolute()))?;
util.set("is_absolute", func)?;
Ok(util)
}
pub fn path_split(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(move |_, path: String| {
let components: Vec<String> = PathBuf::from(path)
.components()
.map(|c| c.as_os_str().to_string_lossy().to_string())
.collect();
Ok(components)
})?;
util.set("path_split", func)?;
Ok(util)
}
pub fn node(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(move |lua, path: String| {
let path = PathBuf::from(path);
let abs = path.absolutize()?;
match (abs.parent(), abs.file_name()) {
(Some(parent), Some(name)) => {
let node = Node::new(
parent.to_string_lossy().to_string(),
name.to_string_lossy().to_string(),
);
Ok(lua::serialize(lua, &node).map_err(LuaError::custom)?)
}
(_, _) => Ok(Value::Nil),
}
})?;
util.set("node", func)?;
Ok(util)
}
pub fn node_type(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(move |lua, (node, config): (Table, Option<Table>)| {
let node: Node = lua.from_value(Value::Table(node))?;
let config: Table = if let Some(config) = config {
config
} else {
lua.globals()
.get::<Table>("xplr")?
.get::<Table>("config")?
.get::<Table>("node_types")?
};
let config: NodeTypesConfig = lua.from_value(Value::Table(config))?;
let node_type = config.get(&node);
let node_type = lua::serialize(lua, &node_type).map_err(LuaError::custom)?;
Ok(node_type)
})?;
util.set("node_type", func)?;
Ok(util)
}
pub fn dirname(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|_, path: String| {
let parent = PathBuf::from(path)
.parent()
.map(|p| p.to_string_lossy().to_string());
Ok(parent)
})?;
util.set("dirname", func)?;
Ok(util)
}
pub fn basename(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|_, path: String| {
let parent = PathBuf::from(path)
.file_name()
.map(|p| p.to_string_lossy().to_string());
Ok(parent)
})?;
util.set("basename", func)?;
Ok(util)
}
pub fn absolute(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|_, path: String| {
let abs = PathBuf::from(path)
.absolutize()?
.to_string_lossy()
.to_string();
Ok(abs)
})?;
util.set("absolute", func)?;
Ok(util)
}
pub fn relative_to(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, (path, config): (String, Option<Table>)| {
let config: Option<RelativityConfig<String>> =
lua.from_value(config.map(Value::Table).unwrap_or(Value::Nil))?;
path::relative_to(path, config.as_ref())
.map(|p| p.to_string_lossy().to_string())
.map_err(LuaError::custom)
})?;
util.set("relative_to", func)?;
Ok(util)
}
pub fn shorten(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(move |lua, (path, config): (String, Option<Table>)| {
let config: Option<RelativityConfig<String>> =
lua.from_value(config.map(Value::Table).unwrap_or(Value::Nil))?;
path::shorten(path, config.as_ref()).map_err(LuaError::custom)
})?;
util.set("shorten", func)?;
Ok(util)
}
pub fn explore(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, (path, config): (String, Option<Table>)| {
let config: ExplorerConfig = if let Some(cfg) = config {
lua.from_value(Value::Table(cfg))?
} else {
ExplorerConfig::default()
};
let nodes = explorer::explore(&PathBuf::from(path), &config)
.map_err(LuaError::custom)?;
let res = lua::serialize(lua, &nodes).map_err(LuaError::custom)?;
Ok(res)
})?;
util.set("explore", func)?;
Ok(util)
}
pub fn shell_execute(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(|lua, (program, args): (String, Option<Vec<String>>)| {
let mut cmd = Command::new(program);
let mut cmd_ref = &mut cmd;
if let Some(args) = args {
cmd_ref = cmd_ref.args(args)
};
let output = cmd_ref.output()?;
let res = lua.create_table()?;
res.set("stdout", String::from_utf8_lossy(&output.stdout))?;
res.set("stderr", String::from_utf8_lossy(&output.stderr))?;
res.set("returncode", output.status.code())?;
Ok(res)
})?;
util.set("shell_execute", func)?;
Ok(util)
}
pub fn shell_quote(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|_, string: String| {
Ok(format!("'{}'", string.replace('\'', r#"'"'"'"#)))
})?;
util.set("shell_quote", func)?;
Ok(util)
}
pub fn shell_escape(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(move |_, string: String| {
let val = path::escape(&string).to_string();
Ok(val)
})?;
util.set("shell_escape", func)?;
Ok(util)
}
pub fn from_json(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, string: String| {
let val = json::from_str::<yaml::Value>(&string).map_err(LuaError::custom)?;
lua::serialize(lua, &val).map_err(Error::custom)
})?;
util.set("from_json", func)?;
Ok(util)
}
pub fn to_json(util: Table, lua: &Lua) -> Result<Table> {
#[derive(Debug, Default, Serialize, Deserialize)]
struct Options {
pretty: bool,
}
let func =
lua.create_function(|lua, (value, options): (Value, Option<Table>)| {
let options: Options = if let Some(o) = options {
lua.from_value(Value::Table(o))?
} else {
Default::default()
};
if options.pretty {
json::to_string_pretty(&value).map_err(Error::custom)
} else {
json::to_string(&value).map_err(Error::custom)
}
})?;
util.set("to_json", func)?;
Ok(util)
}
pub fn from_yaml(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, string: String| {
let val = yaml::from_str::<yaml::Value>(&string).map_err(LuaError::custom)?;
lua::serialize(lua, &val).map_err(Error::custom)
})?;
util.set("from_yaml", func)?;
Ok(util)
}
pub fn to_yaml(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|_, value: Value| {
yaml::to_string(&value).map_err(Error::custom)
})?;
util.set("to_yaml", func)?;
Ok(util)
}
pub fn lscolor(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(move |lua, path: String| {
let style = LS_COLORS
.style_for_path(path)
.map(Style::from)
.unwrap_or_default();
lua::serialize(lua, &style).map_err(LuaError::custom)
})?;
util.set("lscolor", func)?;
Ok(util)
}
pub fn paint(util: Table, lua: &Lua) -> Result<Table> {
let func =
lua.create_function(|lua, (string, style): (String, Option<Table>)| {
if *ui::NO_COLOR {
return Ok(string);
}
if let Some(style) = style {
let style: Style = lua.from_value(Value::Table(style))?;
let ansi_style: nu_ansi_term::Style = style.into();
Ok::<String, LuaError>(ansi_style.paint(string).to_string())
} else {
Ok(string)
}
})?;
util.set("paint", func)?;
Ok(util)
}
pub fn style_mix(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, styles: Vec<Table>| {
let mut style = Style::default();
for other in styles {
let other: Style = lua.from_value(Value::Table(other))?;
style = style.extend(&other);
}
lua::serialize(lua, &style).map_err(LuaError::custom)
})?;
util.set("style_mix", func)?;
Ok(util)
}
pub fn textwrap(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, (text, options): (String, Value)| {
let lines = match lua.from_value::<usize>(options.clone()) {
Ok(width) => textwrap::wrap(&text, width),
Err(_) => {
let options = lua.from_value::<WrapOptions>(options)?;
textwrap::wrap(&text, options.get_options())
}
};
Ok(lines.iter().map(Cow::to_string).collect::<Vec<String>>())
})?;
util.set("textwrap", func)?;
Ok(util)
}
pub fn layout_replace(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(
move |lua, (layout, target, replacement): (Value, Value, Value)| {
let layout: Layout = lua.from_value(layout)?;
let target: Layout = lua.from_value(target)?;
let replacement: Layout = lua.from_value(replacement)?;
let res = layout.replace(&target, &replacement);
let res = lua::serialize(lua, &res).map_err(LuaError::custom)?;
Ok(res)
},
)?;
util.set("layout_replace", func)?;
Ok(util)
}
pub fn permissions_rwx(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, permission: Table| {
let permissions: Permissions = lua.from_value(Value::Table(permission))?;
let permissions = permissions.to_string();
Ok(permissions)
})?;
util.set("permissions_rwx", func)?;
Ok(util)
}
pub fn permissions_octal(util: Table, lua: &Lua) -> Result<Table> {
let func = lua.create_function(|lua, permission: Table| {
let permissions: Permissions = lua.from_value(Value::Table(permission))?;
let permissions: Octal = permissions.into();
let permissions = lua::serialize(lua, &permissions).map_err(LuaError::custom)?;
Ok(permissions)
})?;
util.set("permissions_octal", func)?;
Ok(util)
}
pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
let mut util = lua.create_table()?;
util = version(util, lua)?;
util = debug(util, lua)?;
util = clone(util, lua)?;
util = exists(util, lua)?;
util = is_dir(util, lua)?;
util = is_file(util, lua)?;
util = is_symlink(util, lua)?;
util = is_absolute(util, lua)?;
util = path_split(util, lua)?;
util = node(util, lua)?;
util = node_type(util, lua)?;
util = dirname(util, lua)?;
util = basename(util, lua)?;
util = absolute(util, lua)?;
util = relative_to(util, lua)?;
util = shorten(util, lua)?;
util = explore(util, lua)?;
util = shell_execute(util, lua)?;
util = shell_quote(util, lua)?;
util = shell_escape(util, lua)?;
util = from_json(util, lua)?;
util = to_json(util, lua)?;
util = from_yaml(util, lua)?;
util = to_yaml(util, lua)?;
util = lscolor(util, lua)?;
util = paint(util, lua)?;
util = style_mix(util, lua)?;
util = textwrap(util, lua)?;
util = layout_replace(util, lua)?;
util = permissions_rwx(util, lua)?;
util = permissions_octal(util, lua)?;
Ok(util)
}