1
0
mirror of https://github.com/sharkdp/bat synced 2026-08-04 19:01:44 +00:00

Compare commits

...

99 Commits

Author SHA1 Message Date
sharkdp 195d5e0e16 Update Cargo.lock 2018-06-01 00:35:20 +02:00
sharkdp 86c1777205 Skip snapshot test if Git is not available
closes #161
2018-06-01 00:33:39 +02:00
sharkdp 16874ab016 Exclude assets from upload 2018-05-31 23:49:02 +02:00
sharkdp bf5883f06f Updates for 0.4 2018-05-31 22:57:18 +02:00
sharkdp e27bdfc7d6 Fix clippy warnings 2018-05-31 15:27:55 +02:00
David Peter a7f4f202cb Update README.md 2018-05-30 21:32:40 +02:00
David Peter 397223374a Add TOC 2018-05-30 21:23:24 +02:00
David Peter 0c2da90fa4 Change color of crates.io badge 2018-05-30 21:10:14 +02:00
David Peter fe29da6e76 Add license badge 2018-05-30 21:02:40 +02:00
sharkdp 0eccfb50d9 Update header 2018-05-30 20:54:46 +02:00
sharkdp f74263e71d Update syntect 2018-05-30 20:50:50 +02:00
sharkdp 54f4896ced Update syntaxes, fix CMake syntax problem 2018-05-30 20:43:50 +02:00
sharkdp 9d92350cbb Use default Markdown syntax, closes #157 2018-05-30 20:40:02 +02:00
sharkdp 26d409db65 Add 'rouge' to list of alternatives 2018-05-30 19:24:30 +02:00
sharkdp 06cd4b2ce8 Add 'coderay' to list of alternatives 2018-05-30 19:16:08 +02:00
sharkdp a348a9f68e Handle syntax-set loading errors 2018-05-25 22:42:06 +02:00
sharkdp 39e2971e96 Add .ignore files to avoid searching asset folders 2018-05-25 17:44:50 +02:00
TA Thanh Dinh a13eb60df3 Using slice notation instead of as_ref 2018-05-25 17:38:54 +02:00
sharkdp 9342d0bb24 Explicitly specify which args override themselves 2018-05-24 22:53:23 +02:00
sharkdp 36d7250af2 Allow arguments to override themselves 2018-05-24 22:53:23 +02:00
Ezinwa Okpoechi 2ce8655167 Build bat before generating snapshots 2018-05-24 19:10:24 +02:00
Ezinwa Okpoechi a452467e06 Remove unnecessary space if there are no decorations 2018-05-24 19:10:24 +02:00
Ezinwa Okpoechi 2ea3758c4b Justify header when there are decorations but no grid 2018-05-24 19:10:24 +02:00
Gert Hulselmans 65bb4c7ee6 Print themes directory in error message if no themes could be loaded from it.
Before this patch:
    $ bat cache --init
    [bat error]: Could not load themes from '{}'

After:
    $ bat cache --init
    [bat error]: Could not load themes from '/home/user/.config/bat/themes'
2018-05-24 09:09:23 +02:00
sharkdp 556149ad3a Add comparison benchmark 2018-05-22 23:32:46 +02:00
Gert Hulselmans 33bbf25468 Fix launching of pager so text is interpreted as UTF-8 by default.
This fixes issue https://github.com/sharkdp/bat/issues/98
2018-05-22 22:41:58 +02:00
sharkdp 5ee8913538 Add logo header to README 2018-05-22 22:39:20 +02:00
David Peter 9b1c1c0271 Add more benchmark results 2018-05-22 22:27:25 +02:00
Ezinwa Okpoechi 2712d63a4b Move asset clearing to assets module 2018-05-21 23:20:45 +02:00
Ezinwa Okpoechi b21fb6bca8 Extract features to separate module 2018-05-21 23:20:45 +02:00
Ezinwa Okpoechi ee43377a9c Move colors to printer module 2018-05-21 19:24:02 +02:00
Ezinwa Okpoechi 120b33a9db Move output to separate module 2018-05-21 19:24:02 +02:00
sharkdp 17b37fedcb Add project goals and list of alternatives 2018-05-21 00:28:30 +02:00
sharkdp 1013cdd80a Bump version 2018-05-19 17:55:07 +02:00
sharkdp be0a2d6048 Extended help texts for '--help' 2018-05-19 17:54:05 +02:00
sharkdp d1a1943998 Add -u and -n options, see #134 2018-05-19 16:58:45 +02:00
sharkdp 2a9f5a24ed Permissive error handling, closes #17 2018-05-19 12:50:41 +02:00
sharkdp a0ae089c4a Reset line number to 0 for new files 2018-05-19 12:25:07 +02:00
sharkdp 96cc391f2d Skip '--quit-if-one-screen' for --paging=always
closes #97
2018-05-19 10:54:54 +02:00
sharkdp a1e1170319 Add a few popular Sublime Text themes 2018-05-18 20:41:40 +02:00
sharkdp 54a331d162 Add syntax docu, add VimL, CMake, INI, LESS syntax
closes #99
closes #106
2018-05-18 19:57:58 +02:00
sharkdp 5e3b17e6f7 Fix code formatting 2018-05-18 17:44:43 +02:00
eth-p 486e6a19cd Fixed #124 2018-05-18 17:44:43 +02:00
Ezinwa Okpoechi 247dfbee83 Extract syntax finding to assets module 2018-05-18 16:45:59 +02:00
Ezinwa Okpoechi 882931a77b Update snapshots 2018-05-18 16:44:34 +02:00
Ezinwa Okpoechi 12cb438aa4 Handle line with invalid UTF-8 2018-05-18 16:44:34 +02:00
Ezinwa Okpoechi d0782ef954 Add line with invalid UTF-8 2018-05-18 16:44:34 +02:00
sharkdp c826c2a438 Better error handling 2018-05-16 22:55:37 +02:00
sharkdp 9aa97b6c22 Update to latest syntect, closes #100 2018-05-16 22:55:37 +02:00
sharkdp f7890870ea Add syntax for Dockerfiles
closes #101
2018-05-16 22:04:12 +02:00
sharkdp 145b99f01c Include syntaxes and themes in repository
This changes a few things:

- All syntaxes and themes are now stored (as submodules) under
  assets/syntaxes and assets/themes

- The default directories for syntaxes and themes are "syntaxes"
  and "themes" (used to be "syntax" and "themes")

- The "bat cache" command can now take a `--source <dir>` and
  `--target <dir>` option.

- The cached files have been renamed to "themes.bin" and "syntaxes.bin"
2018-05-16 22:04:12 +02:00
Mateusz Piotrowski 9af1d2b891 Add FreeBSD installation instructions
Closes #41.
2018-05-16 20:25:14 +02:00
David Peter 2eee68599d Merge pull request #102 from eth-p/master
Added text wrapping. (Fixes #54)
2018-05-16 08:41:49 +02:00
eth-p d569693dab Formatted with newer cargo fmt.
It should pass all the checks now.
2018-05-16 08:29:41 +02:00
eth-p 900f61032d Fix #117 2018-05-15 14:09:51 -07:00
eth-p d0ca566979 Replace for_line and for_wrap with generate 2018-05-15 13:55:38 -07:00
eth-p a5a7dc14e4 Fix \r character being printed with wrapping enabled. 2018-05-14 14:36:08 -07:00
sharkdp 54b33c8c21 Add simple benchmark scripts 2018-05-14 22:20:26 +02:00
rleungx a5de77af4d address comment 2018-05-14 20:30:16 +02:00
rleungx 870ff7b538 sort the language listing 2018-05-14 20:30:16 +02:00
sharkdp 15b9acd12f Updates for new rustfmt 2018-05-14 19:04:13 +02:00
eth-p b327127f37 Split decorations into a separate file and optimized them a bit. 2018-05-13 18:44:07 -07:00
eth-p 9214a4a4f0 PR #102 Followed @sharkdp's suggestions 2018-05-13 12:45:16 -07:00
sharkdp fcecd97224 Add ansi_term import for Windows, see #107 2018-05-13 21:19:26 +02:00
eth-p 870b3c0daf PR #102 Followed @BrainMaestro's suggestions
Also ran cargo fmt.
2018-05-13 03:26:23 -07:00
sharkdp b9c780c8c8 Ignore errors when clearing cache, closes #91 2018-05-13 10:34:35 +02:00
sharkdp ca865f9d26 Enable ANSI support on Windows 10 2018-05-13 09:58:16 +02:00
sharkdp 3ee0e56315 Update create.sh script 2018-05-13 09:53:30 +02:00
Eric Davies 091b0b6437 Add Julia highlighting support 2018-05-13 09:53:00 +02:00
eth-p 9f005d115d Update snapshot test files. 2018-05-12 14:04:54 -07:00
eth-p b4cfc9633c Fix double spaces when outputting without wrapping. 2018-05-12 14:02:47 -07:00
eth-p f7e055b6b7 Merge branch 'master' of https://github.com/sharkdp/bat 2018-05-12 14:00:25 -07:00
eth-p d4b438b9d3 Fix padding, add --wrap argument, disable wrap for non-tty. (Fixed)
I'm not quite sure what was up with git on that last commit, but it's
all properly committed now.
2018-05-12 13:44:10 -07:00
eth-p cd26d403a3 Fix padding, add --wrap argument, disable wrap for non-tty.
Now bat(1) can be used like cat(1) again!
2018-05-12 13:23:33 -07:00
eth-p f95a23f948 Fix off-by-one error with text wrapping and --style grid 2018-05-12 12:07:41 -07:00
Ezinwa Okpoechi 0886a24685 Add snapshot testing 2018-05-12 18:07:30 +02:00
Ezinwa Okpoechi 53eb9c57d6 Add generated snapshots 2018-05-12 18:07:30 +02:00
Ezinwa Okpoechi 026a9ebae3 Add sample files and snapshot generator 2018-05-12 18:07:30 +02:00
Ethan P bdd10b8bec Merge branch 'master' into master 2018-05-12 06:40:47 -07:00
eth-p fc160b0dcd PR #102 Review Changes
- Reformatted code.
- Removed leftover code.
- Removed leftover comments.
- Fixed compiling on Rust 1.24.0
2018-05-12 06:32:23 -07:00
sharkdp cb7b158172 Add bold, italic and underline font styles 2018-05-12 13:23:39 +02:00
eth-p fcc36b1f79 Forgot to add styling to the horizontal line. 2018-05-11 22:49:26 -07:00
eth-p 4e4110bf50 Added line wrapping. 2018-05-11 21:59:26 -07:00
Ryan Leung 22c8978fca add theme option (#95)
closes #89
2018-05-11 13:53:17 +02:00
Ezinwa Okpoechi f711fb5006 Split style to separate module 2018-05-11 10:15:26 +02:00
Ezinwa Okpoechi 64a9341b73 Split app to separate module 2018-05-11 10:15:26 +02:00
sharkdp ccf88fd5d8 Add and use --style=auto by default 2018-05-10 16:22:24 +02:00
David Peter 6343535eb2 Add 32bit support (#87)
* Add 32bit support, closes #84
* Remove error-chain backtrace support
2018-05-10 14:36:05 +02:00
sharkdp e2ac6de783 Fix clippy warnings 2018-05-10 13:15:59 +02:00
Ezinwa Okpoechi 25cee002f9 Split modules (#86)
* Split diffing to separate module
* Split assets to separate module
2018-05-10 12:36:09 +02:00
sharkdp 13a53d230b Add MacOS install instructions, closes #33 2018-05-10 12:35:07 +02:00
sharkdp ea27053a63 Remove unused lifetimes 2018-05-10 12:20:38 +02:00
Pit Kleyersburg b2b932f3ed Fix header-line if grid is not requested 2018-05-10 12:20:38 +02:00
Pit Kleyersburg 747d074be1 Replace Cow by String 2018-05-10 12:20:38 +02:00
Pit Kleyersburg 61109ece15 Create OutputComponents struct, use HashSet 2018-05-10 12:20:38 +02:00
Pit Kleyersburg 389edd7239 Combine OutputComponents and PredefinedStyles
This commit tries to simply the change. Instead of separating an
`OutputComponent` and a `PredefinedStyle`, I have combined the two into
just `OutputComponent`.

To still have the styles work, I added an impl to `OutputComponent` with
a function `components` which returns the components related to the
specified component.

For a simple output component this is trivial, but for the predefined
styles this is a list of other components.

The evaluating of the command-line parameter simplified significantly,
making use of Claps `values_t!` macro to parse the supplied parameters
into a `Vec<OutputComponent>`. After that it is simply a task of
combining all supplied output components into one vector.

Important: this means that the styles are now additive, rather than one
of the predefined styles overruling other parameters supplied.
2018-05-10 12:20:38 +02:00
Pit Kleyersburg 23813cc08b Make --style parameter more flexible
The `--style` parameter now accepts a comma-separated list of strings,
where every element defines either a single output component (`changes`,
`grid`, `header`, `numbers`) or a predefined style (`full`,
`line-numbers`, `plain`).

If available, bat picks the first predefined style in the user-supplied
style-list and ignores everything else. If no predefined style was
requested, the other parameters that are simple output components will
be used.

Examples:

    --style changes,full,numbers

  Will internally be reduced to only the predefined style `full`.

    --style plain,full

  Will internally be reduced to only the predefined style `plain`.

    --style changes,numbers

  Will not be reduced, because the list does not contain any predefined
  styles.

(Note: if `grid` is requested but no other parameters, bat still creates
the left-most column with a width of `PANEL_WIDTH`. I didn't want to
introduce further logic in this PR that drops or adapts the width of the
left column.)
2018-05-10 12:20:38 +02:00
David Peter fdeb98e859 Add usage examples 2018-05-09 00:33:49 +02:00
David Peter 6a417886aa Add comment about paging & concatenation 2018-05-09 00:12:25 +02:00
70 changed files with 13660 additions and 812 deletions
+42
View File
@@ -0,0 +1,42 @@
[submodule "assets/syntaxes/Elixir"]
path = assets/syntaxes/Elixir
url = https://github.com/princemaple/elixir-sublime-syntax/
[submodule "assets/syntaxes/Packages"]
path = assets/syntaxes/Packages
url = https://github.com/sublimehq/Packages/
[submodule "assets/syntaxes/TOML"]
path = assets/syntaxes/TOML
url = https://github.com/sharkdp/sublime_toml_highlighting
[submodule "assets/syntaxes/Julia"]
path = assets/syntaxes/Julia
url = https://github.com/JuliaEditorSupport/Julia-sublime
[submodule "assets/themes/sublime-monokai-extended"]
path = assets/themes/sublime-monokai-extended
url = https://github.com/jonschlinkert/sublime-monokai-extended
[submodule "assets/syntaxes/Docker"]
path = assets/syntaxes/Docker
url = https://github.com/asbjornenge/Docker.tmbundle
[submodule "assets/syntaxes/VimL"]
path = assets/syntaxes/VimL
url = https://github.com/SalGnt/Sublime-VimL
[submodule "assets/syntaxes/INI"]
path = assets/syntaxes/INI
url = https://github.com/clintberry/sublime-text-2-ini
[submodule "assets/syntaxes/CMake"]
path = assets/syntaxes/CMake
url = https://github.com/sharkdp/Sublime-CMakeLists
[submodule "assets/syntaxes/LESS"]
path = assets/syntaxes/LESS
url = https://github.com/danro/LESS-sublime
[submodule "assets/themes/DarkNeon"]
path = assets/themes/DarkNeon
url = https://github.com/RainyDayMedia/DarkNeon
[submodule "assets/themes/github-sublime-theme"]
path = assets/themes/github-sublime-theme
url = https://github.com/AlexanderEkdahl/github-sublime-theme
[submodule "assets/themes/1337-Scheme"]
path = assets/themes/1337-Scheme
url = https://github.com/MarkMichos/1337-Scheme
[submodule "assets/themes/TwoDark"]
path = assets/themes/TwoDark
url = https://github.com/erremauro/TwoDark
+5 -4
View File
@@ -11,6 +11,9 @@ matrix:
env:
- TARGET=x86_64-unknown-linux-musl
- CC_x86_64_unknown_linux_musl=/usr/bin/musl-gcc
- os: linux
rust: stable
env: TARGET=i686-unknown-linux-gnu
- os: osx
rust: stable
env: TARGET=x86_64-apple-darwin
@@ -34,7 +37,7 @@ matrix:
# skip the global install step
install:
- cargo install --debug --force rustfmt-nightly
script: cargo fmt -- --write-mode=diff
script: cargo fmt -- --check
addons:
apt:
@@ -63,9 +66,7 @@ script:
- cargo build --target $TARGET --verbose
- cargo test --target $TARGET --verbose
# Run 'bat' on its own source code and the README
# Piping to 'cat' forces the tty check to fail so that a pager is not used
- cargo run --target $TARGET -- src/main.rs | cat
- cargo run --target $TARGET -- README.md | cat
- cargo run --target $TARGET -- src/main.rs README.md --paging=never
before_deploy:
- bash ci/before_deploy.bash
Generated
+4 -36
View File
@@ -29,27 +29,6 @@ dependencies = [
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "backtrace-sys"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base64"
version = "0.8.0"
@@ -61,7 +40,7 @@ dependencies = [
[[package]]
name = "bat"
version = "0.3.0"
version = "0.4.1"
dependencies = [
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -71,7 +50,7 @@ dependencies = [
"error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"git2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"syntect 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"syntect 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -198,9 +177,6 @@ dependencies = [
name = "error-chain"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"backtrace 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "flate2"
@@ -529,11 +505,6 @@ dependencies = [
"ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc-demangle"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "safemem"
version = "0.2.0"
@@ -609,7 +580,7 @@ dependencies = [
[[package]]
name = "syntect"
version = "2.0.1"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -803,8 +774,6 @@ dependencies = [
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1"
"checksum backtrace 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea58cd16fd6c9d120b5bcb01d63883ae4cc7ba2aed35c1841b862a3c7ef6639"
"checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661"
"checksum base64 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c4a342b450b268e1be8036311e2c613d7f8a7ed31214dff1cc3b60852a3168d"
"checksum bincode 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bda13183df33055cbb84b847becce220d392df502ebe7a4a78d7021771ed94d0"
"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789"
@@ -862,7 +831,6 @@ dependencies = [
"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
"checksum rustc-demangle 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "76d7ba1feafada44f2d38eed812bd2489a03c0f5abb975799251518b68848649"
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637"
"checksum serde 1.0.51 (registry+https://github.com/rust-lang/crates.io-index)" = "21924cc18e5281f232a17c040355fac97732b42cf019c24996a1642bcb169cdb"
@@ -873,7 +841,7 @@ dependencies = [
"checksum stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "15132e0e364248108c5e2c02e3ab539be8d6f5d52a01ca9bbf27ed657316f02b"
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
"checksum syn 0.13.7 (registry+https://github.com/rust-lang/crates.io-index)" = "61b8f1b737f929c6516ba46a3133fd6d5215ad8a62f66760f851f7048aebedfb"
"checksum syntect 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eceb461f684c9e8e00f7a50055698af62c76d5b86e4ad50b4311acd5f655d606"
"checksum syntect 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc8a6f0db88d4afc340522c20d260411e746b2225b257c6b238a75de9d7cec78"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
+11 -3
View File
@@ -7,13 +7,16 @@ license = "MIT/Apache-2.0"
name = "bat"
readme = "README.md"
repository = "https://github.com/sharkdp/bat"
version = "0.3.0"
version = "0.4.1"
exclude = [
"assets/syntaxes/*",
"assets/themes/*",
]
[dependencies]
atty = "0.2.2"
ansi_term = "0.10"
console = "0.6"
error-chain = "0.11"
directories = "0.10"
lazy_static = "1.0"
@@ -23,7 +26,7 @@ default-features = false
features = []
[dependencies.syntect]
version = "2"
version = "2.1"
default-features = false
features = ["parsing", "yaml-load", "dump-load", "dump-create"]
@@ -31,3 +34,8 @@ features = ["parsing", "yaml-load", "dump-load", "dump-create"]
version = "2"
default-features = false
features = ["suggestions", "color", "wrap_help"]
[dependencies.error-chain]
version = "0.11"
default-features = false
features = []
+128 -24
View File
@@ -1,11 +1,18 @@
# bat
<p align="center">
<img src="doc/logo-header.svg" alt="bat - a cat clone with wings"><br>
<a href="Version info"><img src="https://travis-ci.org/sharkdp/bat.svg?branch=master" alt="Build Status"></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.
</p>
[![Build Status](https://travis-ci.org/sharkdp/bat.svg?branch=master)](https://travis-ci.org/sharkdp/bat)
[![Version info](https://img.shields.io/crates/v/bat.svg)](https://crates.io/crates/bat)
A *cat(1)* clone with syntax highlighting and Git integration.
## Features
<p align="center">
<a href="#syntax-highlighting">Key Features</a> •
<a href="#how-to-use">How To Use</a> •
<a href="#installation">Installation</a> •
<a href="#customization">Customization</a> •
<a href="#project-goals-and-alternatives">Project goals, alternatives</a>
</p>
### Syntax highlighting
@@ -21,6 +28,52 @@ languages:
![Git integration example](https://i.imgur.com/2lSW4RE.png)
### Automatic paging
`bat` can pipe its own output to `less` if the output is too large for one screen.
### File concatenation
Oh.. you can also use it to concatenate files :wink:. Whenever
`bat` detects a non-interactive terminal, it will fall back to printing
the plain file contents.
## How to use
Display a single file on the terminal
```bash
> bat README.md
```
Display multiple files at once
```bash
> bat src/*.rs
```
Read from stdin, explicitly specify the language
```bash
> yaml2json .travis.yml | json_pp | bat -l json
```
```bash
> curl -s https://raw.githubusercontent.com/sharkdp/bat/master/src/main.rs | bat -l rs
```
As a replacement for `cat`:
```bash
bat > note.md # quickly create a new file
bat header.md content.md footer.md > document.md
bat -n main.rs # show line numbers (only)
cat f - g # output 'f', then stdin, then 'g'.
```
## Installation
### From binaries
@@ -28,11 +81,10 @@ languages:
Check out the [Release page](https://github.com/sharkdp/bat/releases) for
binary builds and Debian packages.
#### Arch Linux
#### On Arch Linux
On Arch Linux, you can install
[the AUR package](https://aur.archlinux.org/packages/bat/) via yaourt, or
manually:
You can install [the AUR package](https://aur.archlinux.org/packages/bat/)
via yaourt, or manually:
```bash
git clone https://aur.archlinux.org/bat.git
@@ -40,12 +92,35 @@ cd bat
makepkg -si
```
#### On FreeBSD
You can install a precompiled [`bat` package](https://www.freshports.org/textproc/bat) with pkg:
```bash
pkg install bat
```
or build it on your own from the FreeBSD ports:
```bash
cd /usr/ports/textproc/bat
make install
```
#### On macOS
You can install `bat` with [Homebrew](http://braumeister.org/formula/bat):
```bash
brew install bat
```
### From source
If you want to build to compile `bat` from source, you need Rust 1.24 or
higher. You can then use `cargo` to build everything:
``` bash
```bash
cargo install bat
```
@@ -54,6 +129,21 @@ some dependencies to be built.
## Customization
### Highlighting theme
Use `bat --list-themes` to get a list of all available themes for syntax
highlighting. To select the `TwoDark` theme, for example, call `bat` with
the `--theme=TwoDark` option. Use `alias bat="bat --theme=TwoDark"` in your
shells startup file to make the change permanent.
### Output style
You can use the `--style` option to control the appearance of `bat`s output.
You can use `--style=numbers,changes`, for example, to show only Git changes
and line numbers but no grid and no file header.
### Add new syntaxes and highlighting themes
`bat` uses the excellent [`syntect`](https://github.com/trishume/syntect/)
library for syntax highlighting. `syntect` can read any
[Sublime Text `.sublime-syntax` file](https://www.sublimetext.com/docs/3/syntax.html)
@@ -63,40 +153,54 @@ To build your own language-set and theme, follow these steps:
Create a folder with a syntax highlighting theme:
``` bash
```bash
BAT_CONFIG_DIR="$(bat cache --config-dir)"
mkdir -p "$BAT_CONFIG_DIR/themes"
cd "$BAT_CONFIG_DIR/themes"
# Download a theme, for example:
git clone https://github.com/jonschlinkert/sublime-monokai-extended
git clone https://github.com/greggb/sublime-snazzy
# Create a 'Default.tmTheme' link
ln -s "sublime-monokai-extended/Monokai Extended.tmTheme" Default.tmTheme
# Create a link for the default theme
ln -sf "sublime-snazzy/Sublime Snazzy.tmTheme" Default.tmTheme
```
Create a folder with language definition files:
``` bash
mkdir -p "$BAT_CONFIG_DIR/syntax"
cd "$BAT_CONFIG_DIR/syntax"
```bash
mkdir -p "$BAT_CONFIG_DIR/syntaxes"
cd "$BAT_CONFIG_DIR/syntaxes"
# Download some language definition files, for example:
git clone https://github.com/sublimehq/Packages/
rm -rf Packages/Markdown
git clone https://github.com/jonschlinkert/sublime-markdown-extended
git clone https://github.com/sublimehq/Packages
git clone https://github.com/danro/LESS-sublime
```
Finally, use the following command to parse all these files into a binary
cache:
``` bash
```bash
bat cache --init
```
Use `bat --list-languages` and `bat --list-themes` to check if all languages and themes are
available.
If you ever want to go back to the default settings, call:
``` bash
```bash
bat cache --clear
```
## Project goals and alternatives
`bat` tries to achieve the following goals:
- Provide beautiful, advanced syntax highlighting
- Integrate with Git to show file modifications
- Be a drop-in replacement for (POSIX) `cat`
- Offer a user-friendly command-line interface
There are a lot of alternatives, if you are looking for similar programs. See
[this document](doc/alternatives.md) for a comparison.
+2
View File
@@ -0,0 +1,2 @@
syntaxes/*
themes/*
+31
View File
@@ -0,0 +1,31 @@
## Syntax Highlighting in bat
`bat` uses the [syntect](https://github.com/trishume/syntect) library to highlight source
code. As a basis, syntect uses [Sublime Text](https://www.sublimetext.com/) syntax definitions
in the `.sublime-syntax` format.
In order to add new syntaxes to `bat`, follow these steps:
1. Find a Sublime Text syntax for the given language, preferably in a separate Git repository
which can be included as a submodule (under `assets/syntaxes`).
2. If the Sublime Text syntax is only available as a `.tmLanguage` file, open the file in
Sublime Text and convert it to a `.sublime-syntax` file via *Tools* -> *Developer* ->
*New Syntax from XXX.tmLanguage...*. Save the new file in the `assets/syntaxes` folder.
3. Run the `create.sh` script. It calls `bat cache --init` to parse all available
`.sublime-syntax` files and serialize them to a `syntaxes.bin` file (in this folder).
4. Re-compile `bat`. At compilation time, the `syntaxes.bin` file will be stored inside the
`bat` binary.
### Troubleshooting
Make sure that the local cache does not interfere with the internally stored syntaxes and
themes (`bat cache --clear`).
### Manual modifications
The following files have been manually modified after converting from a `.tmLanguage` file:
* `VimL.sublime-syntax` => added `.vimrc` file type.
+3 -34
View File
@@ -1,36 +1,5 @@
set -e
#!/bin/bash
THEME_FOLDER="$HOME/.config/bat/themes"
SYNTAX_FOLDER="$HOME/.config/bat/syntax"
ASSET_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
if [ ! -e "$THEME_FOLDER" ]; then
mkdir -p "$THEME_FOLDER"
(
cd "$THEME_FOLDER"
git clone https://github.com/jonschlinkert/sublime-monokai-extended
ln -s "sublime-monokai-extended/Monokai Extended.tmTheme" Default.tmTheme
)
fi
if [ ! -e "$SYNTAX_FOLDER" ]; then
mkdir -p "$SYNTAX_FOLDER"
(
cd "$SYNTAX_FOLDER"
git clone https://github.com/sublimehq/Packages/
# Patch JavaScript syntax
sed -i -e 's/{{identifier_break}}+/{{identifier_break}}/' Packages/JavaScript/JavaScript.sublime-syntax
# Use extended Markdown syntax
rm -rf Packages/Markdown
git clone https://github.com/jonschlinkert/sublime-markdown-extended
# Add additional sxntax definitions
git clone https://github.com/princemaple/elixir-sublime-syntax/
git clone https://github.com/sharkdp/sublime_toml_highlighting
)
fi
bat init-cache
cp "$HOME/.cache/bat"/* .
bat cache --init --source="$ASSET_DIR" --target="$ASSET_DIR"
BIN
View File
Binary file not shown.
Binary file not shown.
Submodule assets/syntaxes/INI added at 8dd3e316ad
+43
View File
@@ -0,0 +1,43 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: INI
file_extensions:
- ini
- INI
- inf
- INF
- reg
- REG
- lng
- cfg
- CFG
- url
- URL
- .editorconfig
scope: source.ini
contexts:
main:
- match: ^\s*(;|#).*$\n?
scope: comment.line.semicolon.ini
captures:
1: punctuation.definition.comment.ini
- match: '^\s*(\[)(.*?)(\])'
scope: meta.tag.section.ini
captures:
1: punctuation.definition.section.ini
2: entity.section.ini
3: punctuation.definition.section.ini
- match: '^(\s*(["'']?)(.+?)(\2)\s*(=))?\s*((["'']?)(.*?)(\7))\s*(;.*)?$\n?'
scope: meta.declaration.ini
captures:
1: meta.property.ini
2: punctuation.definition.quote.ini
3: keyword.name.ini
4: punctuation.definition.quote.ini
5: punctuation.definition.equals.ini
6: meta.value.ini
7: punctuation.definition.quote.ini
8: string.name.value.ini
9: punctuation.definition.quote.ini
10: comment.declarationline.semicolon.ini
+91
View File
@@ -0,0 +1,91 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: VimL
file_extensions:
- vim
- .vimrc
scope: source.viml
contexts:
main:
- include: comment
- include: string_quoted_double
- include: string_quoted_single
- include: string_regex
- include: inline_comment
- include: number_int
- include: number_hex
- include: keyword
- include: support_function
- include: support_variable
- include: support_type
- include: function_params
- include: function_definition
- include: function_call
- include: function
- include: variable
- include: expr
comment:
- match: ^\s*".*$
scope: comment.line.quotes.viml
captures:
1: punctuation.definition.comment.vim
expr:
- match: (\|\||&&|==(\?|#)?|(!|>|<)=(#|\?)?|(=|!)~(#|\?)?|(>|<)(#|\?)is|isnot|\.|\*|\\|%)
scope: storage.function.viml
function:
- match: \b(fu(n|nction)?|end(f|fu|fun|function)?)\b
scope: storage.function.viml
function_call:
- match: '(([sgbwtl]|)?:?[0-9a-zA-Z_#]+)(?=\()'
scope: support.function.any-method
function_definition:
- match: '^\s*(function)\s*!?\s+(?=(s:)?[0-9a-zA-Z_#]+\s*\()'
captures:
1: storage.function.viml
push:
- meta_scope: meta.function.viml
- match: (\()
captures:
1: punctuation.definition.parameters.begin.viml
pop: true
- match: "(s:)?[0-9a-zA-Z_#]+"
scope: entity.name.function.viml
function_params:
- match: '-\w+='
scope: support.type.viml
inline_comment:
- match: '(?!\$)(")(?!\{).*$\n?'
scope: comment.line.quotes.viml
captures:
1: punctuation.definition.comment.vim
keyword:
- match: \b(if|while|for|try|return|throw|end(if|for|while|try)?|au(g|group)|else(if|)?|do|in|catch|finally|:)\b
scope: keyword.control.viml
number_hex:
- match: "#[0-9a-f]{6}"
scope: constant.numeric.hex
number_int:
- match: '-?\d+'
scope: constant.numeric.integer
string_quoted_double:
- match: '"(\\\\|\\"|\n[^\S\n]*\\|[^\n"])*"'
scope: string.quoted.double.viml
string_quoted_single:
- match: '''(''''|\n[^\S\n]*\\|[^\n''])*'''
scope: string.quoted.single.viml
string_regex:
- match: '/(\\\\|\\/|\n[^\S\n]*\\|[^\n/])*/'
scope: string.regexp.viml
support_function:
- match: \b(set(local|global)?|let|command|filetype|colorscheme|\w*map|\w*a(b|brev)?|syn|exe(c|cute)?|ec(ho|)?|au(tocmd|)?)\b
scope: support.function.viml
support_type:
- match: <.*?>
scope: support.type.viml
support_variable:
- match: '\b(am(enu|)?|(hl|inc)?search|[Bb]uf([Nn]ew[Ff]ile|[Rr]ead)?|[Ff]ile[Tt]ype)\b'
scope: support.variable.viml
variable:
- match: '([sSgGbBwWlLaAvV]:|@|$|&(?!&))\w*'
scope: variable.other.viml
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
sublime-monokai-extended/Monokai Extended.tmTheme
+20
View File
@@ -0,0 +1,20 @@
# Alternatives
The following table tries to give an overview *from `bat`s perspective*, i.e. we only compare
categories which are relevant for `bat`. Some of these projects have completely different goals and
if you are not looking for a program like `bat`, this comparison might not be for you.
| | 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: |
| 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 |
| Extensible (languages, themes) | :heavy_check_mark: | (:heavy_check_mark:) | (:heavy_check_mark:) | :x: | (:heavy_check_mark:) | :x: | :x: | :x: |
| Advanced highlighting (e.g. nested syntaxes) | :heavy_check_mark: | :heavy_check_mark: | (:heavy_check_mark:) ? | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: |
| Execution time [ms] (`jquery-3.3.1.js`) | 624 | 789 | 400 | 80 | 300 | 316 | 157 | 695 |
| Execution time [ms] (`miniz.c`) | 66 | 656 | 26 | 8 | 53 | 141 | 75 | 254 |
| Execution time [ms] (370 kB XML file) | 238 | 487 | 129 | 111 | 110 | 339 | 147 | 359 |
If you think that some entries in this table are outdated or wrong, please open a ticket or pull
request.
+326
View File
@@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1000"
height="286.64285"
viewBox="0 0 1000 286.64285"
version="1.1"
xml:space="preserve"
style="clip-rule:evenodd;fill-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41420996"
id="svg1285"
sodipodi:docname="logo-header.svg"
inkscape:version="0.92.2 2405546, 2018-03-11"><metadata
id="metadata1289"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1175"
id="namedview1287"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="0.82573726"
inkscape:cx="438.92362"
inkscape:cy="320.87266"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="svg1285"
showguides="true"
inkscape:guide-bbox="true" />
<rect
style="opacity:1;fill:#f2f2f2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.14294769px;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
id="rect1929"
width="1000"
height="286.64285"
x="0"
y="0" /><g
id="g929"
transform="translate(0,13.321425)"><g
transform="matrix(5.1207879,0,0,4.741146,45.483811,-7057.152)"
id="g1168">
<path
d="m 77.792,1533.05 c 0,0.32 -0.064,0.61 -0.192,0.88 -0.128,0.27 -0.3,0.5 -0.516,0.7 -0.216,0.19 -0.468,0.34 -0.756,0.45 -0.288,0.11 -0.596,0.16 -0.924,0.16 h -4.02 v -8.52 h 4.104 c 0.296,0 0.564,0.07 0.804,0.19 0.24,0.13 0.444,0.3 0.612,0.5 0.168,0.21 0.298,0.44 0.39,0.7 0.092,0.26 0.138,0.52 0.138,0.78 0,0.44 -0.108,0.83 -0.324,1.19 -0.216,0.35 -0.516,0.62 -0.9,0.8 0.488,0.14 0.874,0.41 1.158,0.8 0.284,0.39 0.426,0.85 0.426,1.37 z m -0.84,-0.15 c 0,-0.21 -0.036,-0.41 -0.108,-0.6 -0.072,-0.2 -0.174,-0.37 -0.306,-0.52 -0.132,-0.15 -0.288,-0.27 -0.468,-0.36 -0.18,-0.09 -0.374,-0.14 -0.582,-0.14 h -3.264 v 3.23 h 3.18 c 0.216,0 0.418,-0.04 0.606,-0.13 0.188,-0.09 0.352,-0.21 0.492,-0.35 0.14,-0.15 0.25,-0.33 0.33,-0.52 0.08,-0.2 0.12,-0.4 0.12,-0.61 z m -4.728,-5.45 v 3.15 h 2.928 c 0.208,0 0.402,-0.04 0.582,-0.13 0.18,-0.09 0.334,-0.21 0.462,-0.35 0.128,-0.14 0.228,-0.31 0.3,-0.5 0.072,-0.19 0.108,-0.38 0.108,-0.59 0,-0.22 -0.034,-0.42 -0.102,-0.62 -0.068,-0.19 -0.164,-0.35 -0.288,-0.49 -0.124,-0.14 -0.27,-0.26 -0.438,-0.34 -0.168,-0.08 -0.352,-0.13 -0.552,-0.13 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1162"
inkscape:connector-curvature="0" />
<path
d="m 88.436,1526.72 h 0.708 l 3.54,8.52 h -0.888 l -1.104,-2.66 h -3.828 l -1.092,2.66 h -0.9 z m 2.064,5.2 -1.716,-4.21 -1.74,4.21 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1164"
inkscape:connector-curvature="0" />
<path
d="m 105.716,1527.47 h -3.036 v 7.77 h -0.84 v -7.77 h -3.036 v -0.75 h 6.912 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1166"
inkscape:connector-curvature="0" />
</g><g
transform="translate(1.9578e-5)"
id="g6103"><g
id="g1172"
transform="matrix(0.31374232,0,0,0.31374232,-949.56013,-170.6326)">
<path
inkscape:connector-curvature="0"
id="path1170"
style="fill:url(#_Linear1)"
d="m 4399.57,773.907 c 0,0 35.33,20.718 42.25,24.779 13.1,7.683 47.96,27.832 64.24,37.232 1.77,1.027 2.87,2.923 2.87,4.975 0.01,2.053 -1.08,3.952 -2.85,4.986 -27.1,15.803 -110.2,64.254 -128.26,74.788 -2.16,1.254 -4.82,1.237 -6.95,-0.046 -13.57,-8.147 -62.93,-37.791 -75.02,-44.875 -16.93,-9.911 -37.27,-21.84 -37.27,-21.84 z" />
</g><g
id="g1176"
transform="matrix(0.31374232,0,0,0.31374232,-949.56013,-170.6326)">
<path
inkscape:connector-curvature="0"
id="path1174"
style="fill:#31369e"
d="m 4269.48,860.305 c -54.5,-31.918 -88.34,-51.732 -117.18,-68.621 -3.98,-2.335 -6.43,-6.608 -6.44,-11.228 0,-4.619 2.45,-8.893 6.44,-11.229 l 319.41,-187.171 c 2.02,-1.186 4.52,-1.198 6.56,-0.032 2.03,1.166 3.29,3.333 3.29,5.678 0,29.472 0,107.932 0,138.732 0,7.842 -4.16,15.094 -10.94,19.049 -13.32,7.783 -54.43,31.465 -67.73,39.233 0,0 -133.3,75.654 -133.41,75.589 z" />
</g><g
id="g1180"
transform="matrix(0.31374232,0,0,0.31374232,-949.56013,-170.6326)">
<path
inkscape:connector-curvature="0"
id="path1178"
style="fill:#1d2167"
d="m 4604.42,705.593 85.54,34.367 v -63.802 c 0,-2.628 -1.37,-5.066 -3.62,-6.431 -2.24,-1.365 -5.04,-1.459 -7.37,-0.247 -5.93,3.078 -74.55,36.113 -74.55,36.113 z" />
</g><g
id="g1184"
transform="matrix(0.31374232,0,0,0.31374232,-949.56013,-170.6326)">
<path
inkscape:connector-curvature="0"
id="path1182"
style="fill:url(#_Linear2)"
d="m 4976.91,704.019 c 0,0 -8.53,-4.983 -8.56,-5 -79.92,-46.83 -170.16,-99.71 -199.6,-116.963 -2.02,-1.186 -4.52,-1.198 -6.56,-0.032 -2.04,1.166 -3.29,3.333 -3.29,5.678 0,29.472 0,107.932 0,138.732 0,7.842 4.16,15.094 10.93,19.049 13.33,7.783 68.33,39.913 68.33,39.913 z" />
</g><g
id="g1188"
transform="matrix(0.31374232,0,0,0.31374232,-949.56013,-170.6326)">
<path
inkscape:connector-curvature="0"
id="path1186"
style="fill:url(#_Linear3)"
d="m 4968.44,699.073 c 0,0 -235.66,138.235 -263.83,154.762 -3.01,1.764 -6.73,1.783 -9.76,0.05 -3.02,-1.733 -4.89,-4.953 -4.89,-8.44 V 731.796 c 0,0 -111.16,-50.383 -128.47,-59.371 -2.33,-1.212 -4.9781,-0.84921 -7.37,0.247 -1.9592,0.89788 -3.62,3.803 -3.62,6.431 v 166.342 c 0,3.487 0.43,15.045 -12.02,22.226 -2.41,1.392 -130.21,75.278 -130.21,75.278 l 211.99,122.751 c 0,0 377.71,-221.201 467.9,-274.016 3.98,-2.335 6.43,-6.608 6.43,-11.228 0,-4.619 -2.45,-8.893 -6.43,-11.229 -24.64,-14.438 -119.72,-70.154 -119.72,-70.154"
sodipodi:nodetypes="cccsccsssccccscc" />
</g></g><g
transform="matrix(0.18627124,0,0,0.22786575,340.43532,-286.36087)"
id="g1262">
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1192">
<path
d="m 374.487,2270.67 h 4.788 l 23.94,57.61 h -6.006 l -7.465,-18.01 h -25.887 l -7.385,18.01 h -6.086 z m 13.958,35.14 -11.604,-28.49 -11.767,28.49 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1190"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1196">
<path
d="m 421.293,2307.02 c 0,-2.97 0.514,-5.78 1.542,-8.44 1.028,-2.65 2.475,-4.96 4.342,-6.94 1.866,-1.97 4.098,-3.52 6.695,-4.66 2.596,-1.14 5.464,-1.7 8.602,-1.7 4.003,0 7.492,0.9 10.468,2.71 2.975,1.82 5.221,4.26 6.735,7.35 l -5.356,1.7 c -1.19,-2.16 -2.853,-3.85 -4.99,-5.07 -2.137,-1.22 -4.504,-1.83 -7.101,-1.83 -2.164,0 -4.179,0.44 -6.046,1.3 -1.866,0.87 -3.489,2.05 -4.869,3.53 -1.379,1.49 -2.461,3.26 -3.246,5.32 -0.784,2.05 -1.176,4.3 -1.176,6.73 0,2.38 0.405,4.63 1.217,6.74 0.811,2.11 1.92,3.94 3.327,5.48 1.407,1.54 3.043,2.76 4.91,3.65 1.866,0.89 3.854,1.34 5.964,1.34 1.353,0 2.692,-0.19 4.017,-0.57 1.326,-0.38 2.556,-0.91 3.692,-1.58 1.137,-0.68 2.11,-1.45 2.922,-2.32 0.811,-0.86 1.379,-1.78 1.704,-2.75 l 5.437,1.62 c -0.595,1.51 -1.461,2.91 -2.597,4.18 -1.136,1.27 -2.475,2.38 -4.017,3.32 -1.541,0.95 -3.246,1.68 -5.112,2.2 -1.867,0.51 -3.801,0.77 -5.802,0.77 -3.084,0 -5.924,-0.59 -8.521,-1.75 -2.597,-1.16 -4.842,-2.76 -6.736,-4.79 -1.893,-2.03 -3.367,-4.36 -4.422,-7.02 -1.055,-2.65 -1.583,-5.49 -1.583,-8.52 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1194"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1200">
<path
d="m 476.962,2329.1 c -2.001,0 -3.854,-0.34 -5.558,-1.02 -1.705,-0.67 -3.192,-1.61 -4.464,-2.8 -1.271,-1.19 -2.258,-2.58 -2.962,-4.18 -0.703,-1.59 -1.055,-3.31 -1.055,-5.15 0,-1.84 0.433,-3.54 1.299,-5.11 0.865,-1.57 2.083,-2.92 3.652,-4.06 1.568,-1.14 3.435,-2.02 5.599,-2.64 2.164,-0.62 4.544,-0.93 7.141,-0.93 2.164,0 4.355,0.19 6.573,0.57 2.218,0.38 4.193,0.92 5.924,1.62 v -3.81 c 0,-3.74 -1.055,-6.7 -3.165,-8.89 -2.11,-2.19 -5.031,-3.29 -8.764,-3.29 -2.164,0 -4.382,0.44 -6.654,1.3 -2.272,0.87 -4.572,2.11 -6.898,3.74 l -1.948,-3.66 c 5.465,-3.67 10.766,-5.51 15.906,-5.51 5.302,0 9.467,1.48 12.497,4.46 3.03,2.97 4.544,7.09 4.544,12.33 v 19.07 c 0,1.52 0.677,2.28 2.029,2.28 v 4.86 c -0.919,0.17 -1.623,0.25 -2.11,0.25 -1.406,0 -2.502,-0.35 -3.286,-1.06 -0.785,-0.7 -1.204,-1.67 -1.258,-2.92 l -0.162,-3.33 c -1.948,2.55 -4.396,4.5 -7.345,5.85 -2.948,1.35 -6.126,2.03 -9.535,2.03 z m 1.299,-4.22 c 2.867,0 5.504,-0.54 7.912,-1.63 2.407,-1.08 4.233,-2.51 5.478,-4.3 0.486,-0.48 0.852,-1.01 1.095,-1.58 0.244,-0.57 0.365,-1.09 0.365,-1.58 v -6.9 c -1.839,-0.7 -3.746,-1.24 -5.721,-1.62 -1.975,-0.38 -3.99,-0.57 -6.046,-0.57 -3.895,0 -7.06,0.81 -9.494,2.43 -2.435,1.63 -3.652,3.76 -3.652,6.41 0,1.3 0.257,2.52 0.771,3.66 0.514,1.13 1.217,2.12 2.11,2.96 0.893,0.84 1.961,1.5 3.205,1.99 1.245,0.48 2.57,0.73 3.977,0.73 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1198"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1204">
<path
d="m 528.158,2326.26 c -0.325,0.16 -0.798,0.38 -1.42,0.65 -0.622,0.27 -1.339,0.54 -2.151,0.81 -0.811,0.27 -1.731,0.51 -2.759,0.73 -1.028,0.21 -2.11,0.32 -3.246,0.32 -1.19,0 -2.326,-0.16 -3.408,-0.49 -1.082,-0.32 -2.029,-0.82 -2.84,-1.5 -0.812,-0.67 -1.461,-1.52 -1.948,-2.55 -0.487,-1.03 -0.73,-2.22 -0.73,-3.57 v -30.27 h -5.843 v -4.38 h 5.843 v -14.29 h 5.518 v 14.29 h 9.738 v 4.38 h -9.738 v 28.73 c 0.108,1.56 0.663,2.71 1.664,3.44 1,0.73 2.15,1.1 3.448,1.1 1.569,0 2.949,-0.26 4.139,-0.77 1.19,-0.52 1.948,-0.88 2.272,-1.1 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1202"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1208">
<path
d="m 547.959,2307.02 c 0,-2.97 0.514,-5.78 1.542,-8.44 1.027,-2.65 2.475,-4.96 4.341,-6.94 1.866,-1.97 4.098,-3.52 6.695,-4.66 2.597,-1.14 5.464,-1.7 8.602,-1.7 4.003,0 7.493,0.9 10.468,2.71 2.976,1.82 5.221,4.26 6.736,7.35 l -5.356,1.7 c -1.19,-2.16 -2.854,-3.85 -4.991,-5.07 -2.137,-1.22 -4.504,-1.83 -7.101,-1.83 -2.164,0 -4.179,0.44 -6.045,1.3 -1.867,0.87 -3.49,2.05 -4.869,3.53 -1.38,1.49 -2.462,3.26 -3.246,5.32 -0.785,2.05 -1.177,4.3 -1.177,6.73 0,2.38 0.406,4.63 1.217,6.74 0.812,2.11 1.921,3.94 3.327,5.48 1.407,1.54 3.044,2.76 4.91,3.65 1.866,0.89 3.855,1.34 5.965,1.34 1.352,0 2.691,-0.19 4.016,-0.57 1.326,-0.38 2.557,-0.91 3.693,-1.58 1.136,-0.68 2.11,-1.45 2.921,-2.32 0.812,-0.86 1.38,-1.78 1.704,-2.75 l 5.437,1.62 c -0.595,1.51 -1.46,2.91 -2.596,4.18 -1.136,1.27 -2.475,2.38 -4.017,3.32 -1.542,0.95 -3.246,1.68 -5.113,2.2 -1.866,0.51 -3.8,0.77 -5.802,0.77 -3.084,0 -5.924,-0.59 -8.521,-1.75 -2.597,-1.16 -4.842,-2.76 -6.735,-4.79 -1.894,-2.03 -3.368,-4.36 -4.423,-7.02 -1.055,-2.65 -1.582,-5.49 -1.582,-8.52 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1206"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1212">
<path
d="m 593.24,2269.05 h 5.519 v 49.66 c 0,1.57 0.432,2.8 1.298,3.69 0.866,0.89 2.083,1.34 3.652,1.34 0.595,0 1.298,-0.08 2.11,-0.24 0.811,-0.16 1.569,-0.38 2.272,-0.65 l 0.974,4.46 c -1.028,0.43 -2.286,0.79 -3.774,1.06 -1.487,0.27 -2.772,0.4 -3.854,0.4 -2.543,0 -4.545,-0.72 -6.005,-2.15 -1.461,-1.43 -2.192,-3.45 -2.192,-6.04 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1210"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1216">
<path
d="m 630.651,2329.1 c -3.084,0 -5.897,-0.59 -8.44,-1.75 -2.543,-1.16 -4.747,-2.74 -6.614,-4.75 -1.866,-2 -3.313,-4.32 -4.341,-6.97 -1.028,-2.66 -1.542,-5.44 -1.542,-8.36 0,-2.98 0.527,-5.79 1.582,-8.44 1.055,-2.65 2.516,-4.98 4.383,-6.98 1.866,-2 4.071,-3.6 6.613,-4.79 2.543,-1.19 5.329,-1.78 8.359,-1.78 3.029,0 5.829,0.59 8.399,1.78 2.57,1.19 4.788,2.79 6.654,4.79 1.867,2 3.327,4.33 4.382,6.98 1.055,2.65 1.583,5.46 1.583,8.44 0,2.92 -0.514,5.7 -1.542,8.36 -1.028,2.65 -2.475,4.97 -4.342,6.97 -1.866,2.01 -4.084,3.59 -6.654,4.75 -2.57,1.16 -5.397,1.75 -8.48,1.75 z m -15.338,-21.75 c 0,2.38 0.406,4.58 1.218,6.61 0.811,2.03 1.907,3.82 3.286,5.36 1.38,1.54 3.003,2.74 4.869,3.61 1.867,0.86 3.855,1.3 5.965,1.3 2.11,0 4.098,-0.45 5.964,-1.34 1.867,-0.89 3.503,-2.11 4.91,-3.65 1.406,-1.54 2.516,-3.34 3.327,-5.4 0.812,-2.06 1.217,-4.27 1.217,-6.65 0,-2.33 -0.405,-4.53 -1.217,-6.62 -0.811,-2.08 -1.921,-3.89 -3.327,-5.43 -1.407,-1.55 -3.043,-2.76 -4.91,-3.66 -1.866,-0.89 -3.854,-1.34 -5.964,-1.34 -2.11,0 -4.085,0.45 -5.924,1.34 -1.84,0.9 -3.463,2.13 -4.869,3.7 -1.407,1.56 -2.516,3.39 -3.327,5.47 -0.812,2.09 -1.218,4.32 -1.218,6.7 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1214"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1220">
<path
d="m 694.029,2328.28 h -5.518 v -23.61 c 0,-5.03 -0.744,-8.68 -2.232,-10.95 -1.488,-2.28 -3.773,-3.41 -6.857,-3.41 -1.623,0 -3.246,0.29 -4.869,0.89 -1.623,0.59 -3.124,1.43 -4.504,2.52 -1.379,1.08 -2.61,2.36 -3.692,3.85 -1.082,1.49 -1.867,3.1 -2.354,4.83 v 25.88 h -5.518 v -42.27 h 5.032 v 9.57 c 1.731,-3.08 4.206,-5.57 7.425,-7.46 3.219,-1.9 6.722,-2.84 10.509,-2.84 2.326,0 4.301,0.43 5.924,1.29 1.623,0.87 2.921,2.09 3.895,3.65 0.974,1.57 1.677,3.5 2.11,5.77 0.433,2.27 0.649,4.79 0.649,7.54 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1218"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1224">
<path
d="m 721.62,2329.1 c -3.084,0 -5.91,-0.59 -8.48,-1.75 -2.57,-1.16 -4.801,-2.76 -6.695,-4.79 -1.893,-2.03 -3.368,-4.36 -4.423,-7.02 -1.055,-2.65 -1.582,-5.49 -1.582,-8.52 0,-2.97 0.527,-5.77 1.582,-8.4 1.055,-2.62 2.516,-4.92 4.382,-6.89 1.867,-1.98 4.099,-3.55 6.695,-4.71 2.597,-1.16 5.41,-1.74 8.44,-1.74 3.084,0 5.91,0.58 8.48,1.74 2.57,1.16 4.775,2.74 6.614,4.75 1.839,2 3.273,4.3 4.301,6.89 1.028,2.6 1.542,5.36 1.542,8.28 0,0.44 0,0.87 0,1.3 0,0.43 -0.027,0.73 -0.081,0.89 h -36.193 c 0.162,2.28 0.689,4.37 1.582,6.29 0.893,1.92 2.042,3.59 3.449,4.99 1.407,1.41 3.016,2.5 4.828,3.29 1.813,0.78 3.747,1.18 5.803,1.18 1.352,0 2.705,-0.19 4.057,-0.57 1.353,-0.38 2.597,-0.89 3.733,-1.54 1.136,-0.65 2.164,-1.45 3.084,-2.4 0.919,-0.94 1.623,-1.99 2.11,-3.12 l 4.787,1.3 c -0.649,1.57 -1.568,2.99 -2.759,4.26 -1.19,1.27 -2.569,2.38 -4.138,3.32 -1.569,0.95 -3.3,1.68 -5.194,2.2 -1.893,0.51 -3.868,0.77 -5.924,0.77 z m 15.581,-24.19 c -0.162,-2.27 -0.69,-4.34 -1.582,-6.21 -0.893,-1.86 -2.029,-3.47 -3.409,-4.82 -1.379,-1.35 -2.975,-2.41 -4.788,-3.17 -1.812,-0.76 -3.746,-1.13 -5.802,-1.13 -2.056,0 -4.003,0.37 -5.843,1.13 -1.839,0.76 -3.449,1.82 -4.828,3.17 -1.38,1.35 -2.489,2.97 -3.327,4.87 -0.839,1.89 -1.339,3.95 -1.502,6.16 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1222"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1228">
<path
d="m 818.344,2286.01 h 5.437 l -18.178,42.27 h -4.544 l -9.089,-21.58 -9.008,21.58 h -4.544 l -18.178,-42.27 h 5.356 l 15.418,36.59 8.034,-19.31 -7.222,-17.2 h 4.626 l 5.518,13.79 5.518,-13.79 h 4.707 l -7.223,17.2 8.034,19.31 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1226"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1232">
<path
d="m 828.65,2328.28 v -42.27 h 5.518 v 42.27 z m 0,-51.12 v -8.11 h 5.518 v 8.11 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1230"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1236">
<path
d="m 864.193,2326.26 c -0.324,0.16 -0.798,0.38 -1.42,0.65 -0.622,0.27 -1.339,0.54 -2.15,0.81 -0.812,0.27 -1.731,0.51 -2.759,0.73 -1.028,0.21 -2.11,0.32 -3.246,0.32 -1.19,0 -2.327,-0.16 -3.409,-0.49 -1.082,-0.32 -2.028,-0.82 -2.84,-1.5 -0.811,-0.67 -1.461,-1.52 -1.947,-2.55 -0.487,-1.03 -0.731,-2.22 -0.731,-3.57 v -30.27 h -5.843 v -4.38 h 5.843 v -14.29 h 5.518 v 14.29 h 9.738 v 4.38 h -9.738 v 28.73 c 0.109,1.56 0.663,2.71 1.664,3.44 1.001,0.73 2.15,1.1 3.449,1.1 1.569,0 2.948,-0.26 4.139,-0.77 1.19,-0.52 1.947,-0.88 2.272,-1.1 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1234"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1240">
<path
d="m 904.201,2328.28 h -5.519 v -23.61 c 0,-4.81 -0.825,-8.41 -2.475,-10.79 -1.65,-2.38 -4.071,-3.57 -7.263,-3.57 -1.569,0 -3.124,0.31 -4.666,0.93 -1.542,0.62 -2.989,1.47 -4.341,2.56 -1.353,1.08 -2.53,2.35 -3.53,3.81 -1.001,1.46 -1.745,3.06 -2.232,4.79 v 25.88 h -5.518 v -59.23 h 5.518 v 26.53 c 1.677,-3.19 4.003,-5.71 6.979,-7.55 2.975,-1.83 6.194,-2.75 9.657,-2.75 2.434,0 4.49,0.44 6.167,1.33 1.677,0.9 3.057,2.14 4.139,3.74 1.082,1.59 1.866,3.51 2.353,5.76 0.487,2.24 0.731,4.72 0.731,7.42 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1238"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1244">
<path
d="m 983.314,2286.01 h 5.437 l -18.177,42.27 h -4.545 l -9.088,-21.58 -9.008,21.58 h -4.545 l -18.177,-42.27 h 5.356 l 15.418,36.59 8.034,-19.31 -7.222,-17.2 h 4.625 l 5.519,13.79 5.518,-13.79 h 4.707 l -7.223,17.2 8.034,19.31 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1242"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1248">
<path
d="m 993.62,2328.28 v -42.27 h 5.519 v 42.27 z m 0,-51.12 v -8.11 h 5.519 v 8.11 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1246"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1252">
<path
d="m 1044.42,2328.28 h -5.52 v -23.61 c 0,-5.03 -0.74,-8.68 -2.23,-10.95 -1.49,-2.28 -3.77,-3.41 -6.86,-3.41 -1.62,0 -3.24,0.29 -4.87,0.89 -1.62,0.59 -3.12,1.43 -4.5,2.52 -1.38,1.08 -2.61,2.36 -3.69,3.85 -1.08,1.49 -1.87,3.1 -2.36,4.83 v 25.88 h -5.51 v -42.27 h 5.03 v 9.57 c 1.73,-3.08 4.2,-5.57 7.42,-7.46 3.22,-1.9 6.73,-2.84 10.51,-2.84 2.33,0 4.3,0.43 5.93,1.29 1.62,0.87 2.92,2.09 3.89,3.65 0.97,1.57 1.68,3.5 2.11,5.77 0.43,2.27 0.65,4.79 0.65,7.54 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1250"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1256">
<path
d="m 1070.71,2328.85 c -2.92,0 -5.58,-0.61 -7.99,-1.82 -2.41,-1.22 -4.49,-2.83 -6.25,-4.83 -1.76,-2 -3.12,-4.3 -4.1,-6.9 -0.97,-2.6 -1.46,-5.27 -1.46,-8.03 0,-2.92 0.48,-5.72 1.42,-8.4 0.95,-2.68 2.29,-5.02 4.02,-7.02 1.73,-2 3.8,-3.6 6.21,-4.79 2.4,-1.19 5.07,-1.78 7.99,-1.78 3.63,0 6.76,0.9 9.41,2.71 2.66,1.82 4.93,4.1 6.82,6.86 v -8.84 h 4.95 v 42.44 c 0,2.92 -0.55,5.5 -1.66,7.75 -1.11,2.24 -2.6,4.12 -4.47,5.64 -1.86,1.51 -4.05,2.65 -6.57,3.4 -2.51,0.76 -5.18,1.14 -7.99,1.14 -4.87,0 -8.79,-0.86 -11.77,-2.6 -2.97,-1.73 -5.38,-4.05 -7.22,-6.97 l 3.41,-2.68 c 1.67,2.7 3.89,4.69 6.65,5.96 2.76,1.27 5.74,1.91 8.93,1.91 2.05,0 4,-0.28 5.84,-0.85 1.84,-0.57 3.45,-1.41 4.83,-2.52 1.38,-1.11 2.47,-2.51 3.29,-4.22 0.81,-1.7 1.21,-3.69 1.21,-5.96 v -8.36 c -1.62,2.7 -3.84,4.84 -6.65,6.41 -2.81,1.57 -5.76,2.35 -8.85,2.35 z m 1.55,-4.62 c 1.62,0 3.21,-0.3 4.78,-0.89 1.57,-0.6 2.99,-1.4 4.26,-2.4 1.28,-1 2.34,-2.12 3.21,-3.37 0.86,-1.24 1.43,-2.51 1.7,-3.81 v -13.07 c -0.59,-1.51 -1.4,-2.92 -2.43,-4.22 -1.03,-1.29 -2.19,-2.4 -3.49,-3.32 -1.3,-0.92 -2.69,-1.65 -4.18,-2.19 -1.49,-0.55 -2.99,-0.82 -4.5,-0.82 -2.39,0 -4.51,0.5 -6.37,1.5 -1.87,1.01 -3.45,2.32 -4.75,3.94 -1.3,1.62 -2.29,3.46 -2.96,5.52 -0.68,2.06 -1.02,4.14 -1.02,6.25 0,2.27 0.41,4.43 1.22,6.49 0.81,2.06 1.93,3.86 3.37,5.4 1.43,1.54 3.09,2.76 4.99,3.65 1.89,0.89 3.95,1.34 6.17,1.34 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1254"
inkscape:connector-curvature="0" />
</g>
<g
transform="matrix(1.28,0,0,1,-97.8353,0)"
id="g1260">
<path
d="m 1115.51,2329.1 c -3.3,0 -6.52,-0.55 -9.66,-1.63 -3.14,-1.08 -5.84,-2.65 -8.11,-4.7 l 2.51,-3.74 c 2.38,1.95 4.8,3.41 7.27,4.39 2.46,0.97 5.09,1.46 7.91,1.46 3.41,0 6.12,-0.69 8.15,-2.07 2.03,-1.38 3.05,-3.34 3.05,-5.89 0,-1.19 -0.27,-2.17 -0.82,-2.96 -0.54,-0.78 -1.33,-1.47 -2.39,-2.07 -1.05,-0.59 -2.38,-1.12 -3.98,-1.58 -1.59,-0.46 -3.44,-0.93 -5.56,-1.42 -2.43,-0.59 -4.54,-1.19 -6.32,-1.78 -1.79,-0.6 -3.26,-1.28 -4.43,-2.03 -1.16,-0.76 -2.03,-1.69 -2.59,-2.8 -0.57,-1.11 -0.86,-2.51 -0.86,-4.18 0,-2.11 0.42,-3.98 1.26,-5.6 0.84,-1.63 1.98,-2.96 3.41,-4.02 1.43,-1.05 3.11,-1.85 5.03,-2.39 1.92,-0.54 3.96,-0.81 6.13,-0.81 3.24,0 6.19,0.52 8.84,1.58 2.65,1.05 4.79,2.45 6.41,4.18 l -2.67,3.24 c -1.57,-1.62 -3.48,-2.84 -5.72,-3.65 -2.25,-0.81 -4.59,-1.22 -7.02,-1.22 -1.47,0 -2.82,0.15 -4.06,0.45 -1.25,0.3 -2.34,0.77 -3.29,1.42 -0.94,0.65 -1.69,1.47 -2.23,2.48 -0.54,1 -0.81,2.17 -0.81,3.52 0,1.14 0.19,2.06 0.57,2.76 0.37,0.71 1,1.32 1.86,1.83 0.87,0.51 1.96,0.97 3.29,1.38 1.33,0.41 2.94,0.83 4.83,1.26 2.7,0.65 5.08,1.3 7.14,1.94 2.05,0.66 3.77,1.41 5.15,2.28 1.38,0.86 2.42,1.92 3.13,3.16 0.7,1.25 1.05,2.76 1.05,4.55 0,3.89 -1.49,6.98 -4.46,9.25 -2.98,2.27 -6.98,3.41 -12.01,3.41 z"
style="fill:#1d2167;fill-rule:nonzero"
id="path1258"
inkscape:connector-curvature="0" />
</g>
</g></g>
<defs
id="defs1283"><linearGradient
id="_Linear1"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-158.501,-265.604,265.604,-158.501,4436.06,964.047)"><stop
offset="0"
style="stop-color:rgb(49,54,158);stop-opacity:1"
id="stop1266" /><stop
offset="0.52"
style="stop-color:rgb(38,42,122);stop-opacity:1"
id="stop1268" /><stop
offset="1"
style="stop-color:black;stop-opacity:1"
id="stop1270" /></linearGradient><linearGradient
id="_Linear2"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(160.563,96.0376,-96.0376,160.563,4771.78,656.669)"><stop
offset="0"
style="stop-color:rgb(49,54,158);stop-opacity:1"
id="stop1273" /><stop
offset="1"
style="stop-color:rgb(29,33,103);stop-opacity:1"
id="stop1275" /></linearGradient><linearGradient
id="_Linear3"
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(76.53,136.554,-136.554,76.53,4737.26,847.244)"><stop
offset="0"
style="stop-color:rgb(49,54,158);stop-opacity:1"
id="stop1278" /><stop
offset="1"
style="stop-color:rgb(29,33,103);stop-opacity:1"
id="stop1280" /></linearGradient></defs>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

+331
View File
@@ -0,0 +1,331 @@
use atty::{self, Stream};
use clap::{App as ClapApp, AppSettings, Arg, ArgGroup, ArgMatches, SubCommand};
use console::Term;
use errors::*;
use std::collections::HashSet;
use std::env;
use style::{OutputComponent, OutputComponents, OutputWrap};
#[cfg(windows)]
use ansi_term;
pub struct App {
pub matches: ArgMatches<'static>,
interactive_output: bool,
}
impl App {
pub fn new() -> Self {
let interactive_output = atty::is(Stream::Stdout);
#[cfg(windows)]
let interactive_output = interactive_output && ansi_term::enable_ansi_support().is_ok();
App {
matches: Self::matches(interactive_output),
interactive_output,
}
}
fn matches(interactive_output: bool) -> ArgMatches<'static> {
let clap_color_setting = if interactive_output {
AppSettings::ColoredHelp
} else {
AppSettings::ColorNever
};
ClapApp::new(crate_name!())
.version(crate_version!())
.global_setting(clap_color_setting)
.global_setting(AppSettings::DeriveDisplayOrder)
.global_setting(AppSettings::UnifiedHelpMessage)
.global_setting(AppSettings::NextLineHelp)
.setting(AppSettings::InferSubcommands)
.setting(AppSettings::ArgsNegateSubcommands)
.setting(AppSettings::DisableHelpSubcommand)
.setting(AppSettings::VersionlessSubcommands)
.max_term_width(90)
.about(
"A cat(1) clone with wings.\n\n\
Use '--help' instead of '-h' to see a more detailed version of the help text.",
)
.long_about("A cat(1) clone with syntax highlighting and Git integration.")
.arg(
Arg::with_name("language")
.short("l")
.long("language")
.overrides_with("language")
.help("Set the language for highlighting")
.long_help(
"Set the language for syntax highlighting. The language can be \
specified as a name (like 'C++' or 'LaTeX') or possible file \
extension (like 'cpp', 'hpp' or 'md'). Use '--list-languages' \
to show all supported language names and file extensions",
)
.takes_value(true),
)
.arg(
Arg::with_name("FILE")
.help("File(s) to print / concatenate. Use '-' for standard input.")
.long_help(
"File(s) to print. Use no argument or '-' to read from standard \
input",
)
.multiple(true)
.empty_values(false),
)
.arg(
Arg::with_name("style")
.long("style")
.value_name("style-components")
.use_delimiter(true)
.takes_value(true)
.possible_values(&[
"auto", "full", "plain", "changes", "header", "grid", "numbers",
])
.default_value("auto")
.help("Comma-separated list of style elements to display")
.long_help(
"Configure which elements (line numbers, file headers, grid \
borders, Git modifications, ..) to display in addition to the \
file contents. The argument is a comma-separated list of \
components to display (e.g. 'numbers,changes,grid') or a \
pre-defined style ('full')",
),
)
.arg(
Arg::with_name("color")
.long("color")
.overrides_with("color")
.takes_value(true)
.value_name("when")
.possible_values(&["auto", "never", "always"])
.default_value("auto")
.help("When to use colors"),
)
.arg(
Arg::with_name("paging")
.long("paging")
.overrides_with("paging")
.takes_value(true)
.value_name("when")
.possible_values(&["auto", "never", "always"])
.default_value("auto")
.help("When to use the pager")
.long_help("Specify when to use the pager (less)"),
)
.arg(
Arg::with_name("wrap")
.long("wrap")
.overrides_with("wrap")
.takes_value(true)
.value_name("mode")
.possible_values(&["character", "never"])
.default_value("character")
.help("When to wrap text"),
)
.arg(
Arg::with_name("list-languages")
.long("list-languages")
.help("Displays supported languages")
.long_help("Display a list of supported languages"),
)
.arg(
Arg::with_name("theme")
.long("theme")
.overrides_with("theme")
.takes_value(true)
.help("Set the theme for highlighting")
.long_help(
"Set the theme for syntax highlighting. Use '--list-themes' to \
see all available themes",
),
)
.arg(
Arg::with_name("list-themes")
.long("list-themes")
.help("Displays supported themes")
.help("Display a list of supported themes for syntax highlighting"),
)
.arg(
Arg::with_name("number")
.long("number")
.overrides_with("number")
.short("n")
.conflicts_with("style")
.help("Show line numbers (alias for '--style=numbers')")
.long_help(
"Show line numbers (no other decorations). This is an alias for \
'--style=numbers'",
),
)
.arg(
Arg::with_name("unbuffered")
.short("u")
.help("(ignored)")
// TODO: use '.hidden_short_help(true)' when the next clap version is released
.long_help(
"This option exists for POSIX-compliance reasons ('u' is for \
'unbuffered'). The output is always unbuffered - this option \
it simply ignored.",
),
)
.subcommand(
SubCommand::with_name("cache")
.about("Modify the syntax-definition and theme cache")
.arg(
Arg::with_name("init")
.long("init")
.short("i")
.help("Initialize the syntax/theme cache")
.long_help(
"Initialize the syntax/theme cache by loading from the \
source directory (default: the configuration directory)",
),
)
.arg(
Arg::with_name("clear")
.long("clear")
.short("c")
.help("Reset the cache"),
)
.arg(
Arg::with_name("config-dir")
.long("config-dir")
.short("d")
.help("Show the configuration directory"),
)
.group(
ArgGroup::with_name("cache-actions")
.args(&["init", "clear", "config-dir"])
.required(true),
)
.arg(
Arg::with_name("source")
.long("source")
.requires("init")
.takes_value(true)
.value_name("dir")
.help("Use a different source for loading syntaxes and themes from"),
)
.arg(
Arg::with_name("target")
.long("target")
.requires("init")
.takes_value(true)
.value_name("dir")
.help(
"Use a different source to store the cached syntax and theme set",
),
),
)
.help_message("Print this help message.")
.version_message("Show version information.")
.get_matches()
}
pub fn config(&self) -> Result<Config> {
let files = self.files();
Ok(Config {
true_color: is_truecolor_terminal(),
output_components: self.output_components()?,
language: self.matches.value_of("language"),
output_wrap: if !self.interactive_output {
// We don't have the tty width when piping to another program.
// There's no point in wrapping when this is the case.
OutputWrap::None
} else {
match self.matches.value_of("wrap") {
Some("character") => OutputWrap::Character,
Some("never") | _ => OutputWrap::None,
}
},
colored_output: match self.matches.value_of("color") {
Some("always") => true,
Some("never") => false,
Some("auto") | _ => self.interactive_output,
},
paging_mode: match self.matches.value_of("paging") {
Some("always") => PagingMode::Always,
Some("never") => PagingMode::Never,
Some("auto") | _ => if files.contains(&None) {
// If we are reading from stdin, only enable paging if we write to an
// interactive terminal and if we do not *read* from an interactive
// terminal.
if self.interactive_output && !atty::is(Stream::Stdin) {
PagingMode::QuitIfOneScreen
} else {
PagingMode::Never
}
} else {
if self.interactive_output {
PagingMode::QuitIfOneScreen
} else {
PagingMode::Never
}
},
},
term_width: Term::stdout().size().1 as usize,
files,
theme: self.matches.value_of("theme"),
})
}
fn files(&self) -> Vec<Option<&str>> {
self.matches
.values_of("FILE")
.map(|values| {
values
.map(|filename| {
if filename == "-" {
None
} else {
Some(filename)
}
})
.collect()
})
.unwrap_or_else(|| vec![None]) // read from stdin (None) if no args are given
}
fn output_components(&self) -> Result<OutputComponents> {
let matches = &self.matches;
Ok(OutputComponents(if matches.is_present("number") {
[OutputComponent::Numbers].iter().cloned().collect()
} else {
values_t!(matches.values_of("style"), OutputComponent)?
.into_iter()
.map(|style| style.components(self.interactive_output))
.fold(HashSet::new(), |mut acc, components| {
acc.extend(components.iter().cloned());
acc
})
}))
}
}
#[derive(Debug, Clone, Copy)]
pub enum PagingMode {
Always,
QuitIfOneScreen,
Never,
}
pub struct Config<'a> {
pub true_color: bool,
pub output_wrap: OutputWrap,
pub output_components: OutputComponents,
pub language: Option<&'a str>,
pub colored_output: bool,
pub paging_mode: PagingMode,
pub term_width: usize,
pub files: Vec<Option<&'a str>>,
pub theme: Option<&'a str>,
}
fn is_truecolor_terminal() -> bool {
env::var("COLORTERM")
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
.unwrap_or(false)
}
+184
View File
@@ -0,0 +1,184 @@
use directories::ProjectDirs;
use errors::*;
use std::borrow::Cow;
use std::fs::{self, File};
use std::io;
use std::path::{Path, PathBuf};
use syntect::dumps::{dump_to_file, from_binary, from_reader};
use syntect::highlighting::{Theme, ThemeSet};
use syntect::parsing::{SyntaxDefinition, SyntaxSet};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
lazy_static! {
static ref PROJECT_DIRS: ProjectDirs = ProjectDirs::from("", "", crate_name!());
}
pub struct HighlightingAssets {
pub syntax_set: SyntaxSet,
pub theme_set: ThemeSet,
}
impl HighlightingAssets {
pub fn new() -> Self {
Self::from_cache().unwrap_or_else(|_| Self::from_binary())
}
pub fn from_files(dir: Option<&Path>) -> Result<Self> {
let source_dir = dir.unwrap_or_else(|| PROJECT_DIRS.config_dir());
let theme_dir = source_dir.join("themes");
let theme_set = ThemeSet::load_from_folder(&theme_dir).chain_err(|| {
format!(
"Could not load themes from '{}'",
theme_dir.to_string_lossy()
)
})?;
let mut syntax_set = SyntaxSet::new();
let syntax_dir = source_dir.join("syntaxes");
if !syntax_dir.exists() {
return Err(format!(
"Could not load syntaxes from '{}'",
syntax_dir.to_string_lossy()
).into());
}
syntax_set.load_syntaxes(syntax_dir, true)?;
syntax_set.load_plain_text_syntax();
Ok(HighlightingAssets {
syntax_set,
theme_set,
})
}
fn from_cache() -> Result<Self> {
let theme_set_path = theme_set_path();
let syntax_set_file = File::open(&syntax_set_path()).chain_err(|| {
format!(
"Could not load cached syntax set '{}'",
syntax_set_path().to_string_lossy()
)
})?;
let mut syntax_set: SyntaxSet =
from_reader(syntax_set_file).chain_err(|| "Could not parse cached syntax set")?;
syntax_set.link_syntaxes();
let theme_set_file = File::open(&theme_set_path).chain_err(|| {
format!(
"Could not load cached theme set '{}'",
theme_set_path.to_string_lossy()
)
})?;
let theme_set: ThemeSet =
from_reader(theme_set_file).chain_err(|| "Could not parse cached theme set")?;
Ok(HighlightingAssets {
syntax_set,
theme_set,
})
}
fn from_binary() -> Self {
let mut syntax_set: SyntaxSet = from_binary(include_bytes!("../assets/syntaxes.bin"));
syntax_set.link_syntaxes();
let theme_set: ThemeSet = from_binary(include_bytes!("../assets/themes.bin"));
HighlightingAssets {
syntax_set,
theme_set,
}
}
pub fn save(&self, dir: Option<&Path>) -> Result<()> {
let target_dir = dir.unwrap_or_else(|| PROJECT_DIRS.cache_dir());
let _ = fs::create_dir(target_dir);
let theme_set_path = target_dir.join("themes.bin");
let syntax_set_path = target_dir.join("syntaxes.bin");
print!(
"Writing theme set to {} ... ",
theme_set_path.to_string_lossy()
);
dump_to_file(&self.theme_set, &theme_set_path).chain_err(|| {
format!(
"Could not save theme set to {}",
theme_set_path.to_string_lossy()
)
})?;
println!("okay");
print!(
"Writing syntax set to {} ... ",
syntax_set_path.to_string_lossy()
);
dump_to_file(&self.syntax_set, &syntax_set_path).chain_err(|| {
format!(
"Could not save syntax set to {}",
syntax_set_path.to_string_lossy()
)
})?;
println!("okay");
Ok(())
}
pub fn get_theme(&self, theme: &str) -> Result<&Theme> {
Ok(self.theme_set.themes.get(theme).ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
format!("Could not find '{}' theme", theme),
)
})?)
}
pub fn get_syntax(&self, language: Option<&str>, filename: Option<&str>) -> &SyntaxDefinition {
let syntax = match (language, filename) {
(Some(language), _) => self.syntax_set.find_syntax_by_token(language),
(None, Some(filename)) => {
#[cfg(not(unix))]
let may_read_from_file = true;
// Do not peek at the file (to determine the syntax) if it is a FIFO because they can
// only be read once.
#[cfg(unix)]
let may_read_from_file = !fs::metadata(filename)
.map(|m| m.file_type().is_fifo())
.unwrap_or(false);
if may_read_from_file {
self.syntax_set
.find_syntax_for_file(filename)
.unwrap_or(None)
} else {
None
}
}
(None, None) => None,
};
syntax.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text())
}
}
fn theme_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("themes.bin")
}
fn syntax_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("syntaxes.bin")
}
pub fn config_dir() -> Cow<'static, str> {
PROJECT_DIRS.config_dir().to_string_lossy()
}
pub fn clear_assets() {
print!("Clearing theme set cache ... ");
fs::remove_file(theme_set_path()).ok();
println!("okay");
print!("Clearing syntax set cache ... ");
fs::remove_file(syntax_set_path()).ok();
println!("okay");
}
+153
View File
@@ -0,0 +1,153 @@
use ansi_term::Style;
use diff::LineChange;
use printer::{Colors, Printer};
#[derive(Clone)]
pub struct DecorationText {
pub width: usize,
pub text: String,
}
pub trait Decoration {
fn generate(&self, line_number: usize, continuation: bool, printer: &Printer)
-> DecorationText;
fn width(&self) -> usize;
}
// Line number decoration.
pub struct LineNumberDecoration {
color: Style,
cached_wrap: DecorationText,
cached_wrap_invalid_at: usize,
}
impl LineNumberDecoration {
pub fn new(colors: &Colors) -> Self {
LineNumberDecoration {
color: colors.line_number,
cached_wrap_invalid_at: 10000,
cached_wrap: DecorationText {
text: colors.line_number.paint(" ".repeat(4)).to_string(),
width: 4,
},
}
}
}
impl Decoration for LineNumberDecoration {
fn generate(
&self,
line_number: usize,
continuation: bool,
_printer: &Printer,
) -> DecorationText {
if continuation {
if line_number > self.cached_wrap_invalid_at {
let new_width = self.cached_wrap.width + 1;
return DecorationText {
text: self.color.paint(" ".repeat(new_width)).to_string(),
width: new_width,
};
}
self.cached_wrap.clone()
} else {
let plain: String = format!("{:4}", line_number);
DecorationText {
width: plain.len(),
text: self.color.paint(plain).to_string(),
}
}
}
fn width(&self) -> usize {
4
}
}
// Line changes decoration.
pub struct LineChangesDecoration {
cached_none: DecorationText,
cached_added: DecorationText,
cached_removed_above: DecorationText,
cached_removed_below: DecorationText,
cached_modified: DecorationText,
}
impl LineChangesDecoration {
#[inline]
fn generate_cached(style: Style, text: &str) -> DecorationText {
DecorationText {
text: style.paint(text).to_string(),
width: text.chars().count(),
}
}
pub fn new(colors: &Colors) -> Self {
LineChangesDecoration {
cached_none: Self::generate_cached(Style::default(), " "),
cached_added: Self::generate_cached(colors.git_added, "+"),
cached_removed_above: Self::generate_cached(colors.git_removed, ""),
cached_removed_below: Self::generate_cached(colors.git_removed, "_"),
cached_modified: Self::generate_cached(colors.git_modified, "~"),
}
}
}
impl Decoration for LineChangesDecoration {
fn generate(
&self,
line_number: usize,
continuation: bool,
printer: &Printer,
) -> DecorationText {
if !continuation {
if let Some(ref changes) = printer.line_changes {
return match changes.get(&(line_number as u32)) {
Some(&LineChange::Added) => self.cached_added.clone(),
Some(&LineChange::RemovedAbove) => self.cached_removed_above.clone(),
Some(&LineChange::RemovedBelow) => self.cached_removed_below.clone(),
Some(&LineChange::Modified) => self.cached_modified.clone(),
_ => self.cached_none.clone(),
};
}
}
self.cached_none.clone()
}
fn width(&self) -> usize {
self.cached_none.width
}
}
// Grid border decoration.
pub struct GridBorderDecoration {
cached: DecorationText,
}
impl GridBorderDecoration {
pub fn new(colors: &Colors) -> Self {
GridBorderDecoration {
cached: DecorationText {
text: colors.grid.paint("").to_string(),
width: 1,
},
}
}
}
impl Decoration for GridBorderDecoration {
fn generate(
&self,
_line_number: usize,
_continuation: bool,
_printer: &Printer,
) -> DecorationText {
self.cached.clone()
}
fn width(&self) -> usize {
self.cached.width
}
}
+77
View File
@@ -0,0 +1,77 @@
use git2::{DiffOptions, IntoCString, Repository};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Copy, Clone, Debug)]
pub enum LineChange {
Added,
RemovedAbove,
RemovedBelow,
Modified,
}
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 mut diff_options = DiffOptions::new();
let pathspec = path_relative_to_repo.into_c_string().ok()?;
diff_options.pathspec(pathspec);
diff_options.context_lines(0);
let diff = repo
.diff_index_to_workdir(None, Some(&mut diff_options))
.ok()?;
let mut line_changes: LineChanges = HashMap::new();
let mark_section =
|line_changes: &mut LineChanges, start: u32, end: i32, change: LineChange| {
for line in start..(end + 1) as u32 {
line_changes.insert(line, change);
}
};
let _ = diff.foreach(
&mut |_, _| true,
None,
Some(&mut |delta, hunk| {
let path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
if path_relative_to_repo != path {
return false;
}
let old_lines = hunk.old_lines();
let new_start = hunk.new_start();
let new_lines = hunk.new_lines();
let new_end = (new_start + new_lines) as i32 - 1;
if old_lines == 0 && new_lines > 0 {
mark_section(&mut line_changes, new_start, new_end, LineChange::Added);
} else if new_lines == 0 && old_lines > 0 {
if new_start == 0 {
mark_section(&mut line_changes, 1, 1, LineChange::RemovedAbove);
} else {
mark_section(
&mut line_changes,
new_start,
new_start as i32,
LineChange::RemovedBelow,
);
}
} else {
mark_section(&mut line_changes, new_start, new_end, LineChange::Modified);
}
true
}),
None,
);
Some(line_changes)
}
+112
View File
@@ -0,0 +1,112 @@
use ansi_term::Colour::Green;
use app::Config;
use assets::HighlightingAssets;
use diff::get_git_diff;
use errors::*;
use output::OutputType;
use printer::Printer;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use syntect::easy::HighlightLines;
use syntect::highlighting::Theme;
use syntect::parsing::SyntaxDefinition;
pub fn list_languages(assets: &HighlightingAssets, term_width: usize) {
let mut languages = assets
.syntax_set
.syntaxes()
.iter()
.filter(|syntax| !syntax.hidden && !syntax.file_extensions.is_empty())
.collect::<Vec<_>>();
languages.sort_by_key(|lang| lang.name.to_uppercase());
let longest = languages
.iter()
.map(|syntax| syntax.name.len())
.max()
.unwrap_or(32); // Fallback width if they have no language definitions.
let comma_separator = ", ";
let separator = " ";
// Line-wrapping for the possible file extension overflow.
let desired_width = term_width - longest - separator.len();
for lang in languages {
print!("{:width$}{}", lang.name, separator, width = longest);
// Number of characters on this line so far, wrap before `desired_width`
let mut num_chars = 0;
let mut extension = lang.file_extensions.iter().peekable();
while let Some(word) = extension.next() {
// If we can't fit this word in, then create a line break and align it in.
let new_chars = word.len() + comma_separator.len();
if num_chars + new_chars >= desired_width {
num_chars = 0;
print!("\n{:width$}{}", "", separator, width = longest);
}
num_chars += new_chars;
print!("{}", Green.paint(&word[..]));
if extension.peek().is_some() {
print!("{}", comma_separator);
}
}
println!();
}
}
pub fn print_files(assets: &HighlightingAssets, config: &Config) -> Result<bool> {
let theme = assets.get_theme(config.theme.unwrap_or("Default"))?;
let mut output_type = OutputType::from_mode(config.paging_mode);
let handle = output_type.handle()?;
let mut printer = Printer::new(handle, &config);
let mut no_errors: bool = true;
for file in &config.files {
printer.line_changes = file.and_then(|filename| get_git_diff(filename));
let syntax = assets.get_syntax(config.language, *file);
let result = print_file(theme, &syntax, &mut printer, *file);
if let Err(error) = result {
handle_error(&error);
no_errors = false;
}
}
Ok(no_errors)
}
fn print_file(
theme: &Theme,
syntax: &SyntaxDefinition,
printer: &mut Printer,
filename: Option<&str>,
) -> Result<()> {
let stdin = io::stdin(); // TODO: this is not always needed
let mut reader: Box<BufRead> = match filename {
None => Box::new(stdin.lock()),
Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
};
let mut highlighter = HighlightLines::new(syntax, theme);
printer.print_header(filename)?;
let mut buffer = Vec::new();
while reader.read_until(b'\n', &mut buffer)? > 0 {
{
let line = String::from_utf8_lossy(&buffer);
let regions = highlighter.highlight(line.as_ref());
printer.print_line(&regions)?;
}
buffer.clear();
}
printer.print_footer()?;
Ok(())
}
+66 -608
View File
@@ -17,648 +17,106 @@ extern crate directories;
extern crate git2;
extern crate syntect;
mod app;
mod assets;
mod decorations;
mod diff;
mod features;
mod output;
mod printer;
mod style;
mod terminal;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Child, Command, Stdio};
use std::io;
use std::path::Path;
use std::process;
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
use ansi_term::Style;
use atty::Stream;
use clap::{App, AppSettings, Arg, ArgGroup, SubCommand};
use directories::ProjectDirs;
use git2::{DiffOptions, IntoCString, Repository};
use syntect::dumps::{dump_to_file, from_binary, from_reader};
use syntect::easy::HighlightLines;
use syntect::highlighting::{Theme, ThemeSet};
use syntect::parsing::SyntaxSet;
use printer::Printer;
lazy_static! {
static ref PROJECT_DIRS: ProjectDirs = ProjectDirs::from("", "", crate_name!());
}
use app::App;
use assets::{clear_assets, config_dir, HighlightingAssets};
use features::{list_languages, print_files};
mod errors {
error_chain! {
foreign_links {
Clap(::clap::Error);
Io(::std::io::Error);
SyntectError(::syntect::LoadingError);
}
}
pub fn handle_error(error: &Error) {
match error {
&Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == super::io::ErrorKind::BrokenPipe =>
{
super::process::exit(0);
}
_ => {
use ansi_term::Colour::Red;
eprintln!("{}: {}", Red.paint("[bat error]"), error);
}
};
}
}
use errors::*;
pub enum OptionsStyle {
Plain,
LineNumbers,
Full,
}
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Some(true)` on full success and
/// `Some(false)` if any intermediate errors occurred (were printed).
fn run() -> Result<bool> {
let app = App::new();
pub struct Options<'a> {
pub true_color: bool,
pub style: OptionsStyle,
pub language: Option<&'a str>,
pub colored_output: bool,
pub paging: bool,
pub term_width: usize,
}
enum OutputType {
Pager(Child),
Stdout(io::Stdout),
}
impl OutputType {
fn pager() -> Result<Self> {
Ok(OutputType::Pager(Command::new("less")
.args(&["--quit-if-one-screen", "--RAW-CONTROL-CHARS", "--no-init"])
.stdin(Stdio::piped())
.spawn()
.chain_err(|| "Could not spawn pager")?))
}
fn stdout() -> Self {
OutputType::Stdout(io::stdout())
}
fn handle(&mut self) -> Result<&mut Write> {
Ok(match *self {
OutputType::Pager(ref mut command) => command
.stdin
.as_mut()
.chain_err(|| "Could not open stdin for pager")?,
OutputType::Stdout(ref mut handle) => handle,
})
}
}
impl Drop for OutputType {
fn drop(&mut self) {
if let OutputType::Pager(ref mut command) = *self {
let _ = command.wait();
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum LineChange {
Added,
RemovedAbove,
RemovedBelow,
Modified,
}
pub type LineChanges = HashMap<u32, LineChange>;
const GRID_COLOR: u8 = 238;
const LINE_NUMBER_COLOR: u8 = 244;
#[derive(Default)]
pub struct Colors {
pub grid: Style,
pub filename: Style,
pub git_added: Style,
pub git_removed: Style,
pub git_modified: Style,
pub line_number: Style,
}
impl Colors {
fn plain() -> Self {
Colors::default()
}
fn colored() -> Self {
Colors {
grid: Fixed(GRID_COLOR).normal(),
filename: White.bold(),
git_added: Green.normal(),
git_removed: Red.normal(),
git_modified: Yellow.normal(),
line_number: Fixed(LINE_NUMBER_COLOR).normal(),
}
}
}
fn print_file(
options: &Options,
theme: &Theme,
syntax_set: &SyntaxSet,
printer: &mut Printer,
filename: Option<&str>,
) -> Result<()> {
let stdin = io::stdin(); // TODO: this is not always needed
let mut reader: Box<BufRead> = match filename {
None => Box::new(stdin.lock()),
Some(filename) => Box::new(BufReader::new(File::open(filename)?)),
};
let syntax = match (options.language, filename) {
(Some(language), _) => syntax_set.find_syntax_by_token(language),
(None, Some(filename)) => {
#[cfg(not(unix))]
let may_read_from_file = true;
// Do not peek at the file (to determine the syntax) if it is a FIFO because they can
// only be read once.
#[cfg(unix)]
let may_read_from_file = !fs::metadata(filename)?.file_type().is_fifo();
if may_read_from_file {
syntax_set.find_syntax_for_file(filename)?
} else {
None
}
}
(None, None) => None,
};
let syntax = syntax.unwrap_or_else(|| syntax_set.find_syntax_plain_text());
let mut highlighter = HighlightLines::new(syntax, theme);
printer.print_header(filename)?;
let mut line_nr = 1;
let mut line_buffer = String::new();
loop {
line_buffer.clear();
let num_bytes = reader.read_line(&mut line_buffer);
let line = match num_bytes {
Ok(0) => {
break;
}
Ok(_) => {
if !line_buffer.ends_with("\n") {
line_buffer.push('\n');
}
&line_buffer
}
Err(_) => "<bat: INVALID UTF-8>\n",
};
let regions = highlighter.highlight(line);
printer.print_line(line_nr, &regions)?;
line_nr += 1;
}
printer.print_footer()?;
Ok(())
}
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 mut diff_options = DiffOptions::new();
let pathspec = path_relative_to_repo.into_c_string().ok()?;
diff_options.pathspec(pathspec);
diff_options.context_lines(0);
let diff = repo.diff_index_to_workdir(None, Some(&mut diff_options))
.ok()?;
let mut line_changes: LineChanges = HashMap::new();
let mark_section =
|line_changes: &mut LineChanges, start: u32, end: i32, change: LineChange| {
for line in start..(end + 1) as u32 {
line_changes.insert(line, change);
}
};
let _ = diff.foreach(
&mut |_, _| true,
None,
Some(&mut |delta, hunk| {
let path = delta.new_file().path().unwrap_or_else(|| Path::new(""));
if path_relative_to_repo != path {
return false;
}
let old_lines = hunk.old_lines();
let new_start = hunk.new_start();
let new_lines = hunk.new_lines();
let new_end = (new_start + new_lines) as i32 - 1;
if old_lines == 0 && new_lines > 0 {
mark_section(&mut line_changes, new_start, new_end, LineChange::Added);
} else if new_lines == 0 && old_lines > 0 {
if new_start == 0 {
mark_section(&mut line_changes, 1, 1, LineChange::RemovedAbove);
} else {
mark_section(
&mut line_changes,
new_start,
new_start as i32,
LineChange::RemovedBelow,
);
}
} else {
mark_section(&mut line_changes, new_start, new_end, LineChange::Modified);
}
true
}),
None,
);
Some(line_changes)
}
fn get_output_type(paging: bool) -> OutputType {
if paging {
OutputType::pager().unwrap_or_else(|_| OutputType::stdout())
} else {
OutputType::stdout()
}
}
fn is_truecolor_terminal() -> bool {
env::var("COLORTERM")
.map(|colorterm| colorterm == "truecolor" || colorterm == "24bit")
.unwrap_or(false)
}
struct HighlightingAssets {
pub syntax_set: SyntaxSet,
pub theme_set: ThemeSet,
}
impl HighlightingAssets {
fn theme_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("theme_set")
}
fn syntax_set_path() -> PathBuf {
PROJECT_DIRS.cache_dir().join("syntax_set")
}
fn from_files() -> Result<Self> {
let config_dir = PROJECT_DIRS.config_dir();
let theme_dir = config_dir.join("themes");
let theme_set = ThemeSet::load_from_folder(&theme_dir).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!(
"Could not load themes from '{}'",
theme_dir.to_string_lossy()
),
)
})?;
let mut syntax_set = SyntaxSet::new();
let syntax_dir = config_dir.join("syntax");
let _ = syntax_set.load_syntaxes(syntax_dir, true);
syntax_set.load_plain_text_syntax();
Ok(HighlightingAssets {
syntax_set,
theme_set,
})
}
fn save(&self) -> Result<()> {
let cache_dir = PROJECT_DIRS.cache_dir();
let _ = fs::create_dir(cache_dir);
print!(
"Writing theme set to {} ... ",
Self::theme_set_path().to_string_lossy()
);
dump_to_file(&self.theme_set, &Self::theme_set_path()).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!(
"Could not save theme set to {}",
Self::theme_set_path().to_string_lossy()
),
)
})?;
println!("okay");
print!(
"Writing syntax set to {} ... ",
Self::syntax_set_path().to_string_lossy()
);
dump_to_file(&self.syntax_set, &Self::syntax_set_path()).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!(
"Could not save syntax set to {}",
Self::syntax_set_path().to_string_lossy()
),
)
})?;
println!("okay");
Ok(())
}
fn from_cache() -> Result<Self> {
let syntax_set_file = File::open(&Self::syntax_set_path()).chain_err(|| {
format!(
"Could not load cached syntax set '{}'",
Self::syntax_set_path().to_string_lossy()
)
})?;
let mut syntax_set: SyntaxSet = from_reader(syntax_set_file).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!("Could not parse cached syntax set"),
)
})?;
syntax_set.link_syntaxes();
let theme_set_file = File::open(&Self::theme_set_path()).chain_err(|| {
format!(
"Could not load cached theme set '{}'",
Self::theme_set_path().to_string_lossy()
)
})?;
let theme_set: ThemeSet = from_reader(theme_set_file).map_err(|_| {
io::Error::new(
io::ErrorKind::Other,
format!("Could not parse cached theme set"),
)
})?;
Ok(HighlightingAssets {
syntax_set,
theme_set,
})
}
fn from_binary() -> Self {
let mut syntax_set: SyntaxSet = from_binary(include_bytes!("../assets/syntax_set"));
syntax_set.link_syntaxes();
let theme_set: ThemeSet = from_binary(include_bytes!("../assets/theme_set"));
HighlightingAssets {
syntax_set,
theme_set,
}
}
}
fn run() -> Result<()> {
let interactive_terminal = atty::is(Stream::Stdout);
let clap_color_setting = if interactive_terminal {
AppSettings::ColoredHelp
} else {
AppSettings::ColorNever
};
let app_matches = App::new(crate_name!())
.version(crate_version!())
.global_setting(clap_color_setting)
.global_setting(AppSettings::DeriveDisplayOrder)
.global_setting(AppSettings::UnifiedHelpMessage)
.global_setting(AppSettings::NextLineHelp)
.setting(AppSettings::InferSubcommands)
.setting(AppSettings::ArgsNegateSubcommands)
.setting(AppSettings::DisableHelpSubcommand)
.setting(AppSettings::VersionlessSubcommands)
.max_term_width(90)
.about(crate_description!())
.arg(
Arg::with_name("language")
.short("l")
.long("language")
.help("Set the language for highlighting")
.takes_value(true),
)
.arg(
Arg::with_name("FILE")
.help("File(s) to print")
.multiple(true)
.empty_values(false),
)
.arg(
Arg::with_name("style")
.long("style")
.possible_values(&["auto", "plain", "line-numbers", "full"])
.default_value("auto")
.help("Additional info to display along with content"),
)
.arg(
Arg::with_name("color")
.long("color")
.takes_value(true)
.possible_values(&["auto", "never", "always"])
.default_value("auto")
.help("When to use colors"),
)
.arg(
Arg::with_name("paging")
.long("paging")
.takes_value(true)
.possible_values(&["auto", "never", "always"])
.default_value("auto")
.help("When to use the pager"),
)
.arg(
Arg::with_name("list-languages")
.long("list-languages")
.help("Displays supported languages"),
)
.subcommand(
SubCommand::with_name("cache")
.about("Modify the syntax-definition and theme cache")
.arg(
Arg::with_name("init")
.long("init")
.short("i")
.help("Initialize the cache by loading from the config dir"),
)
.arg(
Arg::with_name("clear")
.long("clear")
.short("c")
.help("Reset the cache"),
)
.arg(
Arg::with_name("config-dir")
.long("config-dir")
.short("d")
.help("Show the configuration directory"),
)
.group(
ArgGroup::with_name("cache-actions")
.args(&["init", "clear", "config-dir"])
.required(true),
),
)
.help_message("Print this help message.")
.version_message("Show version information.")
.get_matches();
match app_matches.subcommand() {
match app.matches.subcommand() {
("cache", Some(cache_matches)) => {
if cache_matches.is_present("init") {
let assets = HighlightingAssets::from_files()?;
assets.save()?;
} else if cache_matches.is_present("clear") {
print!("Clearing theme set cache ... ");
fs::remove_file(HighlightingAssets::theme_set_path())?;
println!("okay");
let source_dir = cache_matches.value_of("source").map(Path::new);
let target_dir = cache_matches.value_of("target").map(Path::new);
print!("Clearing syntax set cache ... ");
fs::remove_file(HighlightingAssets::syntax_set_path())?;
println!("okay");
let assets = HighlightingAssets::from_files(source_dir)?;
assets.save(target_dir)?;
} else if cache_matches.is_present("clear") {
clear_assets();
} else if cache_matches.is_present("config-dir") {
println!("{}", PROJECT_DIRS.config_dir().to_string_lossy());
println!("{}", config_dir());
}
return Ok(true);
}
_ => {
let files: Vec<Option<&str>> = app_matches
.values_of("FILE")
.map(|values| {
values
.map(|filename| {
if filename == "-" {
None
} else {
Some(filename)
}
})
.collect()
})
.unwrap_or_else(|| vec![None]); // read from stdin (None) if no args are given
let config = app.config()?;
let assets = HighlightingAssets::new();
let options = Options {
true_color: is_truecolor_terminal(),
style: match app_matches.value_of("style") {
Some("plain") => OptionsStyle::Plain,
Some("line-numbers") => OptionsStyle::LineNumbers,
Some("full") => OptionsStyle::Full,
Some("auto") | _ => if interactive_terminal {
OptionsStyle::Full
} else {
OptionsStyle::Plain
},
},
language: app_matches.value_of("language"),
colored_output: match app_matches.value_of("color") {
Some("always") => true,
Some("never") => false,
_ => interactive_terminal,
},
paging: match app_matches.value_of("paging") {
Some("always") => true,
Some("never") => false,
Some("auto") | _ => if files.contains(&None) {
// If we are reading from stdin, only enable paging if we write to an
// interactive terminal and if we do not *read* from an interactive
// terminal.
interactive_terminal && !atty::is(Stream::Stdin)
} else {
interactive_terminal
},
},
term_width: console::Term::stdout().size().1 as usize,
};
if app.matches.is_present("list-languages") {
list_languages(&assets, config.term_width);
return Ok(true);
}
let assets =
HighlightingAssets::from_cache().unwrap_or(HighlightingAssets::from_binary());
let theme = assets.theme_set.themes.get("Default").ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
format!("Could not find 'Default' theme"),
)
})?;
if app_matches.is_present("list-languages") {
let languages = assets.syntax_set.syntaxes();
let longest = languages
.iter()
.filter(|s| !s.hidden && s.file_extensions.len() > 0)
.map(|s| s.name.len())
.max()
.unwrap_or(32); // Fallback width if they have no language definitions.
let separator = " ";
for lang in languages {
if lang.hidden || lang.file_extensions.len() == 0 {
continue;
}
print!("{:width$}{}", lang.name, separator, width = longest);
// Line-wrapping for the possible file extension overflow.
let desired_width = options.term_width - longest - separator.len();
// Number of characters on this line so far, wrap before `desired_width`
let mut num_chars = 0;
let comma_separator = ", ";
let mut extension = lang.file_extensions.iter().peekable();
while let Some(word) = extension.next() {
// If we can't fit this word in, then create a line break and align it in.
let new_chars = word.len() + comma_separator.len();
if num_chars + new_chars >= desired_width {
num_chars = 0;
print!("\n{:width$}{}", "", separator, width = longest);
}
num_chars += new_chars;
print!("{}", Green.paint(word as &str));
if extension.peek().is_some() {
print!("{}", comma_separator);
}
}
println!();
if app.matches.is_present("list-themes") {
let themes = &assets.theme_set.themes;
for (theme, _) in themes.iter() {
println!("{}", theme);
}
return Ok(());
return Ok(true);
}
let mut output_type = get_output_type(options.paging);
let handle = output_type.handle()?;
let mut printer = Printer::new(handle, &options);
for file in files {
printer.line_changes = file.and_then(|filename| get_git_diff(filename));
print_file(&options, theme, &assets.syntax_set, &mut printer, file)?;
}
print_files(&assets, &config)
}
}
Ok(())
}
fn main() {
let result = run();
if let Err(error) = result {
match error {
Error(ErrorKind::Io(ref io_error), _)
if io_error.kind() == io::ErrorKind::BrokenPipe => {}
_ => {
eprintln!("{}: {}", Red.paint("[bat error]"), error);
process::exit(1);
}
};
match result {
Err(error) => {
handle_error(&error);
process::exit(1);
}
Ok(false) => {
process::exit(1);
}
Ok(true) => {
process::exit(0);
}
}
}
+57
View File
@@ -0,0 +1,57 @@
use app::PagingMode;
use errors::*;
use std::io::{self, Write};
use std::process::{Child, Command, Stdio};
pub enum OutputType {
Pager(Child),
Stdout(io::Stdout),
}
impl OutputType {
pub fn from_mode(mode: PagingMode) -> Self {
use self::PagingMode::*;
match mode {
Always => OutputType::try_pager(false),
QuitIfOneScreen => OutputType::try_pager(true),
_ => OutputType::stdout(),
}
}
/// Try to launch the pager. Fall back to stdout in case of errors.
fn try_pager(quit_if_one_screen: bool) -> Self {
let mut args = vec!["--RAW-CONTROL-CHARS", "--no-init"];
if quit_if_one_screen {
args.push("--quit-if-one-screen");
}
Command::new("less")
.args(&args)
.env("LESSCHARSET", "UTF-8")
.stdin(Stdio::piped())
.spawn()
.map(OutputType::Pager)
.unwrap_or_else(|_| OutputType::stdout())
}
fn stdout() -> Self {
OutputType::Stdout(io::stdout())
}
pub fn handle(&mut self) -> Result<&mut Write> {
Ok(match *self {
OutputType::Pager(ref mut command) => command
.stdin
.as_mut()
.chain_err(|| "Could not open stdin for pager")?,
OutputType::Stdout(ref mut handle) => handle,
})
}
}
impl Drop for OutputType {
fn drop(&mut self) {
if let OutputType::Pager(ref mut command) = *self {
let _ = command.wait();
}
}
}
+231 -89
View File
@@ -1,49 +1,98 @@
use ansi_term::Colour::{Fixed, Green, Red, White, Yellow};
use ansi_term::Style;
use app::Config;
use console::AnsiCodeIterator;
use decorations::{Decoration, GridBorderDecoration, LineChangesDecoration, LineNumberDecoration};
use diff::LineChanges;
use errors::*;
use std::boxed::Box;
use std::io::Write;
use std::vec::Vec;
use style::OutputWrap;
use syntect::highlighting;
use terminal::as_terminal_escaped;
use {Colors, LineChange, LineChanges, Options, OptionsStyle};
const PANEL_WIDTH: usize = 7;
pub struct Printer<'a> {
handle: &'a mut Write,
colors: Colors,
options: &'a Options<'a>,
config: &'a Config<'a>,
decorations: Vec<Box<Decoration>>,
panel_width: usize,
line_number: usize,
pub line_changes: Option<LineChanges>,
}
impl<'a> Printer<'a> {
pub fn new(handle: &'a mut Write, options: &'a Options) -> Self {
let colors = if options.colored_output {
pub fn new(handle: &'a mut Write, config: &'a Config) -> Self {
let colors = if config.colored_output {
Colors::colored()
} else {
Colors::plain()
};
// Create decorations.
let mut decorations: Vec<Box<Decoration>> = Vec::new();
if config.output_components.numbers() {
decorations.push(Box::new(LineNumberDecoration::new(&colors)));
}
if config.output_components.changes() {
decorations.push(Box::new(LineChangesDecoration::new(&colors)));
}
let mut panel_width: usize =
decorations.len() + decorations.iter().fold(0, |a, x| a + x.width());
// The grid border decoration isn't added until after the panel_width calculation, since the
// print_horizontal_line, print_header, and print_footer functions all assume the panel
// width is without the grid border.
if config.output_components.grid() && !decorations.is_empty() {
decorations.push(Box::new(GridBorderDecoration::new(&colors)));
}
// Disable the panel if the terminal is too small (i.e. can't fit 5 characters with the
// panel showing).
if config.term_width
< (decorations.len() + decorations.iter().fold(0, |a, x| a + x.width())) + 5
{
decorations.clear();
panel_width = 0;
}
// Create printer.
Printer {
panel_width,
handle,
colors,
options,
config,
decorations,
line_number: 0,
line_changes: None,
}
}
pub fn print_header(&mut self, filename: Option<&str>) -> Result<()> {
match self.options.style {
OptionsStyle::Full => {}
_ => return Ok(()),
self.line_number = 0;
if !self.config.output_components.header() {
return Ok(());
}
self.print_horizontal_line('┬')?;
if self.config.output_components.grid() {
self.print_horizontal_line('┬')?;
write!(
self.handle,
"{}{} ",
" ".repeat(PANEL_WIDTH),
self.colors.grid.paint(""),
)?;
write!(
self.handle,
"{}{}",
" ".repeat(self.panel_width),
self.colors
.grid
.paint(if self.panel_width > 0 { "" } else { "" }),
)?;
} else {
write!(self.handle, "{}", " ".repeat(self.panel_width))?;
}
writeln!(
self.handle,
@@ -52,94 +101,187 @@ impl<'a> Printer<'a> {
self.colors.filename.paint(filename.unwrap_or("STDIN"))
)?;
self.print_horizontal_line('┼')
if self.config.output_components.grid() {
self.print_horizontal_line('┼')?;
}
Ok(())
}
pub fn print_footer(&mut self) -> Result<()> {
if let OptionsStyle::Full = self.options.style {
if self.config.output_components.grid() {
self.print_horizontal_line('┴')
} else {
Ok(())
}
}
pub fn print_line(
&mut self,
line_number: usize,
regions: &[(highlighting::Style, &str)],
) -> Result<()> {
let decorations = vec![
self.print_line_number(line_number),
self.print_git_marker(line_number),
self.print_line_border(),
Some(as_terminal_escaped(
&regions,
self.options.true_color,
self.options.colored_output,
)),
];
pub fn print_line(&mut self, regions: &[(highlighting::Style, &str)]) -> Result<()> {
self.line_number += 1;
let mut cursor: usize = 0;
let mut cursor_max: usize = self.config.term_width;
let mut panel_wrap: Option<String> = None;
write!(
self.handle,
"{}",
decorations
.into_iter()
.filter_map(|dec| dec)
.collect::<Vec<_>>()
.join(" ")
)?;
// Line decorations.
if self.panel_width > 0 {
let decorations = self
.decorations
.iter()
.map(|ref d| d.generate(self.line_number, false, self))
.collect::<Vec<_>>();
for deco in decorations {
write!(self.handle, "{} ", deco.text)?;
cursor_max -= deco.width + 1;
}
}
// Line contents.
if self.config.output_wrap == OutputWrap::None {
let true_color = self.config.true_color;
let colored_output = self.config.colored_output;
write!(
self.handle,
"{}",
regions
.iter()
.map(|&(style, text)| as_terminal_escaped(
style,
text,
true_color,
colored_output,
))
.collect::<Vec<_>>()
.join("")
)?;
} else {
for &(style, region) in regions.iter() {
let mut ansi_iterator = AnsiCodeIterator::new(region);
let mut ansi_prefix = "";
for chunk in ansi_iterator {
match chunk {
// ANSI escape passthrough.
(text, true) => {
ansi_prefix = text;
}
// Regular text.
(text, false) => {
let text = text.trim_right_matches(|c| c == '\r' || c == '\n');
let mut chars = text.chars();
let mut remaining = text.chars().count();
while remaining > 0 {
let available = cursor_max - cursor;
// It fits.
if remaining <= available {
let text = chars.by_ref().take(remaining).collect::<String>();
cursor += remaining;
write!(
self.handle,
"{}",
as_terminal_escaped(
style,
&*format!("{}{}", ansi_prefix, text),
self.config.true_color,
self.config.colored_output,
)
)?;
break;
}
// Generate wrap padding if not already generated.
if panel_wrap.is_none() {
panel_wrap = if self.panel_width > 0 {
Some(format!(
"{} ",
self.decorations
.iter()
.map(|ref d| d
.generate(self.line_number, true, self)
.text)
.collect::<Vec<String>>()
.join(" ")
))
} else {
Some("".to_string())
}
}
// It wraps.
let text = chars.by_ref().take(available).collect::<String>();
cursor = 0;
remaining -= available;
write!(
self.handle,
"{}\n{}",
as_terminal_escaped(
style,
&*format!("{}{}", ansi_prefix, text),
self.config.true_color,
self.config.colored_output,
),
panel_wrap.clone().unwrap()
)?;
}
}
}
}
}
write!(self.handle, "\n")?;
}
Ok(())
}
fn print_line_number(&self, line_number: usize) -> Option<String> {
if let OptionsStyle::Plain = self.options.style {
return None;
}
Some(
self.colors
.line_number
.paint(format!("{:4}", line_number))
.to_string(),
)
}
fn print_git_marker(&self, line_number: usize) -> Option<String> {
match self.options.style {
OptionsStyle::Full => {}
_ => return None,
}
let marker = if let Some(ref changes) = self.line_changes {
match changes.get(&(line_number as u32)) {
Some(&LineChange::Added) => self.colors.git_added.paint("+"),
Some(&LineChange::RemovedAbove) => self.colors.git_removed.paint(""),
Some(&LineChange::RemovedBelow) => self.colors.git_removed.paint("_"),
Some(&LineChange::Modified) => self.colors.git_modified.paint("~"),
_ => Style::default().paint(" "),
}
} else {
Style::default().paint(" ")
};
Some(marker.to_string())
}
fn print_line_border(&self) -> Option<String> {
if let OptionsStyle::Plain = self.options.style {
return None;
}
Some(self.colors.grid.paint("").to_string())
}
fn print_horizontal_line(&mut self, grid_char: char) -> Result<()> {
let hline = "".repeat(self.options.term_width - (PANEL_WIDTH + 1));
let hline = format!("{}{}{}", "".repeat(PANEL_WIDTH), grid_char, hline);
writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
if self.panel_width == 0 {
writeln!(
self.handle,
"{}",
self.colors.grid.paint("".repeat(self.config.term_width))
)?;
} else {
let hline = "".repeat(self.config.term_width - (self.panel_width + 1));
let hline = format!("{}{}{}", "".repeat(self.panel_width), grid_char, hline);
writeln!(self.handle, "{}", self.colors.grid.paint(hline))?;
}
Ok(())
}
}
const GRID_COLOR: u8 = 238;
const LINE_NUMBER_COLOR: u8 = 244;
#[derive(Default)]
pub struct Colors {
pub grid: Style,
pub filename: Style,
pub git_added: Style,
pub git_removed: Style,
pub git_modified: Style,
pub line_number: Style,
}
impl Colors {
fn plain() -> Self {
Colors::default()
}
fn colored() -> Self {
Colors {
grid: Fixed(GRID_COLOR).normal(),
filename: White.bold(),
git_added: Green.normal(),
git_removed: Red.normal(),
git_modified: Yellow.normal(),
line_number: Fixed(LINE_NUMBER_COLOR).normal(),
}
}
}
+79
View File
@@ -0,0 +1,79 @@
use errors::*;
use std::collections::HashSet;
use std::str::FromStr;
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum OutputComponent {
Auto,
Changes,
Grid,
Header,
Numbers,
Full,
Plain,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub enum OutputWrap {
Character,
None,
}
impl OutputComponent {
pub fn components(&self, interactive_terminal: bool) -> &'static [OutputComponent] {
match *self {
OutputComponent::Auto => if interactive_terminal {
OutputComponent::Full.components(interactive_terminal)
} else {
OutputComponent::Plain.components(interactive_terminal)
},
OutputComponent::Changes => &[OutputComponent::Changes],
OutputComponent::Grid => &[OutputComponent::Grid],
OutputComponent::Header => &[OutputComponent::Header],
OutputComponent::Numbers => &[OutputComponent::Numbers],
OutputComponent::Full => &[
OutputComponent::Changes,
OutputComponent::Grid,
OutputComponent::Header,
OutputComponent::Numbers,
],
OutputComponent::Plain => &[],
}
}
}
impl FromStr for OutputComponent {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s {
"auto" => Ok(OutputComponent::Auto),
"changes" => Ok(OutputComponent::Changes),
"grid" => Ok(OutputComponent::Grid),
"header" => Ok(OutputComponent::Header),
"numbers" => Ok(OutputComponent::Numbers),
"full" => Ok(OutputComponent::Full),
"plain" | _ => Ok(OutputComponent::Plain),
}
}
}
pub struct OutputComponents(pub HashSet<OutputComponent>);
impl OutputComponents {
pub fn changes(&self) -> bool {
self.0.contains(&OutputComponent::Changes)
}
pub fn grid(&self) -> bool {
self.0.contains(&OutputComponent::Grid)
}
pub fn header(&self) -> bool {
self.0.contains(&OutputComponent::Header)
}
pub fn numbers(&self) -> bool {
self.0.contains(&OutputComponent::Numbers)
}
}
+20 -14
View File
@@ -1,8 +1,6 @@
use std::fmt::Write;
use ansi_term::Colour::{Fixed, RGB};
use ansi_term::Style;
use syntect::highlighting;
use syntect::highlighting::{self, FontStyle};
/// Approximate a 24 bit color value by a 8 bit ANSI code
fn rgb2ansi(r: u8, g: u8, b: u8) -> u8 {
@@ -27,25 +25,33 @@ fn rgb2ansi(r: u8, g: u8, b: u8) -> u8 {
}
pub fn as_terminal_escaped(
v: &[(highlighting::Style, &str)],
style: highlighting::Style,
text: &str,
true_color: bool,
colored: bool,
) -> String {
let mut s: String = String::new();
for &(ref style, text) in v.iter() {
let style = if !colored {
Style::default()
} else if true_color {
RGB(style.foreground.r, style.foreground.g, style.foreground.b).normal()
let style = if !colored {
Style::default()
} else {
let color = if true_color {
RGB(style.foreground.r, style.foreground.g, style.foreground.b)
} else {
let ansi = rgb2ansi(style.foreground.r, style.foreground.g, style.foreground.b);
Fixed(ansi).normal()
Fixed(ansi)
};
write!(s, "{}", style.paint(text)).unwrap();
}
if style.font_style.contains(FontStyle::BOLD) {
color.bold()
} else if style.font_style.contains(FontStyle::UNDERLINE) {
color.underline()
} else if style.font_style.contains(FontStyle::ITALIC) {
color.italic()
} else {
color.normal()
}
};
s
style.paint(text).to_string()
}
#[test]
+1
View File
@@ -0,0 +1 @@
test-src/* linguist-vendored
+1
View File
@@ -0,0 +1 @@
test-src/*
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
if ! which hyperfine > /dev/null 2>&1; then
echo "'hyperfine' does not seem to be installed."
echo "You can get it here: https://github.com/sharkdp/hyperfine"
exit 1
fi
SRC="test-src/jquery-3.3.1.js"
cmd_bat="bat --style=full --color=always --paging=never '$SRC'"
cmd_pygmentize="pygmentize -g '$SRC'"
cmd_highlight="highlight -O truecolor '$SRC'"
cmd_ccat="ccat --color=always '$SRC'"
cmd_source_highlight="source-highlight --failsafe --infer-lang -f esc -i '$SRC'"
cmd_hicat="hicat '$SRC'"
cmd_coderay="coderay '$SRC'"
cmd_rouge="rougify '$SRC'"
hyperfine --warmup 3 \
"$cmd_bat" \
"$cmd_pygmentize" \
"$cmd_highlight" \
"$cmd_ccat" \
"$cmd_source_highlight" \
"$cmd_hicat" \
"$cmd_coderay" \
"$cmd_rouge" \
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
if ! which hyperfine > /dev/null 2>&1; then
echo "'hyperfine' does not seem to be installed."
echo "You can get it here: https://github.com/sharkdp/hyperfine"
exit 1
fi
echo "### Startup time"
echo
hyperfine --warmup 3 bat
echo
echo "### Plain text"
echo
hyperfine --warmup 3 "bat --language txt --paging=never 'test-src/jquery-3.3.1.js'"
echo
echo "### Time to syntax-highlight large files"
echo
for SRC in test-src/*; do
hyperfine --warmup 3 "bat --style=full --color=always --paging=never '$SRC'"
done
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+602
View File
@@ -0,0 +1,602 @@
/**************************************************************************
*
* Copyright 2013-2014 RAD Game Tools and Valve Software
* Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
**************************************************************************/
#include "miniz.h"
typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1];
typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1];
typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];
#ifdef __cplusplus
extern "C" {
#endif
/* ------------------- zlib-style API's */
mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)
{
mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16);
size_t block_len = buf_len % 5552;
if (!ptr)
return MZ_ADLER32_INIT;
while (buf_len)
{
for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
{
s1 += ptr[0], s2 += s1;
s1 += ptr[1], s2 += s1;
s1 += ptr[2], s2 += s1;
s1 += ptr[3], s2 += s1;
s1 += ptr[4], s2 += s1;
s1 += ptr[5], s2 += s1;
s1 += ptr[6], s2 += s1;
s1 += ptr[7], s2 += s1;
}
for (; i < block_len; ++i)
s1 += *ptr++, s2 += s1;
s1 %= 65521U, s2 %= 65521U;
buf_len -= block_len;
block_len = 5552;
}
return (s2 << 16) + s1;
}
/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */
#if 0
mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
{
static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
mz_uint32 crcu32 = (mz_uint32)crc;
if (!ptr)
return MZ_CRC32_INIT;
crcu32 = ~crcu32;
while (buf_len--)
{
mz_uint8 b = *ptr++;
crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)];
crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)];
}
return ~crcu32;
}
#else
/* Faster, but larger CPU cache footprint.
*/
mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
{
static const mz_uint32 s_crc_table[256] =
{
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535,
0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD,
0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D,
0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4,
0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C,
0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC,
0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F,
0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB,
0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA,
0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE,
0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A,
0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409,
0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739,
0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268,
0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0,
0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8,
0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF,
0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703,
0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE,
0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242,
0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6,
0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D,
0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5,
0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605,
0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
};
mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF;
const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr;
while (buf_len >= 4)
{
crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF];
crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF];
crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF];
pByte_buf += 4;
buf_len -= 4;
}
while (buf_len)
{
crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF];
++pByte_buf;
--buf_len;
}
return ~crc32;
}
#endif
void mz_free(void *p)
{
MZ_FREE(p);
}
void *miniz_def_alloc_func(void *opaque, size_t items, size_t size)
{
(void)opaque, (void)items, (void)size;
return MZ_MALLOC(items * size);
}
void miniz_def_free_func(void *opaque, void *address)
{
(void)opaque, (void)address;
MZ_FREE(address);
}
void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size)
{
(void)opaque, (void)address, (void)items, (void)size;
return MZ_REALLOC(address, items * size);
}
const char *mz_version(void)
{
return MZ_VERSION;
}
#ifndef MINIZ_NO_ZLIB_APIS
int mz_deflateInit(mz_streamp pStream, int level)
{
return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);
}
int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
{
tdefl_compressor *pComp;
mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);
if (!pStream)
return MZ_STREAM_ERROR;
if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)))
return MZ_PARAM_ERROR;
pStream->data_type = 0;
pStream->adler = MZ_ADLER32_INIT;
pStream->msg = NULL;
pStream->reserved = 0;
pStream->total_in = 0;
pStream->total_out = 0;
if (!pStream->zalloc)
pStream->zalloc = miniz_def_alloc_func;
if (!pStream->zfree)
pStream->zfree = miniz_def_free_func;
pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));
if (!pComp)
return MZ_MEM_ERROR;
pStream->state = (struct mz_internal_state *)pComp;
if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
{
mz_deflateEnd(pStream);
return MZ_PARAM_ERROR;
}
return MZ_OK;
}
int mz_deflateReset(mz_streamp pStream)
{
if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree))
return MZ_STREAM_ERROR;
pStream->total_in = pStream->total_out = 0;
tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags);
return MZ_OK;
}
int mz_deflate(mz_streamp pStream, int flush)
{
size_t in_bytes, out_bytes;
mz_ulong orig_total_in, orig_total_out;
int mz_status = MZ_OK;
if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out))
return MZ_STREAM_ERROR;
if (!pStream->avail_out)
return MZ_BUF_ERROR;
if (flush == MZ_PARTIAL_FLUSH)
flush = MZ_SYNC_FLUSH;
if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;
orig_total_in = pStream->total_in;
orig_total_out = pStream->total_out;
for (;;)
{
tdefl_status defl_status;
in_bytes = pStream->avail_in;
out_bytes = pStream->avail_out;
defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
pStream->next_in += (mz_uint)in_bytes;
pStream->avail_in -= (mz_uint)in_bytes;
pStream->total_in += (mz_uint)in_bytes;
pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state);
pStream->next_out += (mz_uint)out_bytes;
pStream->avail_out -= (mz_uint)out_bytes;
pStream->total_out += (mz_uint)out_bytes;
if (defl_status < 0)
{
mz_status = MZ_STREAM_ERROR;
break;
}
else if (defl_status == TDEFL_STATUS_DONE)
{
mz_status = MZ_STREAM_END;
break;
}
else if (!pStream->avail_out)
break;
else if ((!pStream->avail_in) && (flush != MZ_FINISH))
{
if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
break;
return MZ_BUF_ERROR; /* Can't make forward progress without some input.
*/
}
}
return mz_status;
}
int mz_deflateEnd(mz_streamp pStream)
{
if (!pStream)
return MZ_STREAM_ERROR;
if (pStream->state)
{
pStream->zfree(pStream->opaque, pStream->state);
pStream->state = NULL;
}
return MZ_OK;
}
mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)
{
(void)pStream;
/* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */
return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);
}
int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)
{
int status;
mz_stream stream;
memset(&stream, 0, sizeof(stream));
/* In case mz_ulong is 64-bits (argh I hate longs). */
if ((source_len | *pDest_len) > 0xFFFFFFFFU)
return MZ_PARAM_ERROR;
stream.next_in = pSource;
stream.avail_in = (mz_uint32)source_len;
stream.next_out = pDest;
stream.avail_out = (mz_uint32)*pDest_len;
status = mz_deflateInit(&stream, level);
if (status != MZ_OK)
return status;
status = mz_deflate(&stream, MZ_FINISH);
if (status != MZ_STREAM_END)
{
mz_deflateEnd(&stream);
return (status == MZ_OK) ? MZ_BUF_ERROR : status;
}
*pDest_len = stream.total_out;
return mz_deflateEnd(&stream);
}
int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);
}
mz_ulong mz_compressBound(mz_ulong source_len)
{
return mz_deflateBound(NULL, source_len);
}
typedef struct
{
tinfl_decompressor m_decomp;
mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed;
int m_window_bits;
mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
tinfl_status m_last_status;
} inflate_state;
int mz_inflateInit2(mz_streamp pStream, int window_bits)
{
inflate_state *pDecomp;
if (!pStream)
return MZ_STREAM_ERROR;
if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))
return MZ_PARAM_ERROR;
pStream->data_type = 0;
pStream->adler = 0;
pStream->msg = NULL;
pStream->total_in = 0;
pStream->total_out = 0;
pStream->reserved = 0;
if (!pStream->zalloc)
pStream->zalloc = miniz_def_alloc_func;
if (!pStream->zfree)
pStream->zfree = miniz_def_free_func;
pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
if (!pDecomp)
return MZ_MEM_ERROR;
pStream->state = (struct mz_internal_state *)pDecomp;
tinfl_init(&pDecomp->m_decomp);
pDecomp->m_dict_ofs = 0;
pDecomp->m_dict_avail = 0;
pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
pDecomp->m_first_call = 1;
pDecomp->m_has_flushed = 0;
pDecomp->m_window_bits = window_bits;
return MZ_OK;
}
int mz_inflateInit(mz_streamp pStream)
{
return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);
}
int mz_inflate(mz_streamp pStream, int flush)
{
inflate_state *pState;
mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
size_t in_bytes, out_bytes, orig_avail_in;
tinfl_status status;
if ((!pStream) || (!pStream->state))
return MZ_STREAM_ERROR;
if (flush == MZ_PARTIAL_FLUSH)
flush = MZ_SYNC_FLUSH;
if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH))
return MZ_STREAM_ERROR;
pState = (inflate_state *)pStream->state;
if (pState->m_window_bits > 0)
decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
orig_avail_in = pStream->avail_in;
first_call = pState->m_first_call;
pState->m_first_call = 0;
if (pState->m_last_status < 0)
return MZ_DATA_ERROR;
if (pState->m_has_flushed && (flush != MZ_FINISH))
return MZ_STREAM_ERROR;
pState->m_has_flushed |= (flush == MZ_FINISH);
if ((flush == MZ_FINISH) && (first_call))
{
/* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */
decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
in_bytes = pStream->avail_in;
out_bytes = pStream->avail_out;
status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
pState->m_last_status = status;
pStream->next_in += (mz_uint)in_bytes;
pStream->avail_in -= (mz_uint)in_bytes;
pStream->total_in += (mz_uint)in_bytes;
pStream->adler = tinfl_get_adler32(&pState->m_decomp);
pStream->next_out += (mz_uint)out_bytes;
pStream->avail_out -= (mz_uint)out_bytes;
pStream->total_out += (mz_uint)out_bytes;
if (status < 0)
return MZ_DATA_ERROR;
else if (status != TINFL_STATUS_DONE)
{
pState->m_last_status = TINFL_STATUS_FAILED;
return MZ_BUF_ERROR;
}
return MZ_STREAM_END;
}
/* flush != MZ_FINISH then we must assume there's more input. */
if (flush != MZ_FINISH)
decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;
if (pState->m_dict_avail)
{
n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
pStream->next_out += n;
pStream->avail_out -= n;
pStream->total_out += n;
pState->m_dict_avail -= n;
pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}
for (;;)
{
in_bytes = pStream->avail_in;
out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;
status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
pState->m_last_status = status;
pStream->next_in += (mz_uint)in_bytes;
pStream->avail_in -= (mz_uint)in_bytes;
pStream->total_in += (mz_uint)in_bytes;
pStream->adler = tinfl_get_adler32(&pState->m_decomp);
pState->m_dict_avail = (mz_uint)out_bytes;
n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
pStream->next_out += n;
pStream->avail_out -= n;
pStream->total_out += n;
pState->m_dict_avail -= n;
pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
if (status < 0)
return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */
else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */
else if (flush == MZ_FINISH)
{
/* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */
if (status == TINFL_STATUS_DONE)
return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
/* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */
else if (!pStream->avail_out)
return MZ_BUF_ERROR;
}
else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
break;
}
return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}
int mz_inflateEnd(mz_streamp pStream)
{
if (!pStream)
return MZ_STREAM_ERROR;
if (pStream->state)
{
pStream->zfree(pStream->opaque, pStream->state);
pStream->state = NULL;
}
return MZ_OK;
}
int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
mz_stream stream;
int status;
memset(&stream, 0, sizeof(stream));
/* In case mz_ulong is 64-bits (argh I hate longs). */
if ((source_len | *pDest_len) > 0xFFFFFFFFU)
return MZ_PARAM_ERROR;
stream.next_in = pSource;
stream.avail_in = (mz_uint32)source_len;
stream.next_out = pDest;
stream.avail_out = (mz_uint32)*pDest_len;
status = mz_inflateInit(&stream);
if (status != MZ_OK)
return status;
status = mz_inflate(&stream, MZ_FINISH);
if (status != MZ_STREAM_END)
{
mz_inflateEnd(&stream);
return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;
}
*pDest_len = stream.total_out;
return mz_inflateEnd(&stream);
}
const char *mz_error(int err)
{
static struct
{
int m_err;
const char *m_pDesc;
} s_error_descs[] =
{
{ MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" }
};
mz_uint i;
for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i)
if (s_error_descs[i].m_err == err)
return s_error_descs[i].m_pDesc;
return NULL;
}
#endif /*MINIZ_NO_ZLIB_APIS */
#ifdef __cplusplus
}
#endif
/*
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
*/
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
import itertools
import subprocess
import pathlib
import shutil
def generate_snapshots():
single_styles = ["changes", "grid", "header", "numbers"]
collective_styles = ["full", "plain"]
for num in range(len(single_styles)):
for grouped in itertools.combinations(single_styles, num + 1):
generate_snapshot(",".join(grouped))
for style in collective_styles:
generate_snapshot(style)
def generate_snapshot(option):
command = "../../target/debug/bat --style={0} sample.rs > output/{0}.snapshot.txt".format(
option
)
print("generating snapshot for {}".format(option))
subprocess.call(command, shell=True)
def build_bat():
print("building bat")
subprocess.call("cargo build", cwd="../..", shell=True)
def prepare_output_dir():
shutil.rmtree("output", ignore_errors=True)
pathlib.Path("output").mkdir()
def modify_sample_file():
print("modifying sample.rs to show changes")
shutil.copyfile("sample.modified.rs", "sample.rs")
def undo_sample_file_modification():
print("undoing sample.rs modifications")
subprocess.call("git checkout -- sample.rs", shell=True)
build_bat()
prepare_output_dir()
modify_sample_file()
generate_snapshots()
undo_sample_file_modification()
@@ -0,0 +1,26 @@
───────┬────────────────────────────────────────────────────────────────────────
│ File: sample.rs
───────┼────────────────────────────────────────────────────────────────────────
1 │ struct Rectangle {
2 │ width: u32,
3 │ height: u32,
4 │ }
5 │
6 _ │ fn main() {
7 │ let rect1 = Rectangle { width: 30, height: 50 };
8 │
9 │ println!(
10 ~ │ "The perimeter of the rectangle is {} pixels.",
11 ~ │ perimeter(&rect1)
12 │ );
13 + │ println!(r#"This line contains invalid utf8: ""#;
14 │ }
15 │
16 │ fn area(rectangle: &Rectangle) -> u32 {
17 │ rectangle.width * rectangle.height
18 │ }
19 + │
20 + │ fn perimeter(rectangle: &Rectangle) -> u32 {
21 + │ (rectangle.width + rectangle.height) * 2
22 + │ }
───────┴────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,26 @@
──┬─────────────────────────────────────────────────────────────────────────────
│ File: sample.rs
──┼─────────────────────────────────────────────────────────────────────────────
│ struct Rectangle {
│ width: u32,
│ height: u32,
│ }
_ │ fn main() {
│ let rect1 = Rectangle { width: 30, height: 50 };
│ println!(
~ │ "The perimeter of the rectangle is {} pixels.",
~ │ perimeter(&rect1)
│ );
+ │ println!(r#"This line contains invalid utf8: ""#;
│ }
│ fn area(rectangle: &Rectangle) -> u32 {
│ rectangle.width * rectangle.height
│ }
+ │
+ │ fn perimeter(rectangle: &Rectangle) -> u32 {
+ │ (rectangle.width + rectangle.height) * 2
+ │ }
──┴─────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,23 @@
1 │ struct Rectangle {
2 │ width: u32,
3 │ height: u32,
4 │ }
5 │
6 _ │ fn main() {
7 │ let rect1 = Rectangle { width: 30, height: 50 };
8 │
9 │ println!(
10 ~ │ "The perimeter of the rectangle is {} pixels.",
11 ~ │ perimeter(&rect1)
12 │ );
13 + │ println!(r#"This line contains invalid utf8: ""#;
14 │ }
15 │
16 │ fn area(rectangle: &Rectangle) -> u32 {
17 │ rectangle.width * rectangle.height
18 │ }
19 + │
20 + │ fn perimeter(rectangle: &Rectangle) -> u32 {
21 + │ (rectangle.width + rectangle.height) * 2
22 + │ }
───────┴────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,23 @@
│ struct Rectangle {
│ width: u32,
│ height: u32,
│ }
_ │ fn main() {
│ let rect1 = Rectangle { width: 30, height: 50 };
│ println!(
~ │ "The perimeter of the rectangle is {} pixels.",
~ │ perimeter(&rect1)
│ );
+ │ println!(r#"This line contains invalid utf8: ""#;
│ }
│ fn area(rectangle: &Rectangle) -> u32 {
│ rectangle.width * rectangle.height
│ }
+ │
+ │ fn perimeter(rectangle: &Rectangle) -> u32 {
+ │ (rectangle.width + rectangle.height) * 2
+ │ }
──┴─────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,23 @@
File: sample.rs
1 struct Rectangle {
2 width: u32,
3 height: u32,
4 }
5
6 _ fn main() {
7 let rect1 = Rectangle { width: 30, height: 50 };
8
9 println!(
10 ~ "The perimeter of the rectangle is {} pixels.",
11 ~ perimeter(&rect1)
12 );
13 + println!(r#"This line contains invalid utf8: ""#;
14 }
15
16 fn area(rectangle: &Rectangle) -> u32 {
17 rectangle.width * rectangle.height
18 }
19 +
20 + fn perimeter(rectangle: &Rectangle) -> u32 {
21 + (rectangle.width + rectangle.height) * 2
22 + }
@@ -0,0 +1,23 @@
File: sample.rs
struct Rectangle {
width: u32,
height: u32,
}
_ fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
~ "The perimeter of the rectangle is {} pixels.",
~ perimeter(&rect1)
);
+ println!(r#"This line contains invalid utf8: ""#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
+
+ fn perimeter(rectangle: &Rectangle) -> u32 {
+ (rectangle.width + rectangle.height) * 2
+ }
@@ -0,0 +1,22 @@
1 struct Rectangle {
2 width: u32,
3 height: u32,
4 }
5
6 _ fn main() {
7 let rect1 = Rectangle { width: 30, height: 50 };
8
9 println!(
10 ~ "The perimeter of the rectangle is {} pixels.",
11 ~ perimeter(&rect1)
12 );
13 + println!(r#"This line contains invalid utf8: ""#;
14 }
15
16 fn area(rectangle: &Rectangle) -> u32 {
17 rectangle.width * rectangle.height
18 }
19 +
20 + fn perimeter(rectangle: &Rectangle) -> u32 {
21 + (rectangle.width + rectangle.height) * 2
22 + }
@@ -0,0 +1,22 @@
struct Rectangle {
width: u32,
height: u32,
}
_ fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
~ "The perimeter of the rectangle is {} pixels.",
~ perimeter(&rect1)
);
+ println!(r#"This line contains invalid utf8: ""#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
+
+ fn perimeter(rectangle: &Rectangle) -> u32 {
+ (rectangle.width + rectangle.height) * 2
+ }
+26
View File
@@ -0,0 +1,26 @@
───────┬────────────────────────────────────────────────────────────────────────
│ File: sample.rs
───────┼────────────────────────────────────────────────────────────────────────
1 │ struct Rectangle {
2 │ width: u32,
3 │ height: u32,
4 │ }
5 │
6 _ │ fn main() {
7 │ let rect1 = Rectangle { width: 30, height: 50 };
8 │
9 │ println!(
10 ~ │ "The perimeter of the rectangle is {} pixels.",
11 ~ │ perimeter(&rect1)
12 │ );
13 + │ println!(r#"This line contains invalid utf8: ""#;
14 │ }
15 │
16 │ fn area(rectangle: &Rectangle) -> u32 {
17 │ rectangle.width * rectangle.height
18 │ }
19 + │
20 + │ fn perimeter(rectangle: &Rectangle) -> u32 {
21 + │ (rectangle.width + rectangle.height) * 2
22 + │ }
───────┴────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,26 @@
─────┬──────────────────────────────────────────────────────────────────────────
│ File: sample.rs
─────┼──────────────────────────────────────────────────────────────────────────
1 │ struct Rectangle {
2 │ width: u32,
3 │ height: u32,
4 │ }
5 │
6 │ fn main() {
7 │ let rect1 = Rectangle { width: 30, height: 50 };
8 │
9 │ println!(
10 │ "The perimeter of the rectangle is {} pixels.",
11 │ perimeter(&rect1)
12 │ );
13 │ println!(r#"This line contains invalid utf8: ""#;
14 │ }
15 │
16 │ fn area(rectangle: &Rectangle) -> u32 {
17 │ rectangle.width * rectangle.height
18 │ }
19 │
20 │ fn perimeter(rectangle: &Rectangle) -> u32 {
21 │ (rectangle.width + rectangle.height) * 2
22 │ }
─────┴──────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,26 @@
────────────────────────────────────────────────────────────────────────────────
File: sample.rs
────────────────────────────────────────────────────────────────────────────────
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The perimeter of the rectangle is {} pixels.",
perimeter(&rect1)
);
println!(r#"This line contains invalid utf8: ""#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2
}
────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,23 @@
1 │ struct Rectangle {
2 │ width: u32,
3 │ height: u32,
4 │ }
5 │
6 │ fn main() {
7 │ let rect1 = Rectangle { width: 30, height: 50 };
8 │
9 │ println!(
10 │ "The perimeter of the rectangle is {} pixels.",
11 │ perimeter(&rect1)
12 │ );
13 │ println!(r#"This line contains invalid utf8: ""#;
14 │ }
15 │
16 │ fn area(rectangle: &Rectangle) -> u32 {
17 │ rectangle.width * rectangle.height
18 │ }
19 │
20 │ fn perimeter(rectangle: &Rectangle) -> u32 {
21 │ (rectangle.width + rectangle.height) * 2
22 │ }
─────┴──────────────────────────────────────────────────────────────────────────
+23
View File
@@ -0,0 +1,23 @@
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The perimeter of the rectangle is {} pixels.",
perimeter(&rect1)
);
println!(r#"This line contains invalid utf8: ""#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2
}
────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,23 @@
File: sample.rs
1 struct Rectangle {
2 width: u32,
3 height: u32,
4 }
5
6 fn main() {
7 let rect1 = Rectangle { width: 30, height: 50 };
8
9 println!(
10 "The perimeter of the rectangle is {} pixels.",
11 perimeter(&rect1)
12 );
13 println!(r#"This line contains invalid utf8: ""#;
14 }
15
16 fn area(rectangle: &Rectangle) -> u32 {
17 rectangle.width * rectangle.height
18 }
19
20 fn perimeter(rectangle: &Rectangle) -> u32 {
21 (rectangle.width + rectangle.height) * 2
22 }
@@ -0,0 +1,23 @@
File: sample.rs
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The perimeter of the rectangle is {} pixels.",
perimeter(&rect1)
);
println!(r#"This line contains invalid utf8: ""#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2
}
@@ -0,0 +1,22 @@
1 struct Rectangle {
2 width: u32,
3 height: u32,
4 }
5
6 fn main() {
7 let rect1 = Rectangle { width: 30, height: 50 };
8
9 println!(
10 "The perimeter of the rectangle is {} pixels.",
11 perimeter(&rect1)
12 );
13 println!(r#"This line contains invalid utf8: ""#;
14 }
15
16 fn area(rectangle: &Rectangle) -> u32 {
17 rectangle.width * rectangle.height
18 }
19
20 fn perimeter(rectangle: &Rectangle) -> u32 {
21 (rectangle.width + rectangle.height) * 2
22 }
+22
View File
@@ -0,0 +1,22 @@
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The perimeter of the rectangle is {} pixels.",
perimeter(&rect1)
);
println!(r#"This line contains invalid utf8: ""#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2
}
+22
View File
@@ -0,0 +1,22 @@
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The perimeter of the rectangle is {} pixels.",
perimeter(&rect1)
);
println!(r#"This line contains invalid utf8: "øˆ€€€"#;
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
fn perimeter(rectangle: &Rectangle) -> u32 {
(rectangle.width + rectangle.height) * 2
}
+18
View File
@@ -0,0 +1,18 @@
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// width and height of a rectangle can be different
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
+72
View File
@@ -0,0 +1,72 @@
use std::env;
use std::fs::{self, File};
use std::io::Read;
use std::path::PathBuf;
use std::process::Command;
pub struct BatTester {
exe: PathBuf,
}
impl BatTester {
pub fn new() -> Self {
modify_sample_file();
let root = env::current_exe()
.expect("tests executable")
.parent()
.expect("tests executable directory")
.parent()
.expect("bat executable directory")
.to_path_buf();
let exe_name = if cfg!(windows) { "bat.exe" } else { "bat" };
BatTester {
exe: root.join(exe_name),
}
}
pub fn test_snapshot(&self, style: &str) {
let output = Command::new(&self.exe)
.args(&["tests/snapshots/sample.rs", &format!("--style={}", style)])
.output()
.expect("bat failed");
// have to do the replace because the filename in the header changes based on the current working directory
let actual = String::from_utf8_lossy(&output.stdout)
.as_ref()
.replace("tests/snapshots/", "");
let mut expected = String::new();
let mut file = File::open(format!("tests/snapshots/output/{}.snapshot.txt", style))
.expect("snapshot file missing");
file.read_to_string(&mut expected)
.expect("could not read snapshot file");
assert_eq!(expected, actual);
}
}
impl Drop for BatTester {
fn drop(&mut self) {
undo_sample_file_modification();
}
}
fn modify_sample_file() {
fs::copy(
"tests/snapshots/sample.modified.rs",
"tests/snapshots/sample.rs",
).expect("generating modified sample file failed");
}
fn undo_sample_file_modification() {
let output = Command::new("git")
.args(&["checkout", "--", "tests/snapshots/sample.rs"])
.output()
.expect("git checkout failed");
if !output.status.success() {
panic!("undoing modified sample changes failed")
}
}
+45
View File
@@ -0,0 +1,45 @@
mod tester;
use std::process::{Command, Stdio};
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 status = Command::new("git")
.arg("rev-parse")
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
if !status.map(|s| s.success()).unwrap_or(false) {
// Git not available or not a git repository. Skipping snapshot test.
return;
}
let bat_tester = BatTester::new();
for style in STYLES {
println!("testing {}", style);
bat_tester.test_snapshot(&*style);
}
}