Skip to content

Commit b6cecb5

Browse files
committed
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.
1 parent c0e0966 commit b6cecb5

6 files changed

Lines changed: 95 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
## `bat` as a library
2222

23+
- 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)
2324

2425

2526

src/assets.rs

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl HighlightingAssets {
128128
let _ = fs::create_dir_all(target_dir);
129129
asset_to_cache(&self.theme_set, &target_dir.join("themes.bin"), "theme set")?;
130130
asset_to_cache(
131-
self.get_syntax_set(),
131+
self.get_syntax_set()?,
132132
&target_dir.join("syntaxes.bin"),
133133
"syntax set",
134134
)?;
@@ -147,31 +147,51 @@ impl HighlightingAssets {
147147
self.fallback_theme = Some(theme);
148148
}
149149

150-
pub(crate) fn get_syntax_set(&self) -> &SyntaxSet {
151-
&self.syntax_set
150+
pub(crate) fn get_syntax_set(&self) -> Result<&SyntaxSet> {
151+
Ok(&self.syntax_set)
152152
}
153153

154+
/// Use [Self::get_syntaxes] instead
155+
#[deprecated]
154156
pub fn syntaxes(&self) -> &[SyntaxReference] {
155-
self.get_syntax_set().syntaxes()
157+
self.get_syntax_set()
158+
.expect(".syntaxes() is deprecated, use .get_syntaxes() instead")
159+
.syntaxes()
160+
}
161+
162+
pub fn get_syntaxes(&self) -> Result<&[SyntaxReference]> {
163+
Ok(self.get_syntax_set()?.syntaxes())
156164
}
157165

158166
pub fn themes(&self) -> impl Iterator<Item = &str> {
159167
self.theme_set.themes.keys().map(|s| s.as_ref())
160168
}
161169

170+
/// Use [Self::get_syntax_for_file_name] instead
171+
#[deprecated]
162172
pub fn syntax_for_file_name(
163173
&self,
164174
file_name: impl AsRef<Path>,
165175
mapping: &SyntaxMapping,
166176
) -> Option<&SyntaxReference> {
177+
self.get_syntax_for_file_name(file_name, mapping).expect(
178+
".syntax_for_file_name() is deprecated, use .get_syntax_for_file_name() instead",
179+
)
180+
}
181+
182+
pub fn get_syntax_for_file_name(
183+
&self,
184+
file_name: impl AsRef<Path>,
185+
mapping: &SyntaxMapping,
186+
) -> Result<Option<&SyntaxReference>> {
167187
let file_name = file_name.as_ref();
168-
match mapping.get_syntax_for(file_name) {
188+
Ok(match mapping.get_syntax_for(file_name) {
169189
Some(MappingTarget::MapToUnknown) => None,
170190
Some(MappingTarget::MapTo(syntax_name)) => {
171-
self.get_syntax_set().find_syntax_by_name(syntax_name)
191+
self.get_syntax_set()?.find_syntax_by_name(syntax_name)
172192
}
173-
None => self.get_extension_syntax(file_name.as_os_str()),
174-
}
193+
None => self.get_extension_syntax(file_name.as_os_str())?,
194+
})
175195
}
176196

177197
pub(crate) fn get_theme(&self, theme: &str) -> &Theme {
@@ -197,11 +217,11 @@ impl HighlightingAssets {
197217
mapping: &SyntaxMapping,
198218
) -> Result<&SyntaxReference> {
199219
if let Some(language) = language {
200-
self.get_syntax_set()
220+
self.get_syntax_set()?
201221
.find_syntax_by_token(language)
202222
.ok_or_else(|| ErrorKind::UnknownSyntax(language.to_owned()).into())
203223
} else {
204-
let line_syntax = self.get_first_line_syntax(&mut input.reader);
224+
let line_syntax = self.get_first_line_syntax(&mut input.reader)?;
205225

206226
// Get the path of the file:
207227
// If this was set by the metadata, that will take priority.
@@ -230,13 +250,13 @@ impl HighlightingAssets {
230250
}),
231251

232252
Some(MappingTarget::MapTo(syntax_name)) => self
233-
.get_syntax_set()
253+
.get_syntax_set()?
234254
.find_syntax_by_name(syntax_name)
235255
.ok_or_else(|| ErrorKind::UnknownSyntax(syntax_name.to_owned()).into()),
236256

237257
None => {
238258
let file_name = path.file_name().unwrap_or_default();
239-
self.get_extension_syntax(file_name)
259+
self.get_extension_syntax(file_name)?
240260
.or(line_syntax)
241261
.ok_or_else(|| {
242262
ErrorKind::UndetectedSyntax(path.to_string_lossy().into()).into()
@@ -250,49 +270,60 @@ impl HighlightingAssets {
250270
}
251271
}
252272

253-
fn get_extension_syntax(&self, file_name: &OsStr) -> Option<&SyntaxReference> {
254-
self.find_syntax_by_file_name(file_name).or_else(|| {
255-
self.find_syntax_by_file_name_extension(file_name)
256-
.or_else(|| self.get_extension_syntax_with_stripped_suffix(file_name))
257-
})
273+
fn get_extension_syntax(&self, file_name: &OsStr) -> Result<Option<&SyntaxReference>> {
274+
let mut syntax = self.find_syntax_by_file_name(file_name)?;
275+
if syntax.is_none() {
276+
syntax = self.find_syntax_by_file_name_extension(file_name)?;
277+
}
278+
if syntax.is_none() {
279+
syntax = self.get_extension_syntax_with_stripped_suffix(file_name)?;
280+
}
281+
Ok(syntax)
258282
}
259283

260-
fn find_syntax_by_file_name(&self, file_name: &OsStr) -> Option<&SyntaxReference> {
261-
self.get_syntax_set()
262-
.find_syntax_by_extension(file_name.to_str().unwrap_or_default())
284+
fn find_syntax_by_file_name(&self, file_name: &OsStr) -> Result<Option<&SyntaxReference>> {
285+
Ok(self
286+
.get_syntax_set()?
287+
.find_syntax_by_extension(file_name.to_str().unwrap_or_default()))
263288
}
264289

265-
fn find_syntax_by_file_name_extension(&self, file_name: &OsStr) -> Option<&SyntaxReference> {
290+
fn find_syntax_by_file_name_extension(
291+
&self,
292+
file_name: &OsStr,
293+
) -> Result<Option<&SyntaxReference>> {
266294
let file_path = Path::new(file_name);
267-
self.get_syntax_set().find_syntax_by_extension(
295+
Ok(self.get_syntax_set()?.find_syntax_by_extension(
268296
file_path
269297
.extension()
270298
.and_then(|x| x.to_str())
271299
.unwrap_or_default(),
272-
)
300+
))
273301
}
274302

275303
/// If we find an ignored suffix on the file name, e.g. '~', we strip it and
276304
/// then try again to find a syntax without it. Note that we do this recursively.
277305
fn get_extension_syntax_with_stripped_suffix(
278306
&self,
279307
file_name: &OsStr,
280-
) -> Option<&SyntaxReference> {
308+
) -> Result<Option<&SyntaxReference>> {
281309
let file_path = Path::new(file_name);
310+
let mut syntax = None;
282311
if let Some(file_str) = file_path.to_str() {
283312
for suffix in IGNORED_SUFFIXES.iter() {
284313
if let Some(stripped_filename) = file_str.strip_suffix(suffix) {
285-
return self.get_extension_syntax(OsStr::new(stripped_filename));
314+
syntax = self.get_extension_syntax(OsStr::new(stripped_filename))?;
315+
break;
286316
}
287317
}
288318
}
289-
None
319+
Ok(syntax)
290320
}
291321

292-
fn get_first_line_syntax(&self, reader: &mut InputReader) -> Option<&SyntaxReference> {
293-
String::from_utf8(reader.first_line.clone())
322+
fn get_first_line_syntax(&self, reader: &mut InputReader) -> Result<Option<&SyntaxReference>> {
323+
let syntax_set = self.get_syntax_set()?;
324+
Ok(String::from_utf8(reader.first_line.clone())
294325
.ok()
295-
.and_then(|l| self.get_syntax_set().find_syntax_by_first_line(&l))
326+
.and_then(|l| syntax_set.find_syntax_by_first_line(&l)))
296327
}
297328
}
298329

@@ -364,7 +395,12 @@ mod tests {
364395

365396
self.assets
366397
.get_syntax(None, &mut opened_input, &self.syntax_mapping)
367-
.unwrap_or_else(|_| self.assets.get_syntax_set().find_syntax_plain_text())
398+
.unwrap_or_else(|_| {
399+
self.assets
400+
.get_syntax_set()
401+
.expect("this is mod tests")
402+
.find_syntax_plain_text()
403+
})
368404
.name
369405
.clone()
370406
}
@@ -378,7 +414,12 @@ mod tests {
378414

379415
self.assets
380416
.get_syntax(None, &mut opened_input, &self.syntax_mapping)
381-
.unwrap_or_else(|_| self.assets.get_syntax_set().find_syntax_plain_text())
417+
.unwrap_or_else(|_| {
418+
self.assets
419+
.get_syntax_set()
420+
.expect("this is mod tests")
421+
.find_syntax_plain_text()
422+
})
382423
.name
383424
.clone()
384425
}
@@ -402,7 +443,12 @@ mod tests {
402443

403444
self.assets
404445
.get_syntax(None, &mut opened_input, &self.syntax_mapping)
405-
.unwrap_or_else(|_| self.assets.get_syntax_set().find_syntax_plain_text())
446+
.unwrap_or_else(|_| {
447+
self.assets
448+
.get_syntax_set()
449+
.expect("this is mod tests")
450+
.find_syntax_plain_text()
451+
})
406452
.name
407453
.clone()
408454
}
@@ -560,7 +606,11 @@ mod tests {
560606
assert_eq!(
561607
test.assets
562608
.get_syntax(None, &mut opened_input, &test.syntax_mapping)
563-
.unwrap_or_else(|_| test.assets.get_syntax_set().find_syntax_plain_text())
609+
.unwrap_or_else(|_| test
610+
.assets
611+
.get_syntax_set()
612+
.expect("this is mod tests")
613+
.find_syntax_plain_text())
564614
.name,
565615
"SSH Config"
566616
);

src/bin/bat/main.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub fn get_languages(config: &Config) -> Result<String> {
8282

8383
let assets = assets_from_cache_or_binary()?;
8484
let mut languages = assets
85-
.syntaxes()
85+
.get_syntaxes()?
8686
.iter()
8787
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
8888
.cloned()
@@ -101,7 +101,10 @@ pub fn get_languages(config: &Config) -> Result<String> {
101101
true
102102
} else {
103103
let test_file = Path::new("test").with_extension(extension);
104-
match assets.syntax_for_file_name(test_file, &config.syntax_mapping) {
104+
let syntax = assets
105+
.get_syntax_for_file_name(test_file, &config.syntax_mapping)
106+
.unwrap(); // safe since .get_syntaxes() above worked
107+
match syntax {
105108
Some(syntax) => syntax.name == lang_name,
106109
None => false,
107110
}

src/pretty_printer.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,9 @@ impl<'a> PrettyPrinter<'a> {
235235
}
236236

237237
pub fn syntaxes(&self) -> impl Iterator<Item = &SyntaxReference> {
238-
self.assets.syntaxes().iter()
238+
// We always use assets from the binary, which are guaranteed to always
239+
// be valid, so get_syntaxes() can never fail here
240+
self.assets.get_syntaxes().unwrap().iter()
239241
}
240242

241243
/// Pretty-print all specified inputs. This method will "use" all stored inputs.

src/printer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ impl<'a> InteractivePrinter<'a> {
174174
let syntax = match assets.get_syntax(config.language, input, &config.syntax_mapping) {
175175
Ok(syntax) => syntax,
176176
Err(Error(ErrorKind::UndetectedSyntax(_), _)) => {
177-
assets.get_syntax_set().find_syntax_plain_text()
177+
assets.get_syntax_set()?.find_syntax_plain_text()
178178
}
179179
Err(e) => return Err(e),
180180
};
@@ -192,7 +192,7 @@ impl<'a> InteractivePrinter<'a> {
192192
#[cfg(feature = "git")]
193193
line_changes,
194194
highlighter,
195-
syntax_set: assets.get_syntax_set(),
195+
syntax_set: assets.get_syntax_set()?,
196196
background_color_highlight,
197197
})
198198
}

tests/no_duplicate_extensions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ fn no_duplicate_extensions() {
2626

2727
let mut extensions = HashSet::new();
2828

29-
for syntax in assets.syntaxes() {
29+
for syntax in assets.get_syntaxes().expect("this is a #[test]") {
3030
for extension in &syntax.file_extensions {
3131
assert!(
3232
KNOWN_EXCEPTIONS.contains(&extension.as_str()) || extensions.insert(extension),

0 commit comments

Comments
 (0)