on master help sign in
Implement syntax mapping

This adds a `-m`/`--map-syntax` option that allows users to (re)map certain file extensions or file names to an existing syntax. For example: ``` bat --map-syntax .config:json ``` The option can be use multiple times. Note that you can easily make these mappings permanent by using `bat`s new configuration file. closes #169

commit10965a6122e3ca6ad847871ec41eb01f0f595945 authorsharkdp <davidpeter@web.de> dateWed 17 Oct 2018, 22:30 (8 years ago) parente43d97d
6 files changed+80−3
src/app.rs+19 src/assets.rs+8−2 src/clap_app.rs+16 src/main.rs+1 src/printer.rs+1−1 src/syntax_mapping.rs+35
src/app.rs+19
@@ -19,6 +19,7 @@ use errors::*;
1919use inputfile::InputFile;
2020use line_range::LineRange;
2121use style::{OutputComponent, OutputComponents, OutputWrap};
22+use syntax_mapping::SyntaxMapping;
2223use util::transpose;
2324
2425#[derive(Debug, Clone, Copy, PartialEq)]
@@ -66,6 +67,9 @@ pub struct Config<'a> {
6667
6768 /// The syntax highlighting theme
6869 pub theme: String,
70+
71+ /// File extension/name mappings
72+ pub syntax_mapping: SyntaxMapping,
6973}
7074
7175fn is_truecolor_terminal() -> bool {
@@ -146,6 +150,20 @@ impl App {
146150 }
147151 };
148152
153+ let mut syntax_mapping = SyntaxMapping::new();
154+
155+ if let Some(values) = self.matches.values_of("map-syntax") {
156+ for from_to in values {
157+ let parts: Vec<_> = from_to.split(":").collect();
158+
159+ if parts.len() != 2 {
160+ return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is 'from:to'.".into());
161+ }
162+
163+ syntax_mapping.insert(parts[0].into(), parts[1].into());
164+ }
165+ }
166+
149167 Ok(Config {
150168 true_color: is_truecolor_terminal(),
151169 language: self.matches.value_of("language"),
@@ -202,6 +220,7 @@ impl App {
202220 .unwrap_or(String::from(BAT_THEME_DEFAULT)),
203221 line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
204222 output_components,
223+ syntax_mapping,
205224 })
206225 }
207226
src/assets.rs+8−2
@@ -12,6 +12,7 @@ use dirs::PROJECT_DIRS;
1212
1313use errors::*;
1414use inputfile::{InputFile, InputFileReader};
15+use syntax_mapping::SyntaxMapping;
1516
1617pub const BAT_THEME_DEFAULT: &str = "Monokai Extended";
1718
@@ -167,6 +168,7 @@ impl HighlightingAssets {
167168 language: Option<&str>,
168169 filename: InputFile,
169170 reader: &mut InputFileReader,
171+ mapping: &SyntaxMapping,
170172 ) -> &SyntaxReference {
171173 let syntax = match (language, filename) {
172174 (Some(language), _) => self.syntax_set.find_syntax_by_token(language),
@@ -174,10 +176,14 @@ impl HighlightingAssets {
174176 let path = Path::new(filename);
175177 let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
176178 let extension = path.extension().and_then(|x| x.to_str()).unwrap_or("");
179+
180+ let file_name = mapping.replace(file_name);
181+ let extension = mapping.replace(extension);
182+
177183 let ext_syntax = self
178184 .syntax_set
179− .find_syntax_by_extension(file_name)
180− .or_else(|| self.syntax_set.find_syntax_by_extension(extension));
185+ .find_syntax_by_extension(&file_name)
186+ .or_else(|| self.syntax_set.find_syntax_by_extension(&extension));
181187 let line_syntax = if ext_syntax.is_none() {
182188 String::from_utf8(reader.first_line.clone())
183189 .ok()
src/clap_app.rs+16
@@ -59,6 +59,22 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
5959 .help("Display all supported languages.")
6060 .long_help("Display a list of supported languages for syntax highlighting."),
6161 )
62+ .arg(
63+ Arg::with_name("map-syntax")
64+ .short("m")
65+ .long("map-syntax")
66+ .multiple(true)
67+ .takes_value(true)
68+ .value_name("from:to")
69+ .help("Map a file extension or name to an existing syntax")
70+ .long_help(
71+ "Map a file extension or file name to an existing syntax. For example, \
72+ to highlight *.conf files with the INI syntax, use '-m conf:ini'. \
73+ To highlight files named '.myignore' with the Git Ignore syntax, use \
74+ '-m .myignore:gitignore'.",
75+ )
76+ .takes_value(true),
77+ )
6278 .arg(
6379 Arg::with_name("theme")
6480 .long("theme")
src/main.rs+1
@@ -35,6 +35,7 @@ mod output;
3535mod preprocessor;
3636mod printer;
3737mod style;
38+mod syntax_mapping;
3839mod terminal;
3940mod util;
4041
src/printer.rs+1−1
@@ -142,7 +142,7 @@ impl<'a> InteractivePrinter<'a> {
142142 };
143143
144144 // Determine the type of syntax for highlighting
145− let syntax = assets.get_syntax(config.language, file, reader);
145+ let syntax = assets.get_syntax(config.language, file, reader, &config.syntax_mapping);
146146 Some(HighlightLines::new(syntax, theme))
147147 };
148148
src/syntax_mapping.rsnew file+35
@@ -0,0 +1,35 @@
1+use std::borrow::Cow;
2+use std::collections::HashMap;
3+
4+#[derive(Debug, Clone)]
5+pub struct SyntaxMapping(HashMap<String, String>);
6+
7+impl SyntaxMapping {
8+ pub fn new() -> SyntaxMapping {
9+ SyntaxMapping(HashMap::new())
10+ }
11+
12+ pub fn insert(&mut self, from: String, to: String) -> Option<String> {
13+ self.0.insert(from, to)
14+ }
15+
16+ pub fn replace<'a>(&self, input: &'a str) -> Cow<'a, str> {
17+ let mut out = Cow::from(input);
18+ if let Some(value) = self.0.get(input) {
19+ out = Cow::from(value.clone())
20+ }
21+ out
22+ }
23+}
24+
25+#[test]
26+fn basic() {
27+ let mut map = SyntaxMapping::new();
28+ map.insert("Cargo.lock".into(), "toml".into());
29+ map.insert(".ignore".into(), ".gitignore".into());
30+
31+ assert_eq!("toml", map.replace("Cargo.lock"));
32+ assert_eq!("other.lock", map.replace("other.lock"));
33+
34+ assert_eq!(".gitignore", map.replace(".ignore"));
35+}