on master help sign in
Support for ignored-suffix CLI arguments (#1892)
commitd6ed5e67469dde0e7b9523c7754761b9203ca370 authorBojan Đurđević <bojan88@gmail.com> dateFri 19 Nov 2021, 11:05 (5 years ago) parent59d4cfb
10 files changed+191−55
CHANGELOG.md+1 src/assets.rs+24−13 src/assets/ignored_suffixes.rs−42 src/bin/bat/app.rs+6 src/bin/bat/clap_app.rs+12 src/syntax_mapping.rs+8 src/syntax_mapping/ignored_suffixes.rs+107 tests/examples/test.json.suffix+1 tests/examples/test.json~+1 tests/integration_tests.rs+31
CHANGELOG.md+1
@@ -2,6 +2,7 @@
22
33## Features
44
5+- Support for `--ignored-suffix` argument. See #1892 (@bojan88)
56- `$BAT_CONFIG_DIR` is now a recognized environment variable. It has precedence over `$XDG_CONFIG_HOME`, see #1727 (@billrisher)
67- Support for `x:+delta` syntax in line ranges (e.g. `20:+10`). See #1810 (@bojan88)
78
src/assets.rs+24−13
@@ -9,12 +9,12 @@ use syntect::parsing::{SyntaxReference, SyntaxSet};
99
1010use path_abs::PathAbs;
1111
12−use crate::bat_warning;
1312use crate::error::*;
1413use crate::input::{InputReader, OpenedInput};
15−use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
14+use crate::syntax_mapping::ignored_suffixes::IgnoredSuffixes;
15+use crate::syntax_mapping::MappingTarget;
16+use crate::{bat_warning, SyntaxMapping};
1617
17−use ignored_suffixes::*;
1818use minimal_assets::*;
1919use serialized_syntax_set::*;
2020
@@ -24,7 +24,6 @@ pub use crate::assets::build_assets::*;
2424pub(crate) mod assets_metadata;
2525#[cfg(feature = "build-assets")]
2626mod build_assets;
27−mod ignored_suffixes;
2827mod minimal_assets;
2928mod serialized_syntax_set;
3029
@@ -195,7 +194,10 @@ impl HighlightingAssets {
195194
196195 let file_name = path.file_name().unwrap_or_default();
197196
198− match (self.get_syntax_for_file_name(file_name)?, syntax_match) {
197+ match (
198+ self.get_syntax_for_file_name(file_name, &mapping.ignored_suffixes)?,
199+ syntax_match,
200+ ) {
199201 (Some(syntax), _) => Ok(syntax),
200202
201203 (_, Some(MappingTarget::MapExtensionToUnknown)) => {
@@ -203,7 +205,7 @@ impl HighlightingAssets {
203205 }
204206
205207 _ => self
206− .get_syntax_for_file_extension(file_name)?
208+ .get_syntax_for_file_extension(file_name, &mapping.ignored_suffixes)?
207209 .ok_or_else(|| Error::UndetectedSyntax(path.to_string_lossy().into())),
208210 }
209211 }
@@ -277,12 +279,18 @@ impl HighlightingAssets {
277279 .map(|syntax| SyntaxReferenceInSet { syntax, syntax_set }))
278280 }
279281
280− fn get_syntax_for_file_name(&self, file_name: &OsStr) -> Result<Option<SyntaxReferenceInSet>> {
282+ fn get_syntax_for_file_name(
283+ &self,
284+ file_name: &OsStr,
285+ ignored_suffixes: &IgnoredSuffixes,
286+ ) -> Result<Option<SyntaxReferenceInSet>> {
281287 let mut syntax = self.find_syntax_by_extension(Some(file_name))?;
282288 if syntax.is_none() {
283− syntax = try_with_stripped_suffix(file_name, |stripped_file_name| {
284− self.get_syntax_for_file_name(stripped_file_name) // Note: recursion
285− })?;
289+ syntax =
290+ ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
291+ // Note: recursion
292+ self.get_syntax_for_file_name(stripped_file_name, ignored_suffixes)
293+ })?;
286294 }
287295 Ok(syntax)
288296 }
@@ -290,12 +298,15 @@ impl HighlightingAssets {
290298 fn get_syntax_for_file_extension(
291299 &self,
292300 file_name: &OsStr,
301+ ignored_suffixes: &IgnoredSuffixes,
293302 ) -> Result<Option<SyntaxReferenceInSet>> {
294303 let mut syntax = self.find_syntax_by_extension(Path::new(file_name).extension())?;
295304 if syntax.is_none() {
296− syntax = try_with_stripped_suffix(file_name, |stripped_file_name| {
297− self.get_syntax_for_file_extension(stripped_file_name) // Note: recursion
298− })?;
305+ syntax =
306+ ignored_suffixes.try_with_stripped_suffix(file_name, |stripped_file_name| {
307+ // Note: recursion
308+ self.get_syntax_for_file_extension(stripped_file_name, ignored_suffixes)
309+ })?;
299310 }
300311 Ok(syntax)
301312 }
src/assets/ignored_suffixes.rsdeleted−42
@@ -1,42 +0,0 @@
1−use std::ffi::OsStr;
2−use std::path::Path;
3−
4−use crate::error::*;
5−
6−const IGNORED_SUFFIXES: [&str; 13] = [
7− // Editor etc backups
8− "~",
9− ".bak",
10− ".old",
11− ".orig",
12− // Debian and derivatives apt/dpkg/ucf backups
13− ".dpkg-dist",
14− ".dpkg-old",
15− ".ucf-dist",
16− ".ucf-new",
17− ".ucf-old",
18− // Red Hat and derivatives rpm backups
19− ".rpmnew",
20− ".rpmorig",
21− ".rpmsave",
22− // Build system input/template files
23− ".in",
24−];
25−
26−/// If we find an ignored suffix on the file name, e.g. '~', we strip it and
27−/// then try again without it.
28−pub fn try_with_stripped_suffix<T, F>(file_name: &OsStr, func: F) -> Result<Option<T>>
29−where
30− F: Fn(&OsStr) -> Result<Option<T>>,
31−{
32− let mut from_stripped = None;
33− if let Some(file_str) = Path::new(file_name).to_str() {
34− for suffix in &IGNORED_SUFFIXES {
35− if let Some(stripped_filename) = file_str.strip_suffix(suffix) {
36− from_stripped = func(OsStr::new(stripped_filename))?;
37− break;
38− }
39− }
40− }
41− Ok(from_stripped)
42−}
src/bin/bat/app.rs+6
@@ -107,6 +107,12 @@ impl App {
107107
108108 let mut syntax_mapping = SyntaxMapping::builtin();
109109
110+ if let Some(values) = self.matches.values_of("ignored-suffix") {
111+ for suffix in values {
112+ syntax_mapping.insert_ignored_suffix(suffix);
113+ }
114+ }
115+
110116 if let Some(values) = self.matches.values_of("map-syntax") {
111117 for from_to in values {
112118 let parts: Vec<_> = from_to.split(':').collect();
src/bin/bat/clap_app.rs+12
@@ -509,6 +509,18 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
509509 .hidden_short_help(true)
510510 .help("Show diagnostic information for bug reports.")
511511 )
512+ .arg(
513+ Arg::with_name("ignored-suffix")
514+ .number_of_values(1)
515+ .multiple(true)
516+ .takes_value(true)
517+ .long("ignored-suffix")
518+ .hidden_short_help(true)
519+ .help(
520+ "Ignore extension. For example:\n \
521+ 'bat --ignored-suffix \".dev\" my_file.json.dev' will use JSON syntax, and ignore '.dev'"
522+ )
523+ )
512524 .help_message("Print this help message.")
513525 .version_message("Show version information.");
514526
src/syntax_mapping.rs+8
@@ -1,9 +1,12 @@
11use std::path::Path;
22
33use crate::error::Result;
4+use ignored_suffixes::IgnoredSuffixes;
45
56use globset::{Candidate, GlobBuilder, GlobMatcher};
67
8+pub mod ignored_suffixes;
9+
710#[derive(Debug, Clone, Copy, PartialEq)]
811pub enum MappingTarget<'a> {
912 /// For mapping a path to a specific syntax.
@@ -26,6 +29,7 @@ pub enum MappingTarget<'a> {
2629#[derive(Debug, Clone, Default)]
2730pub struct SyntaxMapping<'a> {
2831 mappings: Vec<(GlobMatcher, MappingTarget<'a>)>,
32+ pub(crate) ignored_suffixes: IgnoredSuffixes<'a>,
2933}
3034
3135impl<'a> SyntaxMapping<'a> {
@@ -172,6 +176,10 @@ impl<'a> SyntaxMapping<'a> {
172176 }
173177 None
174178 }
179+
180+ pub fn insert_ignored_suffix(&mut self, suffix: &'a str) {
181+ self.ignored_suffixes.add_suffix(suffix);
182+ }
175183}
176184
177185#[test]
src/syntax_mapping/ignored_suffixes.rsnew file+107
@@ -0,0 +1,107 @@
1+use std::ffi::OsStr;
2+use std::fmt::Debug;
3+use std::path::Path;
4+
5+use crate::error::*;
6+
7+#[derive(Debug, Clone)]
8+pub struct IgnoredSuffixes<'a> {
9+ values: Vec<&'a str>,
10+}
11+
12+impl Default for IgnoredSuffixes<'_> {
13+ fn default() -> Self {
14+ Self {
15+ values: vec![
16+ // Editor etc backups
17+ "~",
18+ ".bak",
19+ ".old",
20+ ".orig",
21+ // Debian and derivatives apt/dpkg/ucf backups
22+ ".dpkg-dist",
23+ ".dpkg-old",
24+ ".ucf-dist",
25+ ".ucf-new",
26+ ".ucf-old",
27+ // Red Hat and derivatives rpm backups
28+ ".rpmnew",
29+ ".rpmorig",
30+ ".rpmsave",
31+ // Build system input/template files
32+ ".in",
33+ ],
34+ }
35+ }
36+}
37+
38+impl<'a> IgnoredSuffixes<'a> {
39+ pub fn add_suffix(&mut self, suffix: &'a str) {
40+ self.values.push(suffix)
41+ }
42+
43+ pub fn strip_suffix(&self, file_name: &'a str) -> Option<&'a str> {
44+ for suffix in self.values.iter() {
45+ if let Some(stripped_file_name) = file_name.strip_suffix(suffix) {
46+ return Some(stripped_file_name);
47+ }
48+ }
49+ None
50+ }
51+
52+ /// If we find an ignored suffix on the file name, e.g. '~', we strip it and
53+ /// then try again without it.
54+ pub fn try_with_stripped_suffix<T, F>(&self, file_name: &'a OsStr, func: F) -> Result<Option<T>>
55+ where
56+ F: Fn(&'a OsStr) -> Result<Option<T>>,
57+ {
58+ if let Some(file_str) = Path::new(file_name).to_str() {
59+ if let Some(stripped_file_name) = self.strip_suffix(file_str) {
60+ return func(OsStr::new(stripped_file_name));
61+ }
62+ }
63+ Ok(None)
64+ }
65+}
66+
67+#[test]
68+fn internal_suffixes() {
69+ let ignored_suffixes = IgnoredSuffixes::default();
70+
71+ let file_names = ignored_suffixes
72+ .values
73+ .iter()
74+ .map(|suffix| format!("test.json{}", suffix));
75+ for file_name_str in file_names {
76+ let file_name = OsStr::new(&file_name_str);
77+ let expected_stripped_file_name = OsStr::new("test.json");
78+ let stripped_file_name = ignored_suffixes
79+ .try_with_stripped_suffix(file_name, |stripped_file_name| Ok(Some(stripped_file_name)));
80+ assert_eq!(
81+ expected_stripped_file_name,
82+ stripped_file_name.unwrap().unwrap()
83+ );
84+ }
85+}
86+
87+#[test]
88+fn external_suffixes() {
89+ let mut ignored_suffixes = IgnoredSuffixes::default();
90+ ignored_suffixes.add_suffix(".development");
91+ ignored_suffixes.add_suffix(".production");
92+
93+ let file_names = ignored_suffixes
94+ .values
95+ .iter()
96+ .map(|suffix| format!("test.json{}", suffix));
97+ for file_name_str in file_names {
98+ let file_name = OsStr::new(&file_name_str);
99+ let expected_stripped_file_name = OsStr::new("test.json");
100+ let stripped_file_name = ignored_suffixes
101+ .try_with_stripped_suffix(file_name, |stripped_file_name| Ok(Some(stripped_file_name)));
102+ assert_eq!(
103+ expected_stripped_file_name,
104+ stripped_file_name.unwrap().unwrap()
105+ );
106+ }
107+}
tests/examples/test.json.suffixnew file+1
@@ -0,0 +1 @@
1+{"test": "value"}
tests/examples/test.json~new file+1
@@ -0,0 +1 @@
1+{"test": "value"}
tests/integration_tests.rs+31
@@ -1225,3 +1225,34 @@ fn grid_for_file_without_newline() {
12251225 )
12261226 .stderr("");
12271227}
1228+
1229+#[test]
1230+fn ignored_suffix_arg() {
1231+ bat()
1232+ .arg("-f")
1233+ .arg("-p")
1234+ .arg("test.json~")
1235+ .assert()
1236+ .success()
1237+ .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")
1238+ .stderr("");
1239+
1240+ bat()
1241+ .arg("-f")
1242+ .arg("-p")
1243+ .arg("--ignored-suffix=.suffix")
1244+ .arg("test.json.suffix")
1245+ .assert()
1246+ .success()
1247+ .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")
1248+ .stderr("");
1249+
1250+ bat()
1251+ .arg("-f")
1252+ .arg("-p")
1253+ .arg("test.json.suffix")
1254+ .assert()
1255+ .success()
1256+ .stdout("\u{1b}[38;5;231m{\"test\": \"value\"}\u{1b}[0m")
1257+ .stderr("");
1258+}