mirror of
https://github.com/sharkdp/bat
synced 2026-08-03 18:51:44 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba4d5a2e7d | |||
| 026273a05c | |||
| 8903b64830 | |||
| 194155f062 | |||
| a6ff3b900d | |||
| 8a52bcf92d | |||
| 0b1535f63b | |||
| 0a5b3eaf3e | |||
| 11294249f8 | |||
| 35a1236ad0 | |||
| af8da50c2c | |||
| 649d1e3df2 | |||
| 199cfd4519 | |||
| c7cce0752b | |||
| 3a89486189 | |||
| 720a2fb7a5 | |||
| e3c990f11e | |||
| 90c7d0c365 | |||
| 56002267d2 | |||
| 84734eac9d |
Generated
+1
-1
@@ -35,7 +35,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bat"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
dependencies = [
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ license = "MIT/Apache-2.0"
|
||||
name = "bat"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/bat"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
exclude = [
|
||||
"assets/syntaxes/*",
|
||||
"assets/themes/*",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<p align="center">
|
||||
<img src="doc/logo-header.svg" alt="bat - a cat clone with wings"><br>
|
||||
<a href="https://travis-ci.org/sharkdp/bat"><img src="https://travis-ci.org/sharkdp/bat.svg?branch=master" alt="Build Status"></a>
|
||||
<a href="https://ci.appveyor.com/project/sharkdp/bat"><img src="https://ci.appveyor.com/api/projects/status/cptsmtbiwbnr2vhf?svg=true"></a>
|
||||
<img src="https://img.shields.io/crates/l/bat.svg" alt="license">
|
||||
<a href="https://crates.io/crates/bat"><img src="https://img.shields.io/crates/v/bat.svg?colorB=319e8c" alt="Version info"></a><br>
|
||||
A <i>cat(1)</i> clone with syntax highlighting and Git integration.
|
||||
@@ -82,7 +83,7 @@ bat f - g # output 'f', then stdin, then 'g'.
|
||||
Download the latest `.deb` package from the [release page](https://github.com/sharkdp/bat/releases)
|
||||
and install it via:
|
||||
``` bash
|
||||
sudo dpkg -i bat_0.6.0_amd64.deb # adapt version number and architecture
|
||||
sudo dpkg -i bat_0.6.1_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Arch Linux
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
environment:
|
||||
global:
|
||||
TARGET: x86_64-pc-windows-msvc
|
||||
RUST_VERSION: stable
|
||||
CRATE_NAME: bat
|
||||
CARGO_HOME: "c:\\cargo"
|
||||
RUSTUP_HOME: "c:\\rustup"
|
||||
|
||||
install:
|
||||
- appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- rustup-init.exe -y --default-host %TARGET%
|
||||
- set PATH=%PATH%;C:\cargo\bin
|
||||
- rustc -Vv
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
test_script:
|
||||
- cargo test --target %TARGET% --verbose
|
||||
- cargo run --target %TARGET% -- src/main.rs README.md --paging=never
|
||||
|
||||
before_deploy:
|
||||
# Generate artifacts for release
|
||||
- cargo build --release --verbose
|
||||
- ps: ci\before_deploy.ps1
|
||||
|
||||
deploy:
|
||||
description: 'Automatically deployed release'
|
||||
artifact: /.*\.zip/
|
||||
# Here's how:
|
||||
# - Go to 'https://github.com/settings/tokens/new' and generate a Token with only the
|
||||
# `public_repo` scope enabled
|
||||
# - Then go to 'https://ci.appveyor.com/tools/encrypt' and enter the newly generated token.
|
||||
# - Enter the "encrypted value" below
|
||||
auth_token:
|
||||
secure: oO4/J+hcFXgXcEqEc8gzlQNqLG0IxU78s4EyH+uczGdsyWajb3yipxPR6nXUvmoj
|
||||
provider: GitHub
|
||||
on:
|
||||
RUST_VERSION: stable
|
||||
appveyor_repo_tag: true
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.cargo\registry
|
||||
- target
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
on_build_success: false
|
||||
on_build_failure: false
|
||||
on_build_status_changed: false
|
||||
@@ -0,0 +1,26 @@
|
||||
# This script takes care of packaging the build artifacts that will go in the
|
||||
# release zipfile
|
||||
|
||||
$SRC_DIR = $PWD.Path
|
||||
$STAGE = [System.Guid]::NewGuid().ToString()
|
||||
|
||||
Set-Location $ENV:Temp
|
||||
New-Item -Type Directory -Name $STAGE
|
||||
Set-Location $STAGE
|
||||
|
||||
$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"
|
||||
|
||||
Copy-Item "$SRC_DIR\target\release\bat.exe" '.\'
|
||||
|
||||
# readme and license
|
||||
Copy-Item $SRC_DIR\README.md '.\'
|
||||
Copy-Item $SRC_DIR\LICENSE-MIT '.\'
|
||||
Copy-Item $SRC_DIR\LICENSE-APACHE '.\'
|
||||
|
||||
7z a "$ZIP" *
|
||||
|
||||
Push-AppveyorArtifact "$ZIP"
|
||||
|
||||
Set-Location ..
|
||||
Remove-Item $STAGE -Force -Recurse
|
||||
Set-Location $SRC_DIR
|
||||
+1
-1
@@ -6,7 +6,7 @@ if you are not looking for a program like `bat`, this comparison might not be fo
|
||||
|
||||
| | bat | [pygments](http://pygments.org/) | [highlight](http://www.andre-simon.de/doku/highlight/highlight.php) | [ccat](https://github.com/jingweno/ccat) | [source-highlight](https://www.gnu.org/software/src-highlite/) | [hicat](https://github.com/rstacruz/hicat) | [coderay](https://github.com/rubychan/coderay) | [rouge](https://github.com/jneen/rouge) |
|
||||
|----------------------------------------------|---------------------------------------------------------------------|----------------------------------|---------------------------------------------------------------------|------------------------------------------|----------------------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|-----------------------------------------------------|
|
||||
| Drop-in `cat` replacement | (:heavy_check_mark:) [*](https://github.com/sharkdp/bat/issues/134) | :x: | :x: | (:heavy_check_mark:) | :x: | :x: [*](https://github.com/rstacruz/hicat/issues/6) | :x: | :x: |
|
||||
| Drop-in `cat` replacement | :heavy_check_mark: [*](https://github.com/sharkdp/bat/issues/134) | :x: | :x: | (:heavy_check_mark:) | :x: | :x: [*](https://github.com/rstacruz/hicat/issues/6) | :x: | :x: |
|
||||
| Git integration | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | :x: | :x: |
|
||||
| Automatic paging | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | :x: | :x: |
|
||||
| Languages (circa) | 60 | 300 | 200 | 7 | 80 | 130 | 30 | 130 |
|
||||
|
||||
+22
-6
@@ -271,6 +271,13 @@ impl App {
|
||||
'unbuffered'). The output is always unbuffered - this option \
|
||||
is simply ignored.",
|
||||
),
|
||||
).arg(
|
||||
Arg::with_name("terminal-width")
|
||||
.long("terminal-width")
|
||||
.takes_value(true)
|
||||
.value_name("width")
|
||||
.hidden(true)
|
||||
.help("Set the width of the terminal"),
|
||||
).subcommand(
|
||||
SubCommand::with_name("cache")
|
||||
.about("Modify the syntax-definition and theme cache")
|
||||
@@ -328,6 +335,15 @@ impl App {
|
||||
pub fn config(&self) -> Result<Config> {
|
||||
let files = self.files();
|
||||
|
||||
let colored_output = match self.matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => self.interactive_output,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let colored_output = colored_output && ansi_term::enable_ansi_support().is_ok();
|
||||
|
||||
Ok(Config {
|
||||
true_color: is_truecolor_terminal(),
|
||||
output_components: self.output_components()?,
|
||||
@@ -342,11 +358,7 @@ impl App {
|
||||
Some("never") | _ => OutputWrap::None,
|
||||
}
|
||||
},
|
||||
colored_output: match self.matches.value_of("color") {
|
||||
Some("always") => true,
|
||||
Some("never") => false,
|
||||
Some("auto") | _ => self.interactive_output,
|
||||
},
|
||||
colored_output,
|
||||
paging_mode: match self.matches.value_of("paging") {
|
||||
Some("always") => PagingMode::Always,
|
||||
Some("never") => PagingMode::Never,
|
||||
@@ -367,7 +379,11 @@ impl App {
|
||||
}
|
||||
},
|
||||
},
|
||||
term_width: Term::stdout().size().1 as usize,
|
||||
term_width: self
|
||||
.matches
|
||||
.value_of("terminal-width")
|
||||
.and_then(|w| w.parse().ok())
|
||||
.unwrap_or(Term::stdout().size().1 as usize),
|
||||
loop_through: !(self.interactive_output
|
||||
|| self.matches.value_of("color") == Some("always")
|
||||
|| self.matches.value_of("decorations") == Some("always")),
|
||||
|
||||
+7
-4
@@ -15,11 +15,14 @@ pub type LineChanges = HashMap<u32, LineChange>;
|
||||
|
||||
pub fn get_git_diff(filename: &str) -> Option<LineChanges> {
|
||||
let repo = Repository::discover(&filename).ok()?;
|
||||
let path_absolute = fs::canonicalize(&filename).ok()?;
|
||||
let path_relative_to_repo = path_absolute.strip_prefix(repo.workdir()?).ok()?;
|
||||
|
||||
let repo_path_absolute = fs::canonicalize(repo.workdir()?).ok()?;
|
||||
|
||||
let filepath_absolute = fs::canonicalize(&filename).ok()?;
|
||||
let filepath_relative_to_repo = filepath_absolute.strip_prefix(&repo_path_absolute).ok()?;
|
||||
|
||||
let mut diff_options = DiffOptions::new();
|
||||
let pathspec = path_relative_to_repo.into_c_string().ok()?;
|
||||
let pathspec = filepath_relative_to_repo.into_c_string().ok()?;
|
||||
diff_options.pathspec(pathspec);
|
||||
diff_options.context_lines(0);
|
||||
|
||||
@@ -42,7 +45,7 @@ pub fn get_git_diff(filename: &str) -> Option<LineChanges> {
|
||||
Some(&mut |delta, hunk| {
|
||||
let path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
|
||||
|
||||
if path_relative_to_repo != path {
|
||||
if filepath_relative_to_repo != path {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+44
-16
@@ -30,6 +30,7 @@ mod terminal;
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::process;
|
||||
|
||||
@@ -80,13 +81,13 @@ fn run_cache_subcommand(matches: &clap::ArgMatches) -> Result<()> {
|
||||
} else if matches.is_present("clear") {
|
||||
clear_assets();
|
||||
} else if matches.is_present("config-dir") {
|
||||
println!("{}", config_dir());
|
||||
writeln!(io::stdout(), "{}", config_dir())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
|
||||
pub fn list_languages(assets: &HighlightingAssets, config: &Config) -> Result<()> {
|
||||
let mut languages = assets
|
||||
.syntax_set
|
||||
.syntaxes()
|
||||
@@ -104,10 +105,19 @@ pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
|
||||
let comma_separator = ", ";
|
||||
let separator = " ";
|
||||
// Line-wrapping for the possible file extension overflow.
|
||||
let desired_width = term_width - longest - separator.len();
|
||||
let desired_width = config.term_width - longest - separator.len();
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
let style = if config.colored_output {
|
||||
Green.normal()
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
|
||||
for lang in languages {
|
||||
print!("{:width$}{}", lang.name, separator, width = longest);
|
||||
write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?;
|
||||
|
||||
// Number of characters on this line so far, wrap before `desired_width`
|
||||
let mut num_chars = 0;
|
||||
@@ -118,32 +128,50 @@ pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
|
||||
let new_chars = word.len() + comma_separator.len();
|
||||
if num_chars + new_chars >= desired_width {
|
||||
num_chars = 0;
|
||||
print!("\n{:width$}{}", "", separator, width = longest);
|
||||
write!(stdout, "\n{:width$}{}", "", separator, width = longest)?;
|
||||
}
|
||||
|
||||
num_chars += new_chars;
|
||||
print!("{}", Green.paint(&word[..]));
|
||||
write!(stdout, "{}", style.paint(&word[..]))?;
|
||||
if extension.peek().is_some() {
|
||||
print!("{}", comma_separator);
|
||||
write!(stdout, "{}", comma_separator)?;
|
||||
}
|
||||
}
|
||||
println!();
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn list_themes(assets: &HighlightingAssets, cfg: &Config) {
|
||||
pub fn list_themes(assets: &HighlightingAssets, cfg: &Config) -> Result<()> {
|
||||
let themes = &assets.theme_set.themes;
|
||||
let mut config = cfg.clone();
|
||||
let mut style = HashSet::new();
|
||||
style.insert(OutputComponent::Plain);
|
||||
config.files = vec![InputFile::ThemePreviewFile];
|
||||
config.output_components = OutputComponents(style);
|
||||
for (theme, _) in themes.iter() {
|
||||
println!("Theme: {}\n", Style::new().bold().paint(theme.to_string()));
|
||||
config.theme = theme.to_string();
|
||||
let _controller = Controller::new(&config, &assets).run();
|
||||
println!()
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout = stdout.lock();
|
||||
|
||||
if config.colored_output {
|
||||
for (theme, _) in themes.iter() {
|
||||
writeln!(
|
||||
stdout,
|
||||
"Theme: {}\n",
|
||||
Style::new().bold().paint(theme.to_string())
|
||||
)?;
|
||||
config.theme = theme.to_string();
|
||||
let _controller = Controller::new(&config, &assets).run();
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
} else {
|
||||
for (theme, _) in themes.iter() {
|
||||
writeln!(stdout, "{}", theme)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
|
||||
@@ -161,11 +189,11 @@ fn run() -> Result<bool> {
|
||||
let assets = HighlightingAssets::new();
|
||||
|
||||
if app.matches.is_present("list-languages") {
|
||||
list_languages(&assets, config.term_width);
|
||||
list_languages(&assets, &config)?;
|
||||
|
||||
Ok(true)
|
||||
} else if app.matches.is_present("list-themes") {
|
||||
list_themes(&assets, &config);
|
||||
list_themes(&assets, &config)?;
|
||||
|
||||
Ok(true)
|
||||
} else {
|
||||
|
||||
@@ -44,6 +44,7 @@ impl BatTester {
|
||||
.args(&[
|
||||
"sample.rs",
|
||||
"--decorations=always",
|
||||
"--terminal-width=80",
|
||||
&format!("--style={}", style),
|
||||
]).output()
|
||||
.expect("bat failed");
|
||||
|
||||
+30
-28
@@ -2,32 +2,34 @@ mod tester;
|
||||
|
||||
use tester::BatTester;
|
||||
|
||||
static STYLES: &'static [&'static str] = &[
|
||||
"changes",
|
||||
"grid",
|
||||
"header",
|
||||
"numbers",
|
||||
"changes,grid",
|
||||
"changes,header",
|
||||
"changes,numbers",
|
||||
"grid,header",
|
||||
"grid,numbers",
|
||||
"header,numbers",
|
||||
"changes,grid,header",
|
||||
"changes,grid,numbers",
|
||||
"changes,header,numbers",
|
||||
"grid,header,numbers",
|
||||
"changes,grid,header,numbers",
|
||||
"full",
|
||||
"plain",
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_snapshots() {
|
||||
let bat_tester = BatTester::new();
|
||||
|
||||
for style in STYLES {
|
||||
println!("testing {}", style);
|
||||
bat_tester.test_snapshot(&*style);
|
||||
}
|
||||
macro_rules! snapshot_tests {
|
||||
($($test_name: ident: $style: expr,)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $test_name() {
|
||||
let bat_tester = BatTester::new();
|
||||
bat_tester.test_snapshot($style);
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
snapshot_tests! {
|
||||
changes: "changes",
|
||||
grid: "grid",
|
||||
header: "header",
|
||||
numbers: "numbers",
|
||||
changes_grid: "changes,grid",
|
||||
changes_header: "changes,header",
|
||||
changes_numbers: "changes,numbers",
|
||||
grid_header: "grid,header",
|
||||
grid_numbers: "grid,numbers",
|
||||
header_numbers: "header,numbers",
|
||||
changes_grid_header: "changes,grid,header",
|
||||
changes_grid_numbers: "changes,grid,numbers",
|
||||
changes_header_numbers: "changes,header,numbers",
|
||||
grid_header_numbers: "grid,header,numbers",
|
||||
changes_grid_header_numbers: "changes,grid,header,numbers",
|
||||
full: "full",
|
||||
plain: "plain",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user