on master help sign in
Merge pull request #985 from eth-p/string-input

Unify syntax detection for all variants of InputKind, fix #983

commitd08d1f8c4599e96a96a821499aeb46383619ce89 authorEthan P <32112321+eth-p@users.noreply.github.com> dateFri 15 May 2020, 13:57 (6 years ago) parentc2c7070403ab6e
5 files changed+139−33
CHANGELOG.md+1 src/assets.rs+91−33 src/input.rs+9 tests/examples/regression_tests/issue_985.js+5 tests/integration_tests.rs+33
CHANGELOG.md+1
@@ -7,6 +7,7 @@
77## Bugfixes
88
99- bat mishighlights Users that start with digits in SSH config, see #984
10+- `--map-syntax` doesn't work with names provided through `--file-name` (@eth-p)
1011
1112## Other
1213## New syntaxes
src/assets.rs+91−33
@@ -191,42 +191,46 @@ impl HighlightingAssets {
191191 input: &mut OpenedInput,
192192 mapping: &SyntaxMapping,
193193 ) -> &SyntaxReference {
194− let syntax = if let Some(language) = language {
194+ let syntax = if input.kind.is_theme_preview_file() {
195+ self.syntax_set.find_syntax_by_name("Rust")
196+ } else if let Some(language) = language {
195197 self.syntax_set.find_syntax_by_token(language)
196198 } else {
197− match input.kind {
198− OpenedInputKind::OrdinaryFile(ref actual_path) => {
199− let path_str = input
200− .metadata
201− .user_provided_name
202− .as_ref()
203− .unwrap_or(actual_path);
204− let path = Path::new(path_str);
205− let line_syntax = self.get_first_line_syntax(&mut input.reader);
206−
207− let absolute_path = path.canonicalize().ok().unwrap_or_else(|| path.to_owned());
208− match mapping.get_syntax_for(absolute_path) {
209− Some(MappingTarget::MapTo(syntax_name)) => {
210− // TODO: we should probably return an error here if this syntax can not be
211− // found. Currently, we just fall back to 'plain'.
212− self.syntax_set.find_syntax_by_name(syntax_name)
213− }
214− Some(MappingTarget::MapToUnknown) => line_syntax,
215− None => {
216− let file_name = path.file_name().unwrap_or_default();
217− self.get_extension_syntax(file_name).or(line_syntax)
218− }
199+ let line_syntax = self.get_first_line_syntax(&mut input.reader);
200+
201+ // Get the path of the file:
202+ // If this was set by the metadata, that will take priority.
203+ // If it wasn't, it will use the real file path (if available).
204+ let path_str =
205+ input
206+ .metadata
207+ .user_provided_name
208+ .as_ref()
209+ .or_else(|| match input.kind {
210+ OpenedInputKind::OrdinaryFile(ref path) => Some(path),
211+ _ => None,
212+ });
213+
214+ if let Some(path_str) = path_str {
215+ // If a path was provided, we try and detect the syntax based on extension mappings.
216+ let path = Path::new(path_str);
217+ let absolute_path = path.canonicalize().ok().unwrap_or_else(|| path.to_owned());
218+
219+ match mapping.get_syntax_for(absolute_path) {
220+ Some(MappingTarget::MapToUnknown) => line_syntax,
221+ Some(MappingTarget::MapTo(syntax_name)) => {
222+ // TODO: we should probably return an error here if this syntax can not be
223+ // found. Currently, we just fall back to 'plain'.
224+ self.syntax_set.find_syntax_by_name(syntax_name)
219225 }
220− }
221− OpenedInputKind::StdIn | OpenedInputKind::CustomReader => {
222− if let Some(ref name) = input.metadata.user_provided_name {
223− self.get_extension_syntax(&name)
224− .or_else(|| self.get_first_line_syntax(&mut input.reader))
225− } else {
226− self.get_first_line_syntax(&mut input.reader)
226+ None => {
227+ let file_name = path.file_name().unwrap_or_default();
228+ self.get_extension_syntax(file_name).or(line_syntax)
227229 }
228230 }
229− OpenedInputKind::ThemePreviewFile => self.syntax_set.find_syntax_by_name("Rust"),
231+ } else {
232+ // If a path wasn't provided, we fall back to the detect first-line syntax.
233+ line_syntax
230234 }
231235 };
232236
@@ -258,9 +262,9 @@ mod tests {
258262 use super::*;
259263
260264 use std::ffi::OsStr;
265+
261266 use std::fs::File;
262267 use std::io::Write;
263−
264268 use tempdir::TempDir;
265269
266270 use crate::input::Input;
@@ -281,7 +285,11 @@ mod tests {
281285 }
282286 }
283287
284− fn syntax_for_file_with_content_os(&self, file_name: &OsStr, first_line: &str) -> String {
288+ fn syntax_for_real_file_with_content_os(
289+ &self,
290+ file_name: &OsStr,
291+ first_line: &str,
292+ ) -> String {
285293 let file_path = self.temp_dir.path().join(file_name);
286294 {
287295 let mut temp_file = File::create(&file_path).unwrap();
@@ -298,6 +306,19 @@ mod tests {
298306 syntax.name.clone()
299307 }
300308
309+ fn syntax_for_file_with_content_os(&self, file_name: &OsStr, first_line: &str) -> String {
310+ let file_path = self.temp_dir.path().join(file_name);
311+ let input = Input::from_reader(Box::new(BufReader::new(first_line.as_bytes())))
312+ .with_name(Some(file_path.as_os_str()));
313+ let dummy_stdin: &[u8] = &[];
314+ let mut opened_input = input.open(dummy_stdin).unwrap();
315+ let syntax = self
316+ .assets
317+ .get_syntax(None, &mut opened_input, &self.syntax_mapping);
318+
319+ syntax.name.clone()
320+ }
321+
301322 fn syntax_for_file_os(&self, file_name: &OsStr) -> String {
302323 self.syntax_for_file_with_content_os(file_name, "")
303324 }
@@ -319,6 +340,22 @@ mod tests {
319340 .get_syntax(None, &mut opened_input, &self.syntax_mapping);
320341 syntax.name.clone()
321342 }
343+
344+ fn syntax_is_same_for_inputkinds(&self, file_name: &str, content: &str) -> bool {
345+ let as_file = self.syntax_for_real_file_with_content_os(file_name.as_ref(), content);
346+ let as_reader = self.syntax_for_file_with_content_os(file_name.as_ref(), content);
347+ let consistent = as_file == as_reader;
348+ // TODO: Compare StdIn somehow?
349+
350+ if !consistent {
351+ eprintln!(
352+ "Inconsistent syntax detection:\nFor File: {}\nFor Reader: {}",
353+ as_file, as_reader
354+ )
355+ }
356+
357+ consistent
358+ }
322359 }
323360
324361 #[test]
@@ -349,6 +386,27 @@ mod tests {
349386 );
350387 }
351388
389+ #[test]
390+ fn syntax_detection_same_for_inputkinds() {
391+ let mut test = SyntaxDetectionTest::new();
392+
393+ test.syntax_mapping
394+ .insert("*.myext", MappingTarget::MapTo("C"))
395+ .ok();
396+ test.syntax_mapping
397+ .insert("MY_FILE", MappingTarget::MapTo("Markdown"))
398+ .ok();
399+
400+ assert!(test.syntax_is_same_for_inputkinds("Test.md", ""));
401+ assert!(test.syntax_is_same_for_inputkinds("Test.txt", "#!/bin/bash"));
402+ assert!(test.syntax_is_same_for_inputkinds(".bashrc", ""));
403+ assert!(test.syntax_is_same_for_inputkinds("test.h", ""));
404+ assert!(test.syntax_is_same_for_inputkinds("test.js", "#!/bin/bash"));
405+ assert!(test.syntax_is_same_for_inputkinds("test.myext", ""));
406+ assert!(test.syntax_is_same_for_inputkinds("MY_FILE", ""));
407+ assert!(test.syntax_is_same_for_inputkinds("MY_FILE", "<?php"));
408+ }
409+
352410 #[test]
353411 fn syntax_detection_well_defined_mapping_for_duplicate_extensions() {
354412 let test = SyntaxDetectionTest::new();
src/input.rs+9
@@ -39,6 +39,15 @@ pub(crate) enum OpenedInputKind {
3939 CustomReader,
4040}
4141
42+impl OpenedInputKind {
43+ pub(crate) fn is_theme_preview_file(&self) -> bool {
44+ match self {
45+ OpenedInputKind::ThemePreviewFile => true,
46+ _ => false,
47+ }
48+ }
49+}
50+
4251pub(crate) struct OpenedInput<'a> {
4352 pub(crate) kind: OpenedInputKind,
4453 pub(crate) metadata: InputMetadata,
tests/examples/regression_tests/issue_985.jsnew file+5
@@ -0,0 +1,5 @@
1+// This test should be considered a failure if the detected syntax differs between the following two commands:
2+/*
3+# bat --map-syntax '*.js:Markdown' --file-name 'issue_985.js' < issue_985.js
4+# bat --map-syntax '*.js:Markdown' --file-name 'issue_985.js' issue_985.js
5+*/
tests/integration_tests.rs+33
@@ -1,4 +1,8 @@
11use assert_cmd::Command;
2+use std::path::Path;
3+use std::str::from_utf8;
4+
5+const EXAMPLES_DIR: &str = "tests/examples";
26
37fn bat_with_config() -> Command {
48 let mut cmd = Command::cargo_bin("bat").unwrap();
@@ -682,3 +686,32 @@ fn do_not_panic_regression_tests() {
682686 .success();
683687 }
684688}
689+
690+#[test]
691+fn do_not_detect_different_syntax_for_stdin_and_files() {
692+ let file = "regression_tests/issue_985.js";
693+
694+ let cmd_for_file = bat()
695+ .arg("--color=always")
696+ .arg("--map-syntax=*.js:Markdown")
697+ .arg(&format!("--file-name={}", file))
698+ .arg("--style=plain")
699+ .arg(file)
700+ .assert()
701+ .success();
702+
703+ let cmd_for_stdin = bat()
704+ .arg("--color=always")
705+ .arg("--map-syntax=*.js:Markdown")
706+ .arg("--style=plain")
707+ .arg(&format!("--file-name={}", file))
708+ .pipe_stdin(Path::new(EXAMPLES_DIR).join(file))
709+ .unwrap()
710+ .assert()
711+ .success();
712+
713+ assert_eq!(
714+ from_utf8(&cmd_for_file.get_output().stdout).expect("output is valid utf-8"),
715+ from_utf8(&cmd_for_stdin.get_output().stdout).expect("output is valid utf-8")
716+ );
717+}