mirror of
https://github.com/sharkdp/bat.git
synced 2025-09-01 10:52:24 +01:00
Compare commits
12 Commits
v0.21.0
...
bat-plugin
Author | SHA1 | Date | |
---|---|---|---|
|
aa35cb52c4 | ||
|
446b9181e6 | ||
|
040242c9be | ||
|
dbf78d280a | ||
|
bc91af3ee5 | ||
|
3811615606 | ||
|
3339eee2dc | ||
|
e9f8370b13 | ||
|
892b186ba5 | ||
|
6db64cf050 | ||
|
5f139e5ec2 | ||
|
8b50ef87fd |
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -7,3 +7,5 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Using a normal ticket is still fine, but feel free to ask your
|
||||
questions about bat on https://github.com/sharkdp/bat/discussions instead. -->
|
||||
|
2
.github/workflows/CICD.yml
vendored
2
.github/workflows/CICD.yml
vendored
@@ -154,7 +154,7 @@ jobs:
|
||||
- { target: x86_64-apple-darwin , os: macos-10.15 }
|
||||
- { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||
- { target: x86_64-pc-windows-msvc , os: windows-2019 }
|
||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 }
|
||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
||||
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
|
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,3 +1,22 @@
|
||||
# unreleased
|
||||
|
||||
## Features
|
||||
|
||||
- Make the default macOS theme depend on Dark Mode. See #2197, #1746 (@Enselic)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
## Other
|
||||
|
||||
- Relaxed glibc requirements on amd64, see #2106 and #2194 (@sharkdp)
|
||||
|
||||
## Syntaxes
|
||||
|
||||
## Themes
|
||||
|
||||
## `bat` as a library
|
||||
|
||||
|
||||
# v0.21.0
|
||||
|
||||
## Features
|
||||
@@ -30,8 +49,6 @@
|
||||
- Slightly adjust Zig syntax. See #2136 (@Enselic)
|
||||
- Associate `.inf` files with the `INI` syntax. See #2190 (@Enselic)
|
||||
|
||||
## Themes
|
||||
|
||||
## `bat` as a library
|
||||
|
||||
- Allow configuration of `show_nonprintable` with `PrettyPrinter`, see #2142
|
||||
|
25
Cargo.lock
generated
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
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
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
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
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
|
@@ -69,10 +69,57 @@ impl HighlightingAssets {
|
||||
}
|
||||
}
|
||||
|
||||
/// The default theme.
|
||||
///
|
||||
/// ### Windows and Linux
|
||||
///
|
||||
/// Windows and most Linux distributions has a dark terminal theme by
|
||||
/// default. On these platforms, this function always returns a theme that
|
||||
/// looks good on a dark background.
|
||||
///
|
||||
/// ### macOS
|
||||
///
|
||||
/// On macOS the default terminal background is light, but it is common that
|
||||
/// Dark Mode is active, which makes the terminal background dark. On this
|
||||
/// platform, the default theme depends on
|
||||
/// ```bash
|
||||
/// defaults read -globalDomain AppleInterfaceStyle
|
||||
/// ````
|
||||
/// To avoid the overhead of the check on macOS, simply specify a theme
|
||||
/// explicitly via `--theme`, `BAT_THEME`, or `~/.config/bat`.
|
||||
///
|
||||
/// See <https://github.com/sharkdp/bat/issues/1746> and
|
||||
/// <https://github.com/sharkdp/bat/issues/1928> for more context.
|
||||
pub fn default_theme() -> &'static str {
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
Self::default_dark_theme()
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if macos_dark_mode_active() {
|
||||
Self::default_dark_theme()
|
||||
} else {
|
||||
Self::default_light_theme()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default theme that looks good on a dark background.
|
||||
*/
|
||||
fn default_dark_theme() -> &'static str {
|
||||
"Monokai Extended"
|
||||
}
|
||||
|
||||
/**
|
||||
* The default theme that looks good on a light background.
|
||||
*/
|
||||
#[cfg(target_os = "macos")]
|
||||
fn default_light_theme() -> &'static str {
|
||||
"Monokai Extended Light"
|
||||
}
|
||||
|
||||
pub fn from_cache(cache_path: &Path) -> Result<Self> {
|
||||
Ok(HighlightingAssets::new(
|
||||
SerializedSyntaxSet::FromFile(cache_path.join("syntaxes.bin")),
|
||||
@@ -352,6 +399,16 @@ fn asset_from_cache<T: serde::de::DeserializeOwned>(
|
||||
.map_err(|_| format!("Could not parse cached {}", description).into())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn macos_dark_mode_active() -> bool {
|
||||
let mut defaults_cmd = std::process::Command::new("defaults");
|
||||
defaults_cmd.args(&["read", "-globalDomain", "AppleInterfaceStyle"]);
|
||||
match defaults_cmd.output() {
|
||||
Ok(output) => output.stdout == b"Dark\n",
|
||||
Err(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -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"))]
|
||||
|
12
src/input.rs
12
src/input.rs
@@ -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,
|
||||
}
|
||||
|
||||
|
@@ -1427,6 +1427,8 @@ fn ansi_passthrough_emit() {
|
||||
fn ignored_suffix_arg() {
|
||||
bat()
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("test.json~")
|
||||
.assert()
|
||||
@@ -1436,6 +1438,8 @@ fn ignored_suffix_arg() {
|
||||
|
||||
bat()
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("--ignored-suffix=.suffix")
|
||||
.arg("test.json.suffix")
|
||||
@@ -1446,6 +1450,8 @@ fn ignored_suffix_arg() {
|
||||
|
||||
bat()
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("test.json.suffix")
|
||||
.assert()
|
||||
@@ -1463,6 +1469,8 @@ fn highlighting_is_skipped_on_long_lines() {
|
||||
|
||||
bat()
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("longline.json")
|
||||
.assert()
|
||||
@@ -1482,6 +1490,8 @@ fn all_global_git_config_locations_syntax_mapping_work() {
|
||||
bat()
|
||||
.env("XDG_CONFIG_HOME", fake_home.join(".config").as_os_str())
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("git/.config/git/config")
|
||||
.assert()
|
||||
@@ -1492,6 +1502,8 @@ fn all_global_git_config_locations_syntax_mapping_work() {
|
||||
bat()
|
||||
.env("HOME", fake_home.as_os_str())
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("git/.config/git/config")
|
||||
.assert()
|
||||
@@ -1502,6 +1514,8 @@ fn all_global_git_config_locations_syntax_mapping_work() {
|
||||
bat()
|
||||
.env("HOME", fake_home.as_os_str())
|
||||
.arg("-f")
|
||||
.arg("--theme")
|
||||
.arg("Monokai Extended")
|
||||
.arg("-p")
|
||||
.arg("git/.gitconfig")
|
||||
.assert()
|
||||
|
Reference in New Issue
Block a user