Support for ignored-suffix CLI arguments (#1892)

This commit is contained in:
Bojan Đurđević 2021-11-19 11:05:23 -05:00 committed by GitHub
parent 59d4cfb75c
commit d6ed5e6746
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 191 additions and 55 deletions

View File

@ -2,6 +2,7 @@
## Features
- Support for `--ignored-suffix` argument. See #1892 (@bojan88)
- `$BAT_CONFIG_DIR` is now a recognized environment variable. It has precedence over `$XDG_CONFIG_HOME`, see #1727 (@billrisher)
- Support for `x:+delta` syntax in line ranges (e.g. `20:+10`). See #1810 (@bojan88)

View File

@ -9,12 +9,12 @@ use syntect::parsing::{SyntaxReference, SyntaxSet};
use path_abs::PathAbs;
use crate::bat_warning;
use crate::error::*;
use crate::input::{InputReader, OpenedInput};
use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
use crate::syntax_mapping::ignored_suffixes::IgnoredSuffixes;
use crate::syntax_mapping::MappingTarget;
use crate::{bat_warning, SyntaxMapping};
use ignored_suffixes::*;
use minimal_assets::*;
use serialized_syntax_set::*;
@ -24,7 +24,6 @@ pub use crate::assets::build_assets::*;
pub(crate) mod assets_metadata;
#[cfg(feature = "build-assets")]
mod build_assets;
mod ignored_suffixes;
mod minimal_assets;
mod serialized_syntax_set;
@ -195,7 +194,10 @@ impl HighlightingAssets {
let file_name = path.file_name().unwrap_or_default();
match (self.get_syntax_for_file_name(file_name)?, syntax_match) {
match (
self.get_syntax_for_file_name(file_name, &mapping.ignored_suffixes)?,
syntax_match,
) {
(Some(syntax), _) => Ok(syntax),
(_, Some(MappingTarget::MapExtensionToUnknown)) => {
@ -203,7 +205,7 @@ impl HighlightingAssets {
}
_ => self
.get_syntax_for_file_extension(file_name)?
.get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
.ok_or_else(|| Error::UndetectedSyntax(path.to_string_lossy().into())),
}
}
@ -277,12 +279,18 @@ impl HighlightingAssets {
.map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
}
fn get_syntax_for_file_name(&self, file_name: &OsStr) -> Result<Option<SyntaxReferenceInSet>> {
fn get_syntax_for_file_name(
&self,
file_name: &OsStr,
ignored_suffixes: &IgnoredSuffixes,
) -> Result<Option<SyntaxReferenceInSet>> {
let mut syntax = self.find_syntax_by_extension(Some(file_name))?;
if syntax.is_none() {
syntax = try_with_stripped_suffix(file_name, |stripped_file_name| {
self.get_syntax_for_file_name(stripped_file_name) // Note: recursion
})?;
syntax =
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
// Note: recursion
self.get_syntax_for_file_name(stripped_file_name, ignored_suffixes)
})?;
}
Ok(syntax)
}
@ -290,12 +298,15 @@ impl HighlightingAssets {
fn get_syntax_for_file_extension(
&self,
file_name: &OsStr,
ignored_suffixes: &IgnoredSuffixes,
) -> Result<Option<SyntaxReferenceInSet>> {
let mut syntax = self.find_syntax_by_extension(Path::new(file_name).extension())?;
if syntax.is_none() {
syntax = try_with_stripped_suffix(file_name, |stripped_file_name| {
self.get_syntax_for_file_extension(stripped_file_name) // Note: recursion
})?;
syntax =
ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
// Note: recursion
self.get_syntax_for_file_extension(stripped_file_name, ignored_suffixes)
})?;
}
Ok(syntax)
}

View File

@ -1,42 +0,0 @@
use std::ffi::OsStr;
use std::path::Path;
use crate::error::*;
const IGNORED_SUFFIXES: [&str; 13] = [
// Editor etc backups
"~",
".bak",
".old",
".orig",
// Debian and derivatives apt/dpkg/ucf backups
".dpkg-dist",
".dpkg-old",
".ucf-dist",
".ucf-new",
".ucf-old",
// Red Hat and derivatives rpm backups
".rpmnew",
".rpmorig",
".rpmsave",
// Build system input/template files
".in",
];
/// If we find an ignored suffix on the file name, e.g. '~', we strip it and
/// then try again without it.
pub fn try_with_stripped_suffix<T, F>(file_name: &OsStr, func: F) -> Result<Option<T>>
where
F: Fn(&OsStr) -> Result<Option<T>>,
{
let mut from_stripped = None;
if let Some(file_str) = Path::new(file_name).to_str() {
for suffix in &IGNORED_SUFFIXES {
if let Some(stripped_filename) = file_str.strip_suffix(suffix) {
from_stripped = func(OsStr::new(stripped_filename))?;
break;
}
}
}
Ok(from_stripped)
}

View File

@ -107,6 +107,12 @@ impl App {
let mut syntax_mapping = SyntaxMapping::builtin();
if let Some(values) = self.matches.values_of("ignored-suffix") {
for suffix in values {
syntax_mapping.insert_ignored_suffix(suffix);
}
}
if let Some(values) = self.matches.values_of("map-syntax") {
for from_to in values {
let parts: Vec<_> = from_to.split(':').collect();

View File

@ -509,6 +509,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
.hidden_short_help(true)
.help("Show diagnostic information for bug reports.")
)
.arg(
Arg::with_name("ignored-suffix")
.number_of_values(1)
.multiple(true)
.takes_value(true)
.long("ignored-suffix")
.hidden_short_help(true)
.help(
"Ignore extension. For example:\n \
'bat --ignored-suffix \".dev\" my_file.json.dev' will use JSON syntax, and ignore '.dev'"
)
)
.help_message("Print this help message.")
.version_message("Show version information.");

View File

@ -1,9 +1,12 @@
use std::path::Path;
use crate::error::Result;
use ignored_suffixes::IgnoredSuffixes;
use globset::{Candidate, GlobBuilder, GlobMatcher};
pub mod ignored_suffixes;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MappingTarget<'a> {
/// For mapping a path to a specific syntax.
@ -26,6 +29,7 @@ pub enum MappingTarget<'a> {
#[derive(Debug, Clone, Default)]
pub struct SyntaxMapping<'a> {
mappings: Vec<(GlobMatcher, MappingTarget<'a>)>,
pub(crate) ignored_suffixes: IgnoredSuffixes<'a>,
}
impl<'a> SyntaxMapping<'a> {
@ -172,6 +176,10 @@ impl<'a> SyntaxMapping<'a> {
}
None
}
pub fn insert_ignored_suffix(&mut self, suffix: &'a str) {
self.ignored_suffixes.add_suffix(suffix);
}
}
#[test]

View File

@ -0,0 +1,107 @@
use std::ffi::OsStr;
use std::fmt::Debug;
use std::path::Path;
use crate::error::*;
#[derive(Debug, Clone)]
pub struct IgnoredSuffixes<'a> {
values: Vec<&'a str>,
}
impl Default for IgnoredSuffixes<'_> {
fn default() -> Self {
Self {
values: vec![
// Editor etc backups
"~",
".bak",
".old",
".orig",
// Debian and derivatives apt/dpkg/ucf backups
".dpkg-dist",
".dpkg-old",
".ucf-dist",
".ucf-new",
".ucf-old",
// Red Hat and derivatives rpm backups
".rpmnew",
".rpmorig",
".rpmsave",
// Build system input/template files
".in",
],
}
}
}
impl<'a> IgnoredSuffixes<'a> {
pub fn add_suffix(&mut self, suffix: &'a str) {
self.values.push(suffix)
}
pub fn strip_suffix(&self, file_name: &'a str) -> Option<&'a str> {
for suffix in self.values.iter() {
if let Some(stripped_file_name) = file_name.strip_suffix(suffix) {
return Some(stripped_file_name);
}
}
None
}
/// If we find an ignored suffix on the file name, e.g. '~', we strip it and
/// then try again without it.
pub fn try_with_stripped_suffix<T, F>(&self, file_name: &'a OsStr, func: F) -> Result<Option<T>>
where
F: Fn(&'a OsStr) -> Result<Option<T>>,
{
if let Some(file_str) = Path::new(file_name).to_str() {
if let Some(stripped_file_name) = self.strip_suffix(file_str) {
return func(OsStr::new(stripped_file_name));
}
}
Ok(None)
}
}
#[test]
fn internal_suffixes() {
let ignored_suffixes = IgnoredSuffixes::default();
let file_names = ignored_suffixes
.values
.iter()
.map(|suffix| format!("test.json{}", suffix));
for file_name_str in file_names {
let file_name = OsStr::new(&file_name_str);
let expected_stripped_file_name = OsStr::new("test.json");
let stripped_file_name = ignored_suffixes
.try_with_stripped_suffix(file_name, |stripped_file_name| Ok(Some(stripped_file_name)));
assert_eq!(
expected_stripped_file_name,
stripped_file_name.unwrap().unwrap()
);
}
}
#[test]
fn external_suffixes() {
let mut ignored_suffixes = IgnoredSuffixes::default();
ignored_suffixes.add_suffix(".development");
ignored_suffixes.add_suffix(".production");
let file_names = ignored_suffixes
.values
.iter()
.map(|suffix| format!("test.json{}", suffix));
for file_name_str in file_names {
let file_name = OsStr::new(&file_name_str);
let expected_stripped_file_name = OsStr::new("test.json");
let stripped_file_name = ignored_suffixes
.try_with_stripped_suffix(file_name, |stripped_file_name| Ok(Some(stripped_file_name)));
assert_eq!(
expected_stripped_file_name,
stripped_file_name.unwrap().unwrap()
);
}
}

1
tests/examples/test.json.suffix vendored Normal file
View File

@ -0,0 +1 @@
{"test": "value"}

1
tests/examples/test.json~ vendored Normal file
View File

@ -0,0 +1 @@
{"test": "value"}

View File

@ -1225,3 +1225,34 @@ fn grid_for_file_without_newline() {
)
.stderr("");
}
#[test]
fn ignored_suffix_arg() {
bat()
.arg("-f")
.arg("-p")
.arg("test.json~")
.assert()
.success()
.stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m")
.stderr("");
bat()
.arg("-f")
.arg("-p")
.arg("--ignored-suffix=.suffix")
.arg("test.json.suffix")
.assert()
.success()
.stdout("\u{1b}[38;5;231m{\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;208mtest\u{1b}[0m\u{1b}[38;5;208m\"\u{1b}[0m\u{1b}[38;5;231m:\u{1b}[0m\u{1b}[38;5;231m \u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;186mvalue\u{1b}[0m\u{1b}[38;5;186m\"\u{1b}[0m\u{1b}[38;5;231m}\u{1b}[0m")
.stderr("");
bat()
.arg("-f")
.arg("-p")
.arg("test.json.suffix")
.assert()
.success()
.stdout("\u{1b}[38;5;231m{\"test\": \"value\"}\u{1b}[0m")
.stderr("");
}