Repository: dtolnay/cargo-expand (language: rust)

Issue:
feat: add Catppuccin themes
Hi :)

I tried looking at the code base to PR this myself but could not understand where themes are.

Would it be possible to add and support https://github.com/catppuccin/bat ?

Please and thank you


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0386034..b477924 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -77,5 +77,6 @@ jobs:
       - uses: actions/checkout@v4
       - uses: dtolnay/rust-toolchain@stable
       - uses: dtolnay/install@cargo-outdated
+      - run: cargo tree --package bat --depth 0 | grep "^bat v$(cat src/bat.version)$"
       - run: cargo update
       - run: cargo outdated --workspace --exit-code 1
diff --git a/Cargo.lock b/Cargo.lock
index ae36d7b..a77f246 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -184,10 +184,13 @@ dependencies = [
  "cargo-subcommand-metadata",
  "clap",
  "console",
+ "etcetera",
  "fs-err",
+ "home",
  "prettyplease",
  "proc-macro2",
  "quote",
+ "semver",
  "serde",
  "shlex",
  "syn",
@@ -343,6 +346,17 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "etcetera"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
+dependencies = [
+ "cfg-if",
+ "home",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "fancy-regex"
 version = "0.11.0"
diff --git a/Cargo.toml b/Cargo.toml
index f100242..b169f25 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,10 +21,13 @@ bat = { version = "0.24", default-features = false, features = ["paging", "regex
 cargo-subcommand-metadata = "0.1"
 clap = { version = "4", features = ["deprecated", "derive"] }
 console = "0.15"
+etcetera = "0.8"
 fs-err = "3"
+home = "0.5"
 prettyplease = { version = "0.2.25", features = ["verbatim"] }
 proc-macro2 = "1.0.80"
 quote = { version = "1.0.35", default-features = false }
+semver = "1"
 serde = { version = "1.0.183", features = ["derive"] }
 shlex = "1.3"
 syn = { version = "2.0.85", default-features = false, features = ["clone-impls", "fold", "full", "parsing", "printing", "visit-mut"] }
diff --git a/src/assets.rs b/src/assets.rs
new file mode 100644
index 0000000..67910e5
--- /dev/null
+++ b/src/assets.rs
@@ -0,0 +1,26 @@
+use crate::error::Result;
+use etcetera::BaseStrategy as _;
+use std::env;
+use std::path::PathBuf;
+use std::str;
+
+pub const BAT_VERSION: &str = {
+    let mut bytes = include_str!("bat.version").as_bytes();
+    while let [rest @ .., b'\n' | b'\r'] = bytes {
+        bytes = rest;
+    }
+    if let Ok(version) = str::from_utf8(bytes) {
+        version
+    } else {
+        panic!()
+    }
+};
+
+pub fn cache_dir() -> Result<PathBuf> {
+    if let Some(cache_dir) = env::var_os("BAT_CACHE_PATH") {
+        return Ok(PathBuf::from(cache_dir));
+    }
+
+    let basedirs = etcetera::choose_base_strategy()?;
+    Ok(basedirs.cache_dir().join("bat"))
+}
diff --git a/src/bat.version b/src/bat.version
new file mode 100644
index 0000000..2094a10
--- /dev/null
+++ b/src/bat.version
@@ -0,0 +1,1 @@
+0.24.0
diff --git a/src/error.rs b/src/error.rs
index 5538d52..ffda7b2 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -8,6 +8,8 @@ pub enum Error {
     TomlSer(toml::ser::Error),
     TomlDe(toml::de::Error),
     Quote(shlex::QuoteError),
+    HomeDir(etcetera::HomeDirError),
+    Bat(bat::error::Error),
 }
 
 pub type Result<T> = std::result::Result<T, Error>;
@@ -36,6 +38,18 @@ impl From<shlex::QuoteError> for Error {
     }
 }
 
+impl From<etcetera::HomeDirError> for Error {
+    fn from(error: etcetera::HomeDirError) -> Self {
+        Error::HomeDir(error)
+    }
+}
+
+impl From<bat::error::Error> for Error {
+    fn from(error: bat::error::Error) -> Self {
+        Error::Bat(error)
+    }
+}
+
 impl Display for Error {
     fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
         match self {
@@ -43,6 +57,8 @@ impl Display for Error {
             Error::TomlSer(e) => e.fmt(formatter),
             Error::TomlDe(e) => e.fmt(formatter),
             Error::Quote(e) => e.fmt(formatter),
+            Error::HomeDir(e) => e.fmt(formatter),
+            Error::Bat(e) => e.fmt(formatter),
         }
     }
 }
@@ -54,6 +70,8 @@ impl StdError for Error {
             Error::TomlSer(e) => e.source(),
             Error::TomlDe(e) => e.source(),
             Error::Quote(e) => e.source(),
+            Error::HomeDir(e) => e.source(),
+            Error::Bat(e) => e.source(),
         }
     }
 }
diff --git a/src/main.rs b/src/main.rs
index 95df1f5..62ab8c2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -16,6 +16,7 @@
     clippy::uninlined_format_args,
 )]
 
+mod assets;
 mod cmd;
 mod config;
 mod edit;
@@ -33,6 +34,7 @@ use crate::opts::{Coloring, Expand, Subcommand};
 use crate::unparse::unparse_maximal;
 use crate::version::Version;
 use bat::assets::HighlightingAssets;
+use bat::assets_metadata::AssetsMetadata;
 use bat::config::VisibleLines;
 use bat::line_range::{HighlightedLineRanges, LineRanges};
 use bat::style::StyleComponents;
@@ -138,9 +140,7 @@ fn do_cargo_expand() -> Result<i32> {
     let config = config::deserialize();
 
     if args.themes {
-        for theme in HighlightingAssets::from_binary().themes() {
-            let _ = writeln!(io::stdout(), "{}", theme);
-        }
+        print_themes()?;
         return Ok(0);
     }
 
@@ -290,6 +290,20 @@ fn do_cargo_expand() -> Result<i32> {
     };
     let _ = writeln!(io::stderr());
     if do_color {
+        let mut assets = HighlightingAssets::from_binary();
+        if let Some(requested_theme) = &theme {
+            if !assets
+                .themes()
+                .any(|supported_theme| supported_theme == requested_theme)
+            {
+                let cache_dir = assets::cache_dir()?;
+                if let Some(metadata) = AssetsMetadata::load_from_folder(&cache_dir)? {
+                    if metadata.is_compatible_with(assets::BAT_VERSION) {
+                        assets = HighlightingAssets::from_cache(&cache_dir)?;
+                    }
+                }
+            }
+        }
         let config = bat::config::Config {
             language: Some("rust"),
             show_nonprintable: false,
@@ -310,10 +324,8 @@ fn do_cargo_expand() -> Result<i32> {
             pager: None,
             use_italic_text: false,
             highlighted_lines: HighlightedLineRanges(LineRanges::none()),
-            use_custom_assets: false,
             ..Default::default()
         };
-        let assets = HighlightingAssets::from_binary();
         let controller = bat::controller::Controller::new(&config, &assets);
         let inputs = vec![bat::input::Input::from_reader(Box::new(content.as_bytes()))];
         // Ignore any errors.
@@ -657,3 +669,40 @@ fn get_color(args: &Expand, config: &Config) -> Coloring {
 
     Coloring::Auto // default
 }
+
+fn print_themes() -> Result<()> {
+    let mut cache_dir = assets::cache_dir()?;
+    let metadata = AssetsMetadata::load_from_folder(&cache_dir)?;
+    let compatible = metadata
+        .as_ref()
+        .map_or(false, |m| m.is_compatible_with(assets::BAT_VERSION));
+    let assets = if compatible {
+        HighlightingAssets::from_cache(&cache_dir)?
+    } else {
+        HighlightingAssets::from_binary()
+    };
+
+    for theme in assets.themes() {
+        let _ = writeln!(io::stdout(), "{}", theme);
+    }
+
+    if metadata.is_some() && !compatible {
+        if let Some(home_dir) = home::home_dir() {
+            if let Ok(relative) = cache_dir.strip_prefix(home_dir) {
+                cache_dir = Path::new("~").join(relative);
+            }
+        }
+        let bat_version = semver::Version::parse(assets::BAT_VERSION).unwrap();
+        let _ = writeln!(
+            io::stderr(),
+            "\nThere may be other themes in {cache_dir} but they are not \
+             compatible with the version of bat built into cargo-expand. Run \
+             `bat cache --build` with bat v{major}.{minor} to update the cache.",
+            cache_dir = cache_dir.display(),
+            major = bat_version.major,
+            minor = bat_version.minor,
+        );
+    }
+
+    Ok(())
+}


Test patch: