1
0
mirror of https://github.com/sharkdp/bat.git synced 2025-07-15 03:23:36 +01:00

Compare commits

...

6 Commits

Author SHA1 Message Date
aa35cb52c4 Add exemplary hexyl plugin 2022-05-29 22:51:10 +02:00
446b9181e6 Add simple 'directories' plugin 2022-05-29 21:04:47 +02:00
040242c9be Add initial version of curl plugin 2022-05-29 20:57:08 +02:00
dbf78d280a Fix loading of several plugins 2022-05-29 20:57:01 +02:00
bc91af3ee5 Better error handling 2022-05-29 20:48:37 +02:00
3811615606 Initial prototype for Lua plugins 2022-05-29 20:40:05 +02:00
11 changed files with 195 additions and 6 deletions

25
Cargo.lock generated

@ -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"

@ -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/curl.lua Normal 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_or_url)
filename_from_url = string.match(path_or_url, '^https?://.*/(.*)$')
if filename_from_url then
local temp_directory = tempdir()
local new_path = temp_directory .. "/" .. filename_from_url
-- TODO: how to prevent shell injection bugs?
os.execute("curl --silent '" .. path_or_url .. "' --output '" .. new_path .. "'")
return new_path
else
return path_or_url
end
end

17
plugins/directories.lua Normal file

@ -0,0 +1,17 @@
-- https://stackoverflow.com/a/3254007/704831
function is_dir(path)
local f = io.open(path, "r")
local ok, err, code = f:read(1)
f:close()
return code == 21
end
function preprocess(path)
if is_dir(path) then
tmpfile = os.tmpname()
os.execute("ls -alh --color=always '" .. path .. "' > '" .. tmpfile .. "'")
return tmpfile
else
return path
end
end

21
plugins/hexyl.lua Normal file

@ -0,0 +1,21 @@
-- Note: this plugin depends on the existence of 'inspect' [1] and 'hexyl' [2]
--
-- [1] https://github.com/sharkdp/content_inspector
-- [2] https://github.com/sharkdp/hexyl
function is_binary(path)
local stream = assert(io.popen("inspect '" .. path .. "'"))
local output = stream:read('*all')
stream:close()
return string.find(output, ": binary\n")
end
function preprocess(path)
if is_binary(path) then
tmpfile = os.tmpname()
os.execute("hexyl --length 1024 --no-position --border=none --no-squeezing '" .. path .. "' > '" .. tmpfile .. "'")
return tmpfile
else
return path
end
end

21
plugins/uncompress.lua Normal 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

@ -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(),
})
}

@ -474,6 +474,16 @@ 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")
.multiple(true)
.takes_value(true)
.number_of_values(1)
.value_name("name")
.help("Load plugin with specified name.")
.hidden_short_help(true)
)
.arg(
Arg::with_name("unbuffered")
.short("u")

@ -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,66 @@ 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;
if plugins.is_empty() {
// Do not create Lua context if there are no plugins
return Ok(());
}
let lua = Lua::new();
for plugin_name in plugins {
// TODO: properly load plugins from a central directory + user directories
// 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).map_err(|e| {
format!(
"Could not load bat plugin '{}': {}",
plugin_path.to_string_lossy(),
e
)
})?;
lua.context::<_, LuaResult<()>>(|lua_ctx| {
let globals = lua_ctx.globals();
lua_ctx.load(&plugin_source_code).exec()?;
// Plugins are expected to have a 'preprocess' function
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);
// TODO: the following line overwrites actual user provided names. However,
// this is necessary to get proper syntax highlighting for the path that
// is being provided by the plugin.
input.metadata.user_provided_name = Some(path.clone());
}
}
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)

@ -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"))]

@ -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,
}