diff --git a/Cargo.lock b/Cargo.lock index 8babb11a..578c315e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index deac2b5d..4d2a6a44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/plugins/uncompress.lua b/plugins/uncompress.lua new file mode 100644 index 00000000..303781f3 --- /dev/null +++ b/plugins/uncompress.lua @@ -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 \ No newline at end of file diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index 78a0df69..242d27f1 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -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(), }) } diff --git a/src/bin/bat/clap_app.rs b/src/bin/bat/clap_app.rs index c2cdb23f..f786ab11 100644 --- a/src/bin/bat/clap_app.rs +++ b/src/bin/bat/clap_app.rs @@ -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") diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs index 6b2daa2b..b7aaaf86 100644 --- a/src/bin/bat/main.rs +++ b/src/bin/bat/main.rs @@ -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, config: &Config) -> Result { +fn load_and_run_preprocess_plugins(plugins: &[&OsStr], inputs: &mut Vec) -> 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, config: &Config) -> Result { + 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) diff --git a/src/config.rs b/src/config.rs index 76eb3990..e5f15a3e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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"))] diff --git a/src/input.rs b/src/input.rs index 23e21506..6fad2282 100644 --- a/src/input.rs +++ b/src/input.rs @@ -69,7 +69,8 @@ impl InputDescription { } } -pub(crate) enum InputKind<'a> { +pub enum InputKind<'a> { + // TODO OrdinaryFile(PathBuf), StdIn, CustomReader(Box), @@ -86,14 +87,15 @@ impl<'a> InputKind<'a> { } #[derive(Clone, Default)] -pub(crate) struct InputMetadata { - pub(crate) user_provided_name: Option, +pub struct InputMetadata { + // TODO + pub user_provided_name: Option, pub(crate) size: Option, } 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, }