HighlightingAssets: Make .syntaxes() and syntax_for_file_name() failable

Or rather, introduce new versions of these methods and deprecate the old ones.

This is preparation to enable robust and user-friendly support for lazy-loading.
With lazy-loading, we don't know if the SyntaxSet is valid until after we try to
use it, so wherever we try to use it, we need to return a Result. See discussion
about panics in #1747.
This commit is contained in:
Martin Nordholts 2021-07-27 09:43:51 +02:00
parent c0e09662b4
commit a81009607a
6 changed files with 95 additions and 39 deletions

View File

@ -20,6 +20,7 @@
## `bat` as a library ## `bat` as a library
- Deprecate `HighlightingAssets::syntaxes()` and `HighlightingAssets::syntax_for_file_name()`. Use `HighlightingAssets::get_syntaxes()` and `HighlightingAssets::get_syntax_for_file_name()` instead. They return a `Result` which is needed for upcoming lazy-loading work to improve startup performance. See #1747 and #1755 (@Enselic)

View File

@ -128,7 +128,7 @@ impl HighlightingAssets {
let _ = fs::create_dir_all(target_dir); let _ = fs::create_dir_all(target_dir);
asset_to_cache(&self.theme_set, &target_dir.join("themes.bin"), "theme set")?; asset_to_cache(&self.theme_set, &target_dir.join("themes.bin"), "theme set")?;
asset_to_cache( asset_to_cache(
self.get_syntax_set(), self.get_syntax_set()?,
&target_dir.join("syntaxes.bin"), &target_dir.join("syntaxes.bin"),
"syntax set", "syntax set",
)?; )?;
@ -147,31 +147,51 @@ impl HighlightingAssets {
self.fallback_theme = Some(theme); self.fallback_theme = Some(theme);
} }
pub(crate) fn get_syntax_set(&self) -> &SyntaxSet { pub(crate) fn get_syntax_set(&self) -> Result<&SyntaxSet> {
&self.syntax_set Ok(&self.syntax_set)
} }
/// Use [Self::get_syntaxes] instead
#[deprecated]
pub fn syntaxes(&self) -> &[SyntaxReference] { pub fn syntaxes(&self) -> &[SyntaxReference] {
self.get_syntax_set().syntaxes() self.get_syntax_set()
.expect(".syntaxes() is deprecated, use .get_syntaxes() instead")
.syntaxes()
}
pub fn get_syntaxes(&self) -> Result<&[SyntaxReference]> {
Ok(self.get_syntax_set()?.syntaxes())
} }
pub fn themes(&self) -> impl Iterator<Item = &str> { pub fn themes(&self) -> impl Iterator<Item = &str> {
self.theme_set.themes.keys().map(|s| s.as_ref()) self.theme_set.themes.keys().map(|s| s.as_ref())
} }
/// Use [Self::get_syntax_for_file_name] instead
#[deprecated]
pub fn syntax_for_file_name( pub fn syntax_for_file_name(
&self, &self,
file_name: impl AsRef<Path>, file_name: impl AsRef<Path>,
mapping: &SyntaxMapping, mapping: &SyntaxMapping,
) -> Option<&SyntaxReference> { ) -> Option<&SyntaxReference> {
self.get_syntax_for_file_name(file_name, mapping).expect(
".syntax_for_file_name() is deprecated, use .get_syntax_for_file_name() instead",
)
}
pub fn get_syntax_for_file_name(
&self,
file_name: impl AsRef<Path>,
mapping: &SyntaxMapping,
) -> Result<Option<&SyntaxReference>> {
let file_name = file_name.as_ref(); let file_name = file_name.as_ref();
match mapping.get_syntax_for(file_name) { Ok(match mapping.get_syntax_for(file_name) {
Some(MappingTarget::MapToUnknown) => None, Some(MappingTarget::MapToUnknown) => None,
Some(MappingTarget::MapTo(syntax_name)) => { Some(MappingTarget::MapTo(syntax_name)) => {
self.get_syntax_set().find_syntax_by_name(syntax_name) self.get_syntax_set()?.find_syntax_by_name(syntax_name)
} }
None => self.get_extension_syntax(file_name.as_os_str()), None => self.get_extension_syntax(file_name.as_os_str())?,
} })
} }
pub(crate) fn get_theme(&self, theme: &str) -> &Theme { pub(crate) fn get_theme(&self, theme: &str) -> &Theme {
@ -197,11 +217,11 @@ impl HighlightingAssets {
mapping: &SyntaxMapping, mapping: &SyntaxMapping,
) -> Result<&SyntaxReference> { ) -> Result<&SyntaxReference> {
if let Some(language) = language { if let Some(language) = language {
self.get_syntax_set() self.get_syntax_set()?
.find_syntax_by_token(language) .find_syntax_by_token(language)
.ok_or_else(|| ErrorKind::UnknownSyntax(language.to_owned()).into()) .ok_or_else(|| ErrorKind::UnknownSyntax(language.to_owned()).into())
} else { } else {
let line_syntax = self.get_first_line_syntax(&mut input.reader); let line_syntax = self.get_first_line_syntax(&mut input.reader)?;
// Get the path of the file: // Get the path of the file:
// If this was set by the metadata, that will take priority. // If this was set by the metadata, that will take priority.
@ -230,13 +250,13 @@ impl HighlightingAssets {
}), }),
Some(MappingTarget::MapTo(syntax_name)) => self Some(MappingTarget::MapTo(syntax_name)) => self
.get_syntax_set() .get_syntax_set()?
.find_syntax_by_name(syntax_name) .find_syntax_by_name(syntax_name)
.ok_or_else(|| ErrorKind::UnknownSyntax(syntax_name.to_owned()).into()), .ok_or_else(|| ErrorKind::UnknownSyntax(syntax_name.to_owned()).into()),
None => { None => {
let file_name = path.file_name().unwrap_or_default(); let file_name = path.file_name().unwrap_or_default();
self.get_extension_syntax(file_name) self.get_extension_syntax(file_name)?
.or(line_syntax) .or(line_syntax)
.ok_or_else(|| { .ok_or_else(|| {
ErrorKind::UndetectedSyntax(path.to_string_lossy().into()).into() ErrorKind::UndetectedSyntax(path.to_string_lossy().into()).into()
@ -250,26 +270,34 @@ impl HighlightingAssets {
} }
} }
fn get_extension_syntax(&self, file_name: &OsStr) -> Option<&SyntaxReference> { fn get_extension_syntax(&self, file_name: &OsStr) -> Result<Option<&SyntaxReference>> {
self.find_syntax_by_file_name(file_name).or_else(|| { let mut syntax = self.find_syntax_by_file_name(file_name)?;
self.find_syntax_by_file_name_extension(file_name) if syntax.is_none() {
.or_else(|| self.get_extension_syntax_with_stripped_suffix(file_name)) syntax = self.find_syntax_by_file_name_extension(file_name)?;
}) }
if syntax.is_none() {
syntax = self.get_extension_syntax_with_stripped_suffix(file_name)?;
}
Ok(syntax)
} }
fn find_syntax_by_file_name(&self, file_name: &OsStr) -> Option<&SyntaxReference> { fn find_syntax_by_file_name(&self, file_name: &OsStr) -> Result<Option<&SyntaxReference>> {
self.get_syntax_set() Ok(self
.find_syntax_by_extension(file_name.to_str().unwrap_or_default()) .get_syntax_set()?
.find_syntax_by_extension(file_name.to_str().unwrap_or_default()))
} }
fn find_syntax_by_file_name_extension(&self, file_name: &OsStr) -> Option<&SyntaxReference> { fn find_syntax_by_file_name_extension(
&self,
file_name: &OsStr,
) -> Result<Option<&SyntaxReference>> {
let file_path = Path::new(file_name); let file_path = Path::new(file_name);
self.get_syntax_set().find_syntax_by_extension( Ok(self.get_syntax_set()?.find_syntax_by_extension(
file_path file_path
.extension() .extension()
.and_then(|x| x.to_str()) .and_then(|x| x.to_str())
.unwrap_or_default(), .unwrap_or_default(),
) ))
} }
/// If we find an ignored suffix on the file name, e.g. '~', we strip it and /// If we find an ignored suffix on the file name, e.g. '~', we strip it and
@ -277,22 +305,25 @@ impl HighlightingAssets {
fn get_extension_syntax_with_stripped_suffix( fn get_extension_syntax_with_stripped_suffix(
&self, &self,
file_name: &OsStr, file_name: &OsStr,
) -> Option<&SyntaxReference> { ) -> Result<Option<&SyntaxReference>> {
let file_path = Path::new(file_name); let file_path = Path::new(file_name);
let mut syntax = None;
if let Some(file_str) = file_path.to_str() { if let Some(file_str) = file_path.to_str() {
for suffix in IGNORED_SUFFIXES.iter() { for suffix in IGNORED_SUFFIXES.iter() {
if let Some(stripped_filename) = file_str.strip_suffix(suffix) { if let Some(stripped_filename) = file_str.strip_suffix(suffix) {
return self.get_extension_syntax(OsStr::new(stripped_filename)); syntax = self.get_extension_syntax(OsStr::new(stripped_filename))?;
break;
} }
} }
} }
None Ok(syntax)
} }
fn get_first_line_syntax(&self, reader: &mut InputReader) -> Option<&SyntaxReference> { fn get_first_line_syntax(&self, reader: &mut InputReader) -> Result<Option<&SyntaxReference>> {
String::from_utf8(reader.first_line.clone()) let syntax_set = self.get_syntax_set()?;
Ok(String::from_utf8(reader.first_line.clone())
.ok() .ok()
.and_then(|l| self.get_syntax_set().find_syntax_by_first_line(&l)) .and_then(|l| syntax_set.find_syntax_by_first_line(&l)))
} }
} }
@ -364,7 +395,12 @@ mod tests {
self.assets self.assets
.get_syntax(None, &mut opened_input, &self.syntax_mapping) .get_syntax(None, &mut opened_input, &self.syntax_mapping)
.unwrap_or_else(|_| self.assets.get_syntax_set().find_syntax_plain_text()) .unwrap_or_else(|_| {
self.assets
.get_syntax_set()
.expect("this is mod tests")
.find_syntax_plain_text()
})
.name .name
.clone() .clone()
} }
@ -378,7 +414,12 @@ mod tests {
self.assets self.assets
.get_syntax(None, &mut opened_input, &self.syntax_mapping) .get_syntax(None, &mut opened_input, &self.syntax_mapping)
.unwrap_or_else(|_| self.assets.get_syntax_set().find_syntax_plain_text()) .unwrap_or_else(|_| {
self.assets
.get_syntax_set()
.expect("this is mod tests")
.find_syntax_plain_text()
})
.name .name
.clone() .clone()
} }
@ -402,7 +443,12 @@ mod tests {
self.assets self.assets
.get_syntax(None, &mut opened_input, &self.syntax_mapping) .get_syntax(None, &mut opened_input, &self.syntax_mapping)
.unwrap_or_else(|_| self.assets.get_syntax_set().find_syntax_plain_text()) .unwrap_or_else(|_| {
self.assets
.get_syntax_set()
.expect("this is mod tests")
.find_syntax_plain_text()
})
.name .name
.clone() .clone()
} }
@ -560,7 +606,11 @@ mod tests {
assert_eq!( assert_eq!(
test.assets test.assets
.get_syntax(None, &mut opened_input, &test.syntax_mapping) .get_syntax(None, &mut opened_input, &test.syntax_mapping)
.unwrap_or_else(|_| test.assets.get_syntax_set().find_syntax_plain_text()) .unwrap_or_else(|_| test
.assets
.get_syntax_set()
.expect("this is mod tests")
.find_syntax_plain_text())
.name, .name,
"SSH Config" "SSH Config"
); );

View File

@ -82,7 +82,7 @@ pub fn get_languages(config: &Config) -> Result<String> {
let assets = assets_from_cache_or_binary()?; let assets = assets_from_cache_or_binary()?;
let mut languages = assets let mut languages = assets
.syntaxes() .get_syntaxes()?
.iter() .iter()
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty()) .filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
.cloned() .cloned()
@ -101,7 +101,10 @@ pub fn get_languages(config: &Config) -> Result<String> {
true true
} else { } else {
let test_file = Path::new("test").with_extension(extension); let test_file = Path::new("test").with_extension(extension);
match assets.syntax_for_file_name(test_file, &config.syntax_mapping) { let syntax = assets
.get_syntax_for_file_name(test_file, &config.syntax_mapping)
.unwrap(); // safe since .get_syntaxes() above worked
match syntax {
Some(syntax) => syntax.name == lang_name, Some(syntax) => syntax.name == lang_name,
None => false, None => false,
} }

View File

@ -235,7 +235,9 @@ impl<'a> PrettyPrinter<'a> {
} }
pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> { pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> {
self.assets.syntaxes().iter() // We always use assets from the binary, which are guaranteed to always
// be valid, so get_syntaxes() can never fail here
self.assets.get_syntaxes().unwrap().iter()
} }
/// Pretty-print all specified inputs. This method will "use" all stored inputs. /// Pretty-print all specified inputs. This method will "use" all stored inputs.

View File

@ -174,7 +174,7 @@ impl<'a> InteractivePrinter<'a> {
let syntax = match assets.get_syntax(config.language, input, &config.syntax_mapping) { let syntax = match assets.get_syntax(config.language, input, &config.syntax_mapping) {
Ok(syntax) => syntax, Ok(syntax) => syntax,
Err(Error(ErrorKind::UndetectedSyntax(_), _)) => { Err(Error(ErrorKind::UndetectedSyntax(_), _)) => {
assets.get_syntax_set().find_syntax_plain_text() assets.get_syntax_set()?.find_syntax_plain_text()
} }
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
@ -192,7 +192,7 @@ impl<'a> InteractivePrinter<'a> {
#[cfg(feature = "git")] #[cfg(feature = "git")]
line_changes, line_changes,
highlighter, highlighter,
syntax_set: assets.get_syntax_set(), syntax_set: assets.get_syntax_set()?,
background_color_highlight, background_color_highlight,
}) })
} }

View File

@ -26,7 +26,7 @@ fn no_duplicate_extensions() {
let mut extensions = HashSet::new(); let mut extensions = HashSet::new();
for syntax in assets.syntaxes() { for syntax in assets.get_syntaxes().expect("this is a #[test]") {
for extension in &syntax.file_extensions { for extension in &syntax.file_extensions {
assert!( assert!(
KNOWN_EXCEPTIONS.contains(&extension.as_str()) || extensions.insert(extension), KNOWN_EXCEPTIONS.contains(&extension.as_str()) || extensions.insert(extension),