mirror of
https://github.com/sharkdp/bat.git
synced 2024-11-27 02:03:52 +01:00
Credit syntax definition and theme authors with new --acknowledgements
option (#1971)
The text that is printed is generated when building assets, by analyzing LICENSE and NOTICE files that comes with syntaxes and themes. We take this opportunity to also add a NOTICE file as defined by Apache License 2.0.
This commit is contained in:
parent
63ad53817d
commit
a3ea798246
@ -6,6 +6,7 @@
|
||||
- Support for `--ignored-suffix` argument. See #1892 (@bojan88)
|
||||
- `$BAT_CONFIG_DIR` is now a recognized environment variable. It has precedence over `$XDG_CONFIG_HOME`, see #1727 (@billrisher)
|
||||
- Support for `x:+delta` syntax in line ranges (e.g. `20:+10`). See #1810 (@bojan88)
|
||||
- Add new `--acknowledgements` option that gives credit to theme and syntax definition authors. See #1971 (@Enselic)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
@ -47,7 +48,7 @@
|
||||
## `bat` as a library
|
||||
|
||||
- Deprecate `HighlightingAssets::syntaxes()` and `HighlightingAssets::syntax_for_file_name()`. Use `HighlightingAssets::get_syntaxes()` and `HighlightingAssets::get_syntax_for_path()` instead. They return a `Result` which is needed for upcoming lazy-loading work to improve startup performance. They also return which `SyntaxSet` the returned `SyntaxReference` belongs to. See #1747, #1755, #1776, #1862 (@Enselic)
|
||||
- Remove `HighlightingAssets::from_files` and `HighlightingAssets::save_to_cache`. Instead of calling the former and then the latter you now make a single call to `bat::assets::build`. See #1802 (@Enselic)
|
||||
- Remove `HighlightingAssets::from_files` and `HighlightingAssets::save_to_cache`. Instead of calling the former and then the latter you now make a single call to `bat::assets::build`. See #1802, #1971 (@Enselic)
|
||||
- Replace the `error::Error(error::ErrorKind, _)` struct and enum with an `error::Error` enum. `Error(ErrorKind::UnknownSyntax, _)` becomes `Error::UnknownSyntax`, etc. Also remove the `error::ResultExt` trait. These changes stem from replacing `error-chain` with `thiserror`. See #1820 (@Enselic)
|
||||
- Add new `MappingTarget` enum variant `MapExtensionToUnknown`. Refer to its docummentation for more information. Clients are adviced to treat `MapExtensionToUnknown` the same as `MapToUnknown` in exhaustive matches. See #1703 (@cbolgiano)
|
||||
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -96,6 +96,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"path_abs",
|
||||
"predicates",
|
||||
"regex",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
@ -106,6 +107,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"unicode-width",
|
||||
"wait-timeout",
|
||||
"walkdir",
|
||||
"wild",
|
||||
]
|
||||
|
||||
|
@ -34,7 +34,7 @@ minimal-application = [
|
||||
git = ["git2"] # Support indicating git modifications
|
||||
paging = ["shell-words", "grep-cli"] # Support applying a pager on the output
|
||||
# Add "syntect/plist-load" when https://github.com/trishume/syntect/pull/345 reaches us
|
||||
build-assets = ["syntect/yaml-load", "syntect/dump-create"]
|
||||
build-assets = ["syntect/yaml-load", "syntect/dump-create", "regex", "walkdir"]
|
||||
|
||||
# You need to use one of these if you depend on bat as a library:
|
||||
regex-onig = ["syntect/regex-onig"] # Use the "oniguruma" regex engine
|
||||
@ -63,6 +63,8 @@ clircle = "0.3"
|
||||
bugreport = { version = "0.4", optional = true }
|
||||
dirs-next = { version = "2.0.0", optional = true }
|
||||
grep-cli = { version = "0.1.6", optional = true }
|
||||
regex = { version = "1.0", optional = true }
|
||||
walkdir = { version = "2.0", optional = true }
|
||||
|
||||
[dependencies.git2]
|
||||
version = "0.13"
|
||||
|
6
NOTICE
Normal file
6
NOTICE
Normal file
@ -0,0 +1,6 @@
|
||||
Copyright (c) 2018-2021 bat-developers (https://github.com/sharkdp/bat).
|
||||
|
||||
bat is made available under the terms of either the MIT License or the Apache
|
||||
License 2.0, at your option.
|
||||
|
||||
See the LICENSE-APACHE and LICENSE-MIT files for license details.
|
BIN
assets/acknowledgements.bin
vendored
Normal file
BIN
assets/acknowledgements.bin
vendored
Normal file
Binary file not shown.
2
assets/create.sh
vendored
2
assets/create.sh
vendored
@ -53,7 +53,7 @@ bat cache --clear
|
||||
done
|
||||
)
|
||||
|
||||
bat cache --build --blank --source="$ASSET_DIR" --target="$ASSET_DIR"
|
||||
bat cache --build --blank --acknowledgements --source="$ASSET_DIR" --target="$ASSET_DIR"
|
||||
|
||||
(
|
||||
cd "$ASSET_DIR"
|
||||
|
@ -55,6 +55,9 @@ pub(crate) const COMPRESS_THEMES: bool = false;
|
||||
/// performance due to lazy-loading
|
||||
pub(crate) const COMPRESS_LAZY_THEMES: bool = true;
|
||||
|
||||
/// Compress for size of ~10 kB instead of ~120 kB
|
||||
pub(crate) const COMPRESS_ACKNOWLEDGEMENTS: bool = true;
|
||||
|
||||
impl HighlightingAssets {
|
||||
fn new(serialized_syntax_set: SerializedSyntaxSet, theme_set: LazyThemeSet) -> Self {
|
||||
HighlightingAssets {
|
||||
@ -305,6 +308,13 @@ pub(crate) fn get_integrated_themeset() -> LazyThemeSet {
|
||||
from_binary(include_bytes!("../assets/themes.bin"), COMPRESS_THEMES)
|
||||
}
|
||||
|
||||
pub fn get_acknowledgements() -> String {
|
||||
from_binary(
|
||||
include_bytes!("../assets/acknowledgements.bin"),
|
||||
COMPRESS_ACKNOWLEDGEMENTS,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_binary<T: serde::de::DeserializeOwned>(v: &[u8], compressed: bool) -> T {
|
||||
asset_from_contents(v, "n/a", compressed)
|
||||
.expect("data integrated in binary is never faulty, but make sure `compressed` is in sync!")
|
||||
|
@ -5,10 +5,14 @@ use syntect::highlighting::ThemeSet;
|
||||
use syntect::parsing::{SyntaxSet, SyntaxSetBuilder};
|
||||
|
||||
use crate::assets::*;
|
||||
use acknowledgements::build_acknowledgements;
|
||||
|
||||
mod acknowledgements;
|
||||
|
||||
pub fn build(
|
||||
source_dir: &Path,
|
||||
include_integrated_assets: bool,
|
||||
include_acknowledgements: bool,
|
||||
target_dir: &Path,
|
||||
current_version: &str,
|
||||
) -> Result<()> {
|
||||
@ -18,9 +22,17 @@ pub fn build(
|
||||
|
||||
let syntax_set = syntax_set_builder.build();
|
||||
|
||||
let acknowledgements = build_acknowledgements(source_dir, include_acknowledgements)?;
|
||||
|
||||
print_unlinked_contexts(&syntax_set);
|
||||
|
||||
write_assets(&theme_set, &syntax_set, target_dir, current_version)
|
||||
write_assets(
|
||||
&theme_set,
|
||||
&syntax_set,
|
||||
&acknowledgements,
|
||||
target_dir,
|
||||
current_version,
|
||||
)
|
||||
}
|
||||
|
||||
fn build_theme_set(source_dir: &Path, include_integrated_assets: bool) -> Result<LazyThemeSet> {
|
||||
@ -89,6 +101,7 @@ fn print_unlinked_contexts(syntax_set: &SyntaxSet) {
|
||||
fn write_assets(
|
||||
theme_set: &LazyThemeSet,
|
||||
syntax_set: &SyntaxSet,
|
||||
acknowledgements: &Option<String>,
|
||||
target_dir: &Path,
|
||||
current_version: &str,
|
||||
) -> Result<()> {
|
||||
@ -106,6 +119,15 @@ fn write_assets(
|
||||
COMPRESS_SYNTAXES,
|
||||
)?;
|
||||
|
||||
if let Some(acknowledgements) = acknowledgements {
|
||||
asset_to_cache(
|
||||
acknowledgements,
|
||||
&target_dir.join("acknowledgements.bin"),
|
||||
"acknowledgements",
|
||||
COMPRESS_ACKNOWLEDGEMENTS,
|
||||
)?;
|
||||
}
|
||||
|
||||
print!(
|
||||
"Writing metadata to folder {} ... ",
|
||||
target_dir.to_string_lossy()
|
||||
|
219
src/assets/build_assets/acknowledgements.rs
Normal file
219
src/assets/build_assets/acknowledgements.rs
Normal file
@ -0,0 +1,219 @@
|
||||
use std::fs::read_to_string;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use crate::error::*;
|
||||
|
||||
struct PathAndStem {
|
||||
path: PathBuf,
|
||||
stem: String,
|
||||
relative_path: String,
|
||||
}
|
||||
|
||||
/// Looks for LICENSE and NOTICE files in `source_dir`, does some rudimentary
|
||||
/// analysis, and compiles them together in a single string that is meant to be
|
||||
/// used in the output to `--acknowledgements`
|
||||
pub fn build_acknowledgements(
|
||||
source_dir: &Path,
|
||||
include_acknowledgements: bool,
|
||||
) -> Result<Option<String>> {
|
||||
if !include_acknowledgements {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut acknowledgements = format!("{}\n\n", include_str!("../../../NOTICE"));
|
||||
|
||||
// Sort entries so the order is stable over time
|
||||
let entries = walkdir::WalkDir::new(source_dir).sort_by(|a, b| a.path().cmp(b.path()));
|
||||
for path_and_stem in entries
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flat_map(|entry| to_path_and_stem(source_dir, entry))
|
||||
{
|
||||
if let Some(license_text) = handle_file(&path_and_stem)? {
|
||||
append_to_acknowledgements(
|
||||
&mut acknowledgements,
|
||||
&path_and_stem.relative_path,
|
||||
&license_text,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(acknowledgements))
|
||||
}
|
||||
|
||||
fn to_path_and_stem(source_dir: &Path, entry: DirEntry) -> Option<PathAndStem> {
|
||||
let path = entry.path();
|
||||
|
||||
Some(PathAndStem {
|
||||
path: path.to_owned(),
|
||||
stem: path.file_stem().map(|s| s.to_string_lossy().to_string())?,
|
||||
relative_path: path
|
||||
.strip_prefix(source_dir)
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.ok()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_file(path_and_stem: &PathAndStem) -> Result<Option<String>> {
|
||||
if path_and_stem.stem == "NOTICE" {
|
||||
handle_notice(&path_and_stem.path)
|
||||
} else if path_and_stem.stem.to_ascii_uppercase() == "LICENSE" {
|
||||
handle_license(&path_and_stem.path)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_notice(path: &Path) -> Result<Option<String>> {
|
||||
// Assume NOTICE as defined by Apache License 2.0. These must be part of acknowledgements.
|
||||
Ok(Some(read_to_string(path)?))
|
||||
}
|
||||
|
||||
fn handle_license(path: &Path) -> Result<Option<String>> {
|
||||
let license_text = read_to_string(path)?;
|
||||
|
||||
if include_license_in_acknowledgments(&license_text) {
|
||||
Ok(Some(license_text))
|
||||
} else if license_not_needed_in_acknowledgements(&license_text) {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(format!("ERROR: License is of unknown type: {:?}", path).into())
|
||||
}
|
||||
}
|
||||
|
||||
fn include_license_in_acknowledgments(license_text: &str) -> bool {
|
||||
let markers = vec![
|
||||
// MIT
|
||||
"The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.",
|
||||
|
||||
// BSD
|
||||
"Redistributions in binary form must reproduce the above copyright notice,",
|
||||
|
||||
// Apache 2.0
|
||||
"Apache License Version 2.0, January 2004 http://www.apache.org/licenses/",
|
||||
"Licensed under the Apache License, Version 2.0 (the \"License\");",
|
||||
];
|
||||
|
||||
license_contains_marker(license_text, &markers)
|
||||
}
|
||||
|
||||
fn license_not_needed_in_acknowledgements(license_text: &str) -> bool {
|
||||
let markers = vec![
|
||||
// Public domain
|
||||
"This is free and unencumbered software released into the public domain.",
|
||||
|
||||
// Special license of assets/syntaxes/01_Packages/LICENSE
|
||||
"Permission to copy, use, modify, sell and distribute this software is granted. This software is provided \"as is\" without express or implied warranty, and with no claim as to its suitability for any purpose."
|
||||
];
|
||||
|
||||
license_contains_marker(license_text, &markers)
|
||||
}
|
||||
|
||||
fn license_contains_marker(license_text: &str, markers: &[&str]) -> bool {
|
||||
let normalized_license_text = normalize_license_text(license_text);
|
||||
markers.iter().any(|m| normalized_license_text.contains(m))
|
||||
}
|
||||
|
||||
fn append_to_acknowledgements(
|
||||
acknowledgements: &mut String,
|
||||
relative_path: &str,
|
||||
license_text: &str,
|
||||
) {
|
||||
acknowledgements.push_str(&format!("## {}\n\n{}", relative_path, license_text));
|
||||
|
||||
// Make sure the last char is a newline to not mess up formatting later
|
||||
if acknowledgements
|
||||
.chars()
|
||||
.last()
|
||||
.expect("acknowledgements is not the empty string")
|
||||
!= '\n'
|
||||
{
|
||||
acknowledgements.push('\n');
|
||||
}
|
||||
|
||||
// Add two more newlines to make it easy to distinguish where this text ends
|
||||
// and the next starts
|
||||
acknowledgements.push_str("\n\n");
|
||||
}
|
||||
|
||||
/// Replaces newlines with a space character, and replaces multiple spaces with one space.
|
||||
/// This makes the text easier to analyze.
|
||||
fn normalize_license_text(license_text: &str) -> String {
|
||||
use regex::Regex;
|
||||
|
||||
let whitespace_and_newlines = Regex::new(r"\s").unwrap();
|
||||
let as_single_line = whitespace_and_newlines.replace_all(license_text, " ");
|
||||
|
||||
let many_spaces = Regex::new(" +").unwrap();
|
||||
many_spaces.replace_all(&as_single_line, " ").to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[cfg(test)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_normalize_license_text() {
|
||||
let license_text = "This is a license text with these terms:
|
||||
* Complicated multi-line
|
||||
term with indentation";
|
||||
|
||||
assert_eq!(
|
||||
"This is a license text with these terms: * Complicated multi-line term with indentation".to_owned(),
|
||||
normalize_license_text(license_text),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_license_text_with_windows_line_endings() {
|
||||
let license_text = "This license text includes windows line endings\r
|
||||
and we need to handle that.";
|
||||
|
||||
assert_eq!(
|
||||
"This license text includes windows line endings and we need to handle that."
|
||||
.to_owned(),
|
||||
normalize_license_text(license_text),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_append_to_acknowledgements_adds_newline_if_missing() {
|
||||
let mut acknowledgements = "preamble\n\n\n".to_owned();
|
||||
|
||||
append_to_acknowledgements(&mut acknowledgements, "some/path", "line without newline");
|
||||
assert_eq!(
|
||||
"preamble
|
||||
|
||||
|
||||
## some/path
|
||||
|
||||
line without newline
|
||||
|
||||
|
||||
",
|
||||
acknowledgements
|
||||
);
|
||||
|
||||
append_to_acknowledgements(&mut acknowledgements, "another/path", "line with newline\n");
|
||||
assert_eq!(
|
||||
"preamble
|
||||
|
||||
|
||||
## some/path
|
||||
|
||||
line without newline
|
||||
|
||||
|
||||
## another/path
|
||||
|
||||
line with newline
|
||||
|
||||
|
||||
",
|
||||
acknowledgements
|
||||
);
|
||||
}
|
||||
}
|
@ -508,6 +508,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
|
||||
.hidden_short_help(true)
|
||||
.help("Show diagnostic information for bug reports.")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("acknowledgements")
|
||||
.long("acknowledgements")
|
||||
.hidden_short_help(true)
|
||||
.help("Show acknowledgements."),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ignored-suffix")
|
||||
.number_of_values(1)
|
||||
@ -578,6 +584,12 @@ pub fn build_app(interactive_output: bool) -> ClapApp<'static, 'static> {
|
||||
"Create completely new syntax and theme sets \
|
||||
(instead of appending to the default sets).",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("acknowledgements")
|
||||
.long("acknowledgements")
|
||||
.requires("build")
|
||||
.help("Build acknowledgements.bin."),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -47,9 +47,13 @@ fn build_assets(matches: &clap::ArgMatches) -> Result<()> {
|
||||
.map(Path::new)
|
||||
.unwrap_or_else(|| PROJECT_DIRS.cache_dir());
|
||||
|
||||
let blank = matches.is_present("blank");
|
||||
|
||||
bat::assets::build(source_dir, !blank, target_dir, clap::crate_version!())
|
||||
bat::assets::build(
|
||||
source_dir,
|
||||
!matches.is_present("blank"),
|
||||
matches.is_present("acknowledgements"),
|
||||
target_dir,
|
||||
clap::crate_version!(),
|
||||
)
|
||||
}
|
||||
|
||||
fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
|
||||
@ -324,6 +328,9 @@ fn run() -> Result<bool> {
|
||||
} else if app.matches.is_present("cache-dir") {
|
||||
writeln!(io::stdout(), "{}", cache_dir())?;
|
||||
Ok(true)
|
||||
} else if app.matches.is_present("acknowledgements") {
|
||||
writeln!(io::stdout(), "{}", bat::assets::get_acknowledgements())?;
|
||||
Ok(true)
|
||||
} else {
|
||||
run_controller(inputs, &config)
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use assert_cmd::cargo::CommandCargoExt;
|
||||
use predicates::boolean::PredicateBooleanExt;
|
||||
use predicates::{prelude::predicate, str::PredicateStrExt};
|
||||
use serial_test::serial;
|
||||
use std::path::Path;
|
||||
@ -1275,3 +1276,39 @@ fn ignored_suffix_arg() {
|
||||
.stdout("\u{1b}[38;5;231m{\"test\": \"value\"}\u{1b}[0m")
|
||||
.stderr("");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn acknowledgements() {
|
||||
bat()
|
||||
.arg("--acknowledgements")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
// Just some sanity checking that avoids names of persons, except our own Keith Hall :)
|
||||
predicate::str::contains(
|
||||
"Copyright (c) 2018-2021 bat-developers (https://github.com/sharkdp/bat).",
|
||||
)
|
||||
.and(predicate::str::contains(
|
||||
"Copyright (c) 2012-2020 The Sublime CMake authors",
|
||||
))
|
||||
.and(predicate::str::contains(
|
||||
"Copyright 2014-2015 SaltStack Team",
|
||||
))
|
||||
.and(predicate::str::contains(
|
||||
"Copyright (c) 2013-present Dracula Theme",
|
||||
))
|
||||
.and(predicate::str::contains(
|
||||
"## syntaxes/01_Packages/Rust/LICENSE.txt",
|
||||
))
|
||||
.and(predicate::str::contains(
|
||||
"## syntaxes/02_Extra/http-request-response/LICENSE",
|
||||
))
|
||||
.and(predicate::str::contains(
|
||||
"## themes/dracula-sublime/LICENSE",
|
||||
))
|
||||
.and(predicate::str::contains("Copyright (c) 2017 b123400"))
|
||||
.and(predicate::str::contains("Copyright (c) 2021 Keith Hall"))
|
||||
.and(predicate::str::contains("Copyright 2014 Clams")),
|
||||
)
|
||||
.stderr("");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user