Skip to content

Commit

Permalink
Add metadata information to cached assets
Browse files Browse the repository at this point in the history
When saving/reading user-provided syntaxes or themes, `bat` will now maintain a
`metadata.yaml` file which includes information about the `bat` version which was
used to create the cached files. When loading cached files, we now print an error
if they have been created with an incompatible version

closes #882
  • Loading branch information
sharkdp committed Apr 21, 2020
1 parent bcfc78f commit 2b84349
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 21 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@

## Other

- When saving/reading user-provided syntaxes or themes, `bat` will now maintain a
`metadata.yaml` file which includes information about the `bat` version which was
used to create the cached files. When loading cached files, we now print an error
if they have been created with an incompatible version. See #882
- Updated `liquid` dependency to 0.20, see #880 (@ignatenkobrain)

## `bat` as a library
Expand Down
36 changes: 36 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ encoding = "0.2"
shell-words = { version = "0.1.0", optional = true }
unicode-width = "0.1.7"
globset = "0.4"
serde = "1.0"
serde_yaml = "0.8"
semver = "0.9"

[dependencies.git2]
version = "0.13"
Expand Down
15 changes: 13 additions & 2 deletions src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use syntect::dumps::{dump_to_file, from_binary, from_reader};
use syntect::highlighting::{Theme, ThemeSet};
use syntect::parsing::{SyntaxReference, SyntaxSet, SyntaxSetBuilder};

use crate::assets_metadata::AssetsMetadata;
use crate::errors::*;
use crate::inputfile::{InputFile, InputFileReader};
use crate::syntax_mapping::{MappingTarget, SyntaxMapping};
Expand Down Expand Up @@ -68,8 +69,11 @@ impl HighlightingAssets {
})
}

pub fn from_cache(theme_set_path: &Path, syntax_set_path: &Path) -> Result<Self> {
let syntax_set_file = File::open(syntax_set_path).chain_err(|| {
pub fn from_cache(cache_path: &Path) -> Result<Self> {
let syntax_set_path = cache_path.join("syntaxes.bin");
let theme_set_path = cache_path.join("themes.bin");

let syntax_set_file = File::open(&syntax_set_path).chain_err(|| {
format!(
"Could not load cached syntax set '{}'",
syntax_set_path.to_string_lossy()
Expand Down Expand Up @@ -142,6 +146,13 @@ impl HighlightingAssets {
})?;
println!("okay");

print!(
"Writing metadata to folder {} ... ",
target_dir.to_string_lossy()
);
AssetsMetadata::new().save_to_folder(target_dir)?;
println!("okay");

Ok(())
}

Expand Down
82 changes: 82 additions & 0 deletions src/assets_metadata.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::fs::File;
use std::path::Path;
use std::time::SystemTime;

use clap::crate_version;
use semver::Version;
use serde::{Deserialize, Serialize};

use crate::errors::*;

#[derive(Debug, PartialEq, Default, Serialize, Deserialize)]
pub struct AssetsMetadata {
bat_version: Option<String>,
creation_time: Option<SystemTime>,
}

const FILENAME: &'static str = "metadata.yaml";

impl AssetsMetadata {
pub(crate) fn new() -> AssetsMetadata {
AssetsMetadata {
bat_version: Some(crate_version!().into()),
creation_time: Some(SystemTime::now()),
}
}

pub(crate) fn save_to_folder(&self, path: &Path) -> Result<()> {
let file = File::create(path.join(FILENAME))?;
serde_yaml::to_writer(file, self)?;

Ok(())
}

fn try_load_from_folder(path: &Path) -> Result<Self> {
let file = File::open(path.join(FILENAME))?;
Ok(serde_yaml::from_reader(file)?)
}

/// Load metadata about the stored cache file from the given folder.
///
/// There are several possibilities:
/// - We find a metadata.yaml file and are able to parse it
/// => return the contained information
/// - We find a metadata.yaml file and but are not able to parse it
/// => return a SerdeYamlError
/// - We do not find a metadata.yaml file but a syntaxes.bin or themes.bin file
/// => assume that these were created by an old version of bat and return
/// AssetsMetadata::default() without version information
/// - We do not find a metadata.yaml file and no cached assets
/// => no user provided assets are available, return None
pub fn load_from_folder(path: &Path) -> Result<Option<Self>> {
match Self::try_load_from_folder(path) {
Ok(metadata) => Ok(Some(metadata)),
Err(e) => match e.kind() {
ErrorKind::SerdeYamlError(_) => Err(e),
_ => {
if path.join("syntaxes.bin").exists() || path.join("themes.bin").exists() {
Ok(Some(Self::default()))
} else {
Ok(None)
}
}
},
}
}

pub fn is_compatible(&self) -> bool {
let current_version =
Version::parse(crate_version!()).expect("bat follows semantic versioning");
let stored_version = self
.bat_version
.as_ref()
.and_then(|ver| Version::parse(ver).ok());

if let Some(stored_version) = stored_version {
current_version.major == stored_version.major
&& current_version.minor == stored_version.minor
} else {
false
}
}
}
48 changes: 33 additions & 15 deletions src/bin/bat/assets.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
use std::borrow::Cow;
use std::fs;
use std::path::PathBuf;

use crate::directories::PROJECT_DIRS;

use bat::HighlightingAssets;
use clap::crate_version;

fn theme_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("themes.bin")
}
use crate::directories::PROJECT_DIRS;

fn syntax_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("syntaxes.bin")
}
use bat::errors::*;
use bat::{AssetsMetadata, HighlightingAssets};

pub fn config_dir() -> Cow<'static, str> {
PROJECT_DIRS.config_dir().to_string_lossy()
Expand All @@ -23,16 +17,40 @@ pub fn cache_dir() -> Cow<'static, str> {
}

pub fn clear_assets() {
let theme_set_path = PROJECT_DIRS.cache_dir().join("themes.bin");
let syntax_set_path = PROJECT_DIRS.cache_dir().join("syntaxes.bin");
let metadata_file = PROJECT_DIRS.cache_dir().join("metadata.yaml");

print!("Clearing theme set cache ... ");
fs::remove_file(theme_set_path()).ok();
fs::remove_file(theme_set_path).ok();
println!("okay");

print!("Clearing syntax set cache ... ");
fs::remove_file(syntax_set_path()).ok();
fs::remove_file(syntax_set_path).ok();
println!("okay");

print!("Clearing metadata file ... ");
fs::remove_file(metadata_file).ok();
println!("okay");
}

pub fn assets_from_cache_or_binary() -> HighlightingAssets {
HighlightingAssets::from_cache(&theme_set_path(), &syntax_set_path())
.unwrap_or(HighlightingAssets::from_binary())
pub fn assets_from_cache_or_binary() -> Result<HighlightingAssets> {
let cache_dir = PROJECT_DIRS.cache_dir();
if let Some(metadata) = AssetsMetadata::load_from_folder(&cache_dir)? {
if !metadata.is_compatible() {
return Err(format!(
"The binary caches for the user-customized syntaxes and themes \
in '{}' are not compatible with this version of bat ({}). To solve this, \
either rebuild the cache (bat cache --build) or remove \
the custom syntaxes/themes (bat cache --clear).\n\
For more information, see:\n\n \
https://github.com/sharkdp/bat#adding-new-syntaxes--language-definitions",
cache_dir.to_string_lossy(),
crate_version!()
)
.into());
}
}

Ok(HighlightingAssets::from_cache(&cache_dir).unwrap_or(HighlightingAssets::from_binary()))
}
6 changes: 3 additions & 3 deletions src/bin/bat/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
}

pub fn list_languages(config: &Config) -> Result<()> {
let assets = assets_from_cache_or_binary();
let assets = assets_from_cache_or_binary()?;
let mut languages = assets
.syntaxes()
.iter()
Expand Down Expand Up @@ -116,7 +116,7 @@ pub fn list_languages(config: &Config) -> Result<()> {
}

pub fn list_themes(cfg: &Config) -> Result<()> {
let assets = assets_from_cache_or_binary();
let assets = assets_from_cache_or_binary()?;
let mut config = cfg.clone();
let mut style = HashSet::new();
style.insert(StyleComponent::Plain);
Expand Down Expand Up @@ -147,7 +147,7 @@ pub fn list_themes(cfg: &Config) -> Result<()> {
}

fn run_controller(config: &Config) -> Result<bool> {
let assets = assets_from_cache_or_binary();
let assets = assets_from_cache_or_binary()?;
let controller = Controller::new(&config, &assets);
controller.run()
}
Expand Down
11 changes: 10 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,27 @@ error_chain! {
SyntectError(::syntect::LoadingError);
ParseIntError(::std::num::ParseIntError);
GlobParsingError(::globset::Error);
SerdeYamlError(::serde_yaml::Error);
}
}

pub fn default_error_handler(error: &Error) {
use ansi_term::Colour::Red;

match error {
Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == ::std::io::ErrorKind::BrokenPipe =>
{
::std::process::exit(0);
}
Error(ErrorKind::SerdeYamlError(_), _) => {
eprintln!(
"{}: Error while parsing metadata.yaml file: {}",
Red.paint("[bat error]"),
error
);
}
_ => {
use ansi_term::Colour::Red;
eprintln!("{}: {}", Red.paint("[bat error]"), error);
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![recursion_limit = "1024"]

pub(crate) mod assets;
pub(crate) mod assets_metadata;
pub mod config;
pub(crate) mod controller;
mod decorations;
Expand All @@ -19,5 +20,6 @@ mod terminal;
pub(crate) mod wrap;

pub use assets::HighlightingAssets;
pub use assets_metadata::AssetsMetadata;
pub use controller::Controller;
pub use printer::{InteractivePrinter, Printer, SimplePrinter};

0 comments on commit 2b84349

Please sign in to comment.