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
This commit is contained in:
sharkdp 2018-10-17 22:30:09 +02:00 committed by David Peter
parent e43d97dc15
commit 10965a6122
6 changed files with 80 additions and 3 deletions

View File

@ -19,6 +19,7 @@ use errors::*;
use inputfile::InputFile;
use line_range::LineRange;
use style::{OutputComponent, OutputComponents, OutputWrap};
use syntax_mapping::SyntaxMapping;
use util::transpose;
#[derive(Debug, Clone, Copy, PartialEq)]
@ -66,6 +67,9 @@ pub struct Config<'a> {
/// The syntax highlighting theme
pub theme: String,
/// File extension/name mappings
pub syntax_mapping: SyntaxMapping,
}
fn is_truecolor_terminal() -> bool {
@ -146,6 +150,20 @@ impl App {
}
};
let mut syntax_mapping = SyntaxMapping::new();
if let Some(values) = self.matches.values_of("map-syntax") {
for from_to in values {
let parts: Vec<_> = from_to.split(":").collect();
if parts.len() != 2 {
return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is 'from:to'.".into());
}
syntax_mapping.insert(parts[0].into(), parts[1].into());
}
}
Ok(Config {
true_color: is_truecolor_terminal(),
language: self.matches.value_of("language"),
@ -202,6 +220,7 @@ impl App {
.unwrap_or(String::from(BAT_THEME_DEFAULT)),
line_range: transpose(self.matches.value_of("line-range").map(LineRange::from))?,
output_components,
syntax_mapping,
})
}

View File

@ -12,6 +12,7 @@ use dirs::PROJECT_DIRS;
use errors::*;
use inputfile::{InputFile, InputFileReader};
use syntax_mapping::SyntaxMapping;
pub const BAT_THEME_DEFAULT: &str = "Monokai Extended";
@ -167,6 +168,7 @@ impl HighlightingAssets {
language: Option<&str>,
filename: InputFile,
reader: &mut InputFileReader,
mapping: &SyntaxMapping,
) -> &SyntaxReference {
let syntax = match (language, filename) {
(Some(language), _) => self.syntax_set.find_syntax_by_token(language),
@ -174,10 +176,14 @@ impl HighlightingAssets {
let path = Path::new(filename);
let file_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
let extension = path.extension().and_then(|x| x.to_str()).unwrap_or("");
let file_name = mapping.replace(file_name);
let extension = mapping.replace(extension);
let ext_syntax = self
.syntax_set
.find_syntax_by_extension(file_name)
.or_else(|| self.syntax_set.find_syntax_by_extension(extension));
.find_syntax_by_extension(&file_name)
.or_else(|| self.syntax_set.find_syntax_by_extension(&extension));
let line_syntax = if ext_syntax.is_none() {
String::from_utf8(reader.first_line.clone())
.ok()

View File

@ -59,6 +59,22 @@ 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("map-syntax")
.short("m")
.long("map-syntax")
.multiple(true)
.takes_value(true)
.value_name("from:to")
.help("Map a file extension or name to an existing syntax")
.long_help(
"Map a file extension or file name to an existing syntax. For example, \
to highlight *.conf files with the INI syntax, use '-m conf:ini'. \
To highlight files named '.myignore' with the Git Ignore syntax, use \
'-m .myignore:gitignore'.",
)
.takes_value(true),
)
.arg(
Arg::with_name("theme")
.long("theme")

View File

@ -35,6 +35,7 @@ mod output;
mod preprocessor;
mod printer;
mod style;
mod syntax_mapping;
mod terminal;
mod util;

View File

@ -142,7 +142,7 @@ impl<'a> InteractivePrinter<'a> {
};
// Determine the type of syntax for highlighting
let syntax = assets.get_syntax(config.language, file, reader);
let syntax = assets.get_syntax(config.language, file, reader, &config.syntax_mapping);
Some(HighlightLines::new(syntax, theme))
};

35
src/syntax_mapping.rs Normal file
View File

@ -0,0 +1,35 @@
use std::borrow::Cow;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct SyntaxMapping(HashMap<String, String>);
impl SyntaxMapping {
pub fn new() -> SyntaxMapping {
SyntaxMapping(HashMap::new())
}
pub fn insert(&mut self, from: String, to: String) -> Option<String> {
self.0.insert(from, to)
}
pub fn replace<'a>(&self, input: &'a str) -> Cow<'a, str> {
let mut out = Cow::from(input);
if let Some(value) = self.0.get(input) {
out = Cow::from(value.clone())
}
out
}
}
#[test]
fn basic() {
let mut map = SyntaxMapping::new();
map.insert("Cargo.lock".into(), "toml".into());
map.insert(".ignore".into(), ".gitignore".into());
assert_eq!("toml", map.replace("Cargo.lock"));
assert_eq!("other.lock", map.replace("other.lock"));
assert_eq!(".gitignore", map.replace(".ignore"));
}