Initial prototype for Lua plugins

This commit is contained in:
David Peter 2022-05-29 20:38:58 +02:00
parent 3339eee2dc
commit 3811615606
8 changed files with 119 additions and 6 deletions

25
Cargo.lock generated
View File

@ -98,6 +98,7 @@ dependencies = [
"path_abs",
"predicates",
"regex",
"rlua",
"semver",
"serde",
"serde_yaml",
@ -916,6 +917,30 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "rlua"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "627ae78424400009e889c43b0c188d0b7af3fe7301b68c03d9cfacb29115408a"
dependencies = [
"bitflags",
"bstr",
"libc",
"num-traits",
"rlua-lua54-sys",
]
[[package]]
name = "rlua-lua54-sys"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4e1fdfc6a5f7acfa1b1fe26c5076b54f5ebd6818b5982460c39844c8b859370"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "rustversion"
version = "1.0.6"

View File

@ -30,6 +30,7 @@ minimal-application = [
"paging",
"regex-onig",
"wild",
"rlua"
]
git = ["git2"] # Support indicating git modifications
paging = ["shell-words", "grep-cli"] # Support applying a pager on the output
@ -65,6 +66,7 @@ grep-cli = { version = "0.1.6", optional = true }
regex = { version = "1.5.5", optional = true }
walkdir = { version = "2.0", optional = true }
bytesize = { version = "1.1.0" }
rlua = { version = "0.19", optional = true }
[dependencies.git2]
version = "0.14"

21
plugins/uncompress.lua Normal file
View File

@ -0,0 +1,21 @@
function tempdir()
local stream = assert(io.popen('mktemp --directory'))
local output = stream:read('*all')
stream:close()
return string.gsub(output, "\n", "")
end
function preprocess(path)
prefix = string.match(path, '^(.*)%.gz$')
if prefix then
local temp_directory = tempdir()
local new_path = temp_directory .. "/" .. prefix
-- TODO: how to prevent shell injection bugs?
os.execute("gunzip < '" .. path .. "' > '" .. new_path .. "'")
return new_path
else
return path
end
end

View File

@ -241,6 +241,11 @@ impl App {
.map(HighlightedLineRanges)
.unwrap_or_default(),
use_custom_assets: !self.matches.is_present("no-custom-assets"),
plugins: self
.matches
.values_of_os("load-plugin")
.unwrap_or_default()
.collect(),
})
}

View File

@ -474,6 +474,14 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
.help("Display all supported languages.")
.long_help("Display a list of supported languages for syntax highlighting."),
)
.arg(
Arg::with_name("load-plugin")
.long("load-plugin")
.takes_value(true)
.value_name("name")
.help("Load plugin with specified name.")
.hidden_short_help(true)
)
.arg(
Arg::with_name("unbuffered")
.short("u")

View File

@ -8,6 +8,7 @@ mod directories;
mod input;
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::io;
use std::io::{BufReader, Write};
use std::path::Path;
@ -218,7 +219,51 @@ pub fn list_themes(cfg: &Config) -> Result<()> {
Ok(())
}
fn run_controller(inputs: Vec<Input>, config: &Config) -> Result<bool> {
fn load_and_run_preprocess_plugins(plugins: &[&OsStr], inputs: &mut Vec<Input>) -> Result<()> {
use bat::input::InputKind;
use rlua::{Function, Lua, Result as LuaResult};
use std::fs;
use std::path::PathBuf;
let lua = Lua::new();
for plugin_name in plugins {
// TODO: how to handle plugin priority?
let mut plugin_path = PathBuf::from("plugins");
plugin_path.push(plugin_name);
let plugin_source_code = fs::read_to_string(&plugin_path).unwrap(); // TODO: proper error handling
lua.context::<_, LuaResult<()>>(|lua_ctx| {
let globals = lua_ctx.globals();
lua_ctx.load(&plugin_source_code).exec()?;
let preprocess: Function = globals.get("preprocess")?;
for input in inputs.iter_mut() {
if let InputKind::OrdinaryFile(ref mut path) = &mut input.kind {
let path_str: String = path.to_string_lossy().into();
let new_path = preprocess.call::<_, String>(path_str)?;
*path = PathBuf::from(new_path);
input.metadata.user_provided_name =
Some(PathBuf::from(path.file_name().unwrap())); // TODO
}
}
Ok(())
})
.map_err(|e| format!("Error while executing Lua code: {}", e))?;
}
Ok(())
}
fn run_controller(mut inputs: Vec<Input>, config: &Config) -> Result<bool> {
load_and_run_preprocess_plugins(&config.plugins, &mut inputs)?;
let assets = assets_from_cache_or_binary(config.use_custom_assets)?;
let controller = Controller::new(config, &assets);
controller.run(inputs)

View File

@ -5,6 +5,8 @@ use crate::style::StyleComponents;
use crate::syntax_mapping::SyntaxMapping;
use crate::wrapping::WrappingMode;
use std::ffi::OsStr;
#[derive(Debug, Clone)]
pub enum VisibleLines {
/// Show all lines which are included in the line ranges
@ -86,6 +88,9 @@ pub struct Config<'a> {
/// Whether or not to allow custom assets. If this is false or if custom assets (a.k.a.
/// cached assets) are not available, assets from the binary will be used instead.
pub use_custom_assets: bool,
/// List of bat plugins to be loaded
pub plugins: Vec<&'a OsStr>,
}
#[cfg(all(feature = "minimal-application", feature = "paging"))]

View File

@ -69,7 +69,8 @@ impl InputDescription {
}
}
pub(crate) enum InputKind<'a> {
pub enum InputKind<'a> {
// TODO
OrdinaryFile(PathBuf),
StdIn,
CustomReader(Box<dyn Read + 'a>),
@ -86,14 +87,15 @@ impl<'a> InputKind<'a> {
}
#[derive(Clone, Default)]
pub(crate) struct InputMetadata {
pub(crate) user_provided_name: Option<PathBuf>,
pub struct InputMetadata {
// TODO
pub user_provided_name: Option<PathBuf>,
pub(crate) size: Option<u64>,
}
pub struct Input<'a> {
pub(crate) kind: InputKind<'a>,
pub(crate) metadata: InputMetadata,
pub kind: InputKind<'a>, // TODO
pub metadata: InputMetadata, // TODO
pub(crate) description: InputDescription,
}