Compare commits

..
Author SHA1 Message Date
dependabot[bot] 6eee1a8927 build(deps): bump encoding_rs from 0.8.35 to 0.8.42
Bumps [encoding_rs](https://github.com/hsivonen/encoding_rs) from 0.8.35 to 0.8.42.
- [Commits](https://github.com/hsivonen/encoding_rs/compare/v0.8.35...v0.8.42)

---
updated-dependencies:
- dependency-name: encoding_rs
  dependency-version: 0.8.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-01 02:04:07 +00:00
36 changed files with 159 additions and 2753 deletions

No files matched your search

+2 -3
View File
@@ -166,8 +166,7 @@ jobs:
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-latest , dpkg_arch: armhf, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-latest , dpkg_arch: musl-linux-armhf, use-cross: true }
- { target: i686-pc-windows-msvc , os: windows-2025 , }
# The timeout test's socketpair fails on the Ubuntu 26.04 runner.
- { target: i686-unknown-linux-gnu , os: ubuntu-24.04 , dpkg_arch: i386, use-cross: true }
- { target: i686-unknown-linux-gnu , os: ubuntu-latest , dpkg_arch: i386, use-cross: true }
- { target: i686-unknown-linux-musl , os: ubuntu-latest , dpkg_arch: musl-linux-i686, use-cross: true }
- { target: x86_64-apple-darwin , os: macos-15-intel, }
- { target: aarch64-apple-darwin , os: macos-latest , }
@@ -458,7 +457,7 @@ jobs:
needs: build
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: vedantmgoyal9/winget-releaser@b7370d4d152efdce803a26cc6ca9eee58cb2ad54
- uses: vedantmgoyal9/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f
with:
identifier: sharkdp.bat
installers-regex: '-pc-windows-msvc\.zip$'
-3
View File
@@ -284,6 +284,3 @@
[submodule "assets/syntaxes/02_Extra/Caddy"]
path = assets/syntaxes/02_Extra/Caddy
url = https://github.com/caddyserver/sublimetext.git
[submodule "assets/syntaxes/02_Extra/CUE"]
path = assets/syntaxes/02_Extra/CUE
url = https://github.com/cue-lang/vscode-cue.git
-28
View File
@@ -3,22 +3,15 @@
## Other
- Document submodule initialization when rebuilding syntax and theme assets, see #3801 (@xfocus3)
- Fix anchor case in `doc/assets.md` links, see #4026 (@ump45nose)
- Pin the i686 GNU CI runner to Ubuntu 24.04 to avoid `socketpair` failures on Ubuntu 26.04, see #4101 (@PerchAnton)
- Fix dead Visual C++ Redistributable link in README, see #3895 (@1t1sCooL)
- Document `StripAnsiMode` and its variants in rustdoc, see #4067 (@ump45nose)
- Update Cargo dependencies to resolve current RustSec advisories, see #3861 (@TyceHerrman)
- Add instructions for removing fish help abbreviations to README, see #3655 (@claw-explorer). Closes #3536
- Add .NET slnx extension, see #3682 (@ltrzesniewski)
## Features
- Automatically build the syntax/theme cache on first run when custom themes or syntaxes are present but no cache exists yet, so newly installed dotfiles work without a manual `bat cache --build`. Use `--no-custom-assets` to opt out. Closes #4017, see #4038 (@mmustafasenoglu)
- Add `-b` / `--number-nonblank` flag to only number non-blank lines, for `cat -b` compatibility. Closes #3856, see #3857 (@MeGaurav4)
- Add a `--sanitize=<auto|always|never>` flag for safe display of untrusted input. It implies `--strip-ansi` at the same value and additionally substitutes terminal-active control bytes (cursor moves, charset switches, beep, etc.) and Unicode bidi / zero-width formatting characters with the Unicode replacement character (U+FFFD). Mitigates Trojan-Source-style spoofing (CVE-2021-42574). See #3729 (@curious-rabbit)
- Map justfile, Justfile, .justfile, and *.justfile to Makefile syntax highlighting, see #3623 (@zachvalenta)
- Map `.kube/config` to YAML syntax highlighting, see #3911 (@logarithmone1128)
- Preserve `--diff` change markers and snip separators when `--plain` is set. Closes #3630, see #3643 (@mvanhorn)
- Added support for `hidden_file_extensions` from `.sublime-syntax` files, see #3613 (@Matei02355)
- Add word wrapping mode via `--wrap=word`, see #3597 (@veeceey)
@@ -32,26 +25,8 @@
- Syntax highlighting for Python files using uv as script runner in shebang #3689 (@janlarres)
## Bugfixes
- Ignore empty `--ignored-suffix` values to avoid a stack overflow, see #4122 (@spicysharkco)
- Fix `--number-nonblank` counting empty UTF-16 lines, see #4118 (@pcbeingused333)
- Don't emit `--highlight-line` escape sequences (background color or `ansi` theme underline) when colors are disabled with `--color=never`. Closes #4099, see #4100 (@DeviousCardi)
- Fix missing colors, pager, and decorations in mintty/MSYS2 and Cygwin terminals on Windows, see #4115 (@rezamuliawijaya)
- Make full-path `--map-syntax` patterns and the built-in `$HOME` and `$XDG_CONFIG_HOME` mappings work on Windows, see #4113 (@b84330808)
- Keep configuration and BAT_OPTS active for option-like filenames after `--`, and report invalid configuration for help-like filenames, see #4112 (@Marve10s)
- Keep emoji sequences together within text chunks when wrapping content, and measure their combined display width, see #4108 (@Marve10s)
- Preserve full-path syntax mappings when removing ignored filename suffixes, see #4111 (@Marve10s)
- Fix UTF-16 line splitting at unaligned newline bytes corrupting text and shifting line ranges, see #4110 (@Marve10s)
- Respect `NO_COLOR` for the `[bat error]` and `[bat warning]` prefixes, see #4048 (@qodelicious)
- Offer the valid `--wrap` value `auto` instead of the rejected `always` in the PowerShell completion, see #4070 (@ump45nose)
- Report `BAT_CONFIG_DIR` in `bat --diagnostic`, so bug reports show a config directory that overrides `$XDG_CONFIG_HOME`. See #4068 (@ump45nose)
- Ignore a UTF-8 byte order mark at the start of the config file instead of treating it as part of the first argument, see #4103 (@jaeminbird)
- Keep backslashes in the pager command on Windows, so `BAT_PAGER=C:\tools\less.exe` is found instead of becoming `C:toolsless.exe`, see #4104 (@jaeminbird)
- Fix `bat` hanging forever on JavaScript (and Svelte) code where an expression is followed by a block comment, e.g. `a /* x */`. Closes #4080, see #4091 (@NavpreetSSidhu)
- Fix the first line of UTF-16BE input swallowing the second line, which broke line numbers, `--line-range` and highlighting, see #4102 (@cindykrafft)
- Exit quietly with status 0 instead of panicking when stdout is closed early (broken pipe) on auxiliary output paths such as `--completion`, `--config-file`, `--diagnostic`, `--generate-config-file`, `--set-terminal-title` and `cache --build`/`--clear`, matching the main output path since #232, closes #4034, see #4035 (@Mathjk)
- Track strikethrough (SGR 9 / 29) in the ANSI style tracker so it is re-emitted like bold, dim, italic, and underline, and is no longer dropped after the first highlighted token when displaying ANSI input with syntax highlighting, see #4015 (@mikehasa)
- Allow boolean flags to be given more than once, so that a flag set in the config file can also be passed on the command line without erroring, see #3912 (@logarithmone1128)
- Allow options that take a value (`--binary`, `--italic-text`, `--nonprintable-notation`, `--fallback-syntax`, `--squeeze-limit`) to be given more than once, so that a value from the config file can be overridden on the command line, see #4102 (@Huang-404-Q)
- Use parsed CLI arguments to detect number flags, see #3908 (@cuishuang)
- Detect binary content beyond the first line, preventing encrypted files with early line breaks from being treated as text. Closes #3554, see #3877 (@Matei02355)
- Use the last occurrence of numbering and plain flags in combined short arguments, see #3897 (@cuishuang)
@@ -110,8 +85,6 @@
- Add syntax highlighting for `Caddyfile` #3789 (@CosmicHorrorDev)
- Include `.code-workspace` as a JSON extension #3809 (@dhruvkb)
- Add syntax mapping for DNF repo configuration files, see #3814 (@injust)
- Add syntax mapping for Podman `.quadlets` files, see #4069 (@injust)
- Add syntax highlighting for CUE, see #4094 (@nikarh)
## Themes
@@ -127,7 +100,6 @@
## Bugfixes
- Fix `capacity overflow` panic when wrapping a double-width character on a line narrower than the character while a background is painted (e.g. `--terminal-width=1 --wrap=character --highlight-line=1`). Closes #3844, see #4098 (@feafel)
- Fix hang when using `--list-themes` with an explicit pager, see #3457 (@abhinavcool42)
- Fix negative values of N not being parsed in <N:M> line ranges without `=` flag value separator, see #3442 (@lmmx)
- Fix broken Docker syntax preventing use of custom assets, see #3476 (@keith-hall)
Generated
+25 -2
View File
@@ -396,6 +396,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "core_detect"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -660,11 +666,16 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
version = "0.8.35"
version = "0.8.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679"
dependencies = [
"cfg-if",
"core_detect",
"multiversion_no_op",
"rustversion",
"scopeguard",
"simdutf8",
]
[[package]]
@@ -1880,6 +1891,12 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "multiversion_no_op"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
[[package]]
name = "nix"
version = "0.31.3"
@@ -2461,6 +2478,12 @@ version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
[[package]]
name = "simdutf8"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "smallvec"
version = "1.15.1"
+1 -1
View File
@@ -104,7 +104,7 @@ tempfile = "3.27.0"
serde = { version = "1.0", features = ["derive"] }
[target.'cfg(unix)'.dev-dependencies]
nix = { version = "0.31", default-features = false, features = ["poll", "term"] }
nix = { version = "0.31", default-features = false, features = ["term"] }
[build-dependencies]
anyhow = "1.0.97"
+2 -8
View File
@@ -417,7 +417,7 @@ scoop install bat
You can download prebuilt binaries from the [Release page](https://github.com/sharkdp/bat/releases),
You will need to install the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) package.
You will need to install the [Visual C++ Redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) package.
### From binaries
@@ -431,12 +431,6 @@ If you want to build `bat` from source, you need Rust 1.79.0 or
higher. You can then use `cargo` to build everything:
#### From local source
The checkout includes prebuilt syntax and theme assets. If you want to regenerate them
from their sources, first initialize the Git submodules with
`git submodule update --init --recursive`, then follow the [Development](#development)
instructions. This includes the `help` syntax used to highlight command-line help.
```bash
cargo install --path . --locked
```
@@ -800,7 +794,7 @@ Example configuration file:
### Prerequisites
You will need to install the [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) package.
You will need to install the [Visual C++ Redistributable](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads) package.
### Paging
+1 -1
View File
@@ -9,7 +9,7 @@ Register-ArgumentCompleter -Native -CommandName '{{PROJECT_EXECUTABLE}}' -Script
$ArrayCompletion = @('bash', 'fish', 'zsh', 'ps1')
$ArrayWhen = @('auto', 'never', 'always')
$ArrayYesNo = @('never', 'always')
$ArrayWrap = @('auto', 'never', 'character', 'word')
$ArrayWrap = @('always', 'never', 'character', 'word')
$ArrayBinary = @('no-printing', 'as-text')
$ArrayPrint = @('unicode', 'caret')
-10
View File
@@ -12,13 +12,3 @@ index 05a4fed6..78a7bf55 100644
scope: source.js
variables:
bin_digit: '[01_]'
@@ -93,7 +93,8 @@ variables:
(?! \+\+ | -- )
(?=
!= |
- [-+*/%><=&|^\[(;,.:?] |
+ [-+*%><=&|^\[(;,.:?] |
+ /(?!\*) |
(?:in|instanceof){{identifier_break}}
)
))
-952
View File
@@ -1,952 +0,0 @@
%YAML 1.2
---
# http://www.sublimetext.com/docs/3/syntax.html
name: CUE
file_extensions:
- cue
scope: source.cue
contexts:
main:
- include: invalid_slash
- include: whitespace
- include: comment
- match: '(?<![\p{L}\p{Nd}_\$\#])(package)[ \t]+([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*)(?![\p{L}\p{Nd}_\$\#])'
captures:
1: keyword.other.package
2: entity.name.namespace
- match: '(?<![\p{L}\p{Nd}_\$\#])(import)[ \t]+(\()'
captures:
1: keyword.other.import
2: punctuation.section.parens.begin
push:
- meta_scope: meta.imports
- match: \)
captures:
0: punctuation.section.parens.end
pop: true
- include: whitespace
- include: comment
- match: '(?:([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_)[ \t]+)?(")([^:"]+)(?:(:)([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*))?(")'
scope: meta.import-spec
captures:
1: entity.name.namespace
2: punctuation.definition.string.begin
3: string.quoted.double-import
4: punctuation.colon
5: entity.name
6: punctuation.definition.string.end
- match: ;
scope: punctuation.separator
- include: invalid_in_parens
- match: '(?<![\p{L}\p{Nd}_\$\#])(import)[ \t]+(?:([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_)[ \t]+)?(")([^:"]+)(?:(:)([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*))?(")'
scope: meta.import
captures:
1: keyword.other.import
2: entity.name.namespace
3: punctuation.definition.string.begin
4: string.quoted.double-import
5: punctuation.colon
6: entity.name
7: punctuation.definition.string.end
- include: punctuation_comma
- include: declaration
- include: invalid_in_braces
comment:
- match: (//).*$\n?
scope: comment.line
captures:
1: punctuation.definition.comment
alias_postfix:
- match: '(~)[ \t]*(?:([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_)|(\()[ \t]*([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_)(?:[ \t]*(,)[ \t]*([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_))?[ \t]*(\)))'
captures:
1: punctuation.alias
2: variable.other
3: punctuation.section.parens.begin
4: variable.other
5: punctuation.separator
6: variable.other
7: punctuation.section.parens.end
attribute:
- match: '(@)([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)(\()'
captures:
1: punctuation.definition.annotation
2: variable.annotation
3: punctuation.attribute-elements.begin
push:
- meta_scope: meta.annotation
- match: \)
captures:
0: punctuation.attribute-elements.end
pop: true
- include: comment
- include: punctuation_comma
- include: attribute_element
attribute_element:
- match: '[ \t]*(?<![\p{L}\p{Nd}_\$\#])((?:[\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)(?:\.(?:[\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+))*)[ \t]*(=)(?!=)'
captures:
1: variable.other
2: punctuation.bind
push:
- match: '(?=[,\)])'
pop: true
- include: comment
- include: attribute_nested
- include: attribute_string
- include: attribute_nested
- include: attribute_string
attribute_nested:
- match: '[ \t]*(?:([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)[ \t]*)?(\()'
captures:
1: variable.other
2: punctuation.attribute-elements.begin
push:
- match: \)
captures:
0: punctuation.attribute-elements.end
pop: true
- include: comment
- include: punctuation_comma
- include: attribute_element
attribute_string:
- include: string
- match: '(?:[^\n,"''#\(\)/]|/(?!/)|(?<!#)#++(?!["'']))+'
scope: string.unquoted
- match: '[^,\)]+'
scope: invalid
declaration:
- include: attribute
- include: alias_postfix
- include: punctuation_ellipsis
- include: punctuation_colon
- include: punctuation_option
- include: punctuation_bind
- include: expression
expression:
- match: '(?<![\p{L}\p{Nd}_\$\#])(for)[ \t]+([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_)(?:[ \t]*(,)[ \t]*([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_))?[ \t]+(in)(?![\p{L}\p{Nd}_\$\#])(?:[ \t]+(else|otherwise|fallback|try)(?![\p{L}\p{Nd}_\$\#]))?'
captures:
1: keyword.control.for
2: variable.other
3: punctuation.separator
4: variable.other
5: keyword.control.in
6: variable.other
- match: '(?<![\p{L}\p{Nd}_\$\#])(if)(?![\p{L}\p{Nd}_\$\#])(?:[ \t]+(else|otherwise|fallback|try)(?![\p{L}\p{Nd}_\$\#]))?'
captures:
1: keyword.control.conditional
2: variable.other
- match: '(?<![\p{L}\p{Nd}_\$\#])(try)[ \t]+([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+|_)[ \t]*(=)(?![=~])'
captures:
1: keyword.control.try
2: variable.other
3: punctuation.bind
- match: '(?<![\p{L}\p{Nd}_\$\#])try(?=[ \t]*\{|[ \t]+(?:for|if|let|try)(?![\p{L}\p{Nd}_\$\#]))'
scope: keyword.control.try
- match: '(?<![\p{L}\p{Nd}_\$\#])(?:else|otherwise|fallback)(?=[ \t]*\{)'
scope: keyword.control.conditional
- match: '(?<![\p{L}\p{Nd}_\$\#])func(?=[ \t]*\()'
scope: keyword.function
- match: '(?<![\p{L}\p{Nd}_\$\#])(let)[ \t]+([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)[ \t]*(=)(?![=])'
captures:
1: keyword.control.let
2: variable.other
3: punctuation.bind
- match: "->"
scope: keyword.operator.arrow
- match: '[\+\-\*]|/(?!/)'
scope: keyword.operator
- match: "=[=~]|![=~]|<=|>=|[<](?!=)|[>](?![=])"
scope: keyword.operator.comparison
- match: '&{2}|\|{2}|!(?![=~])'
scope: keyword.operator.logical
- match: '&(?!&)|\|(?!\|)'
scope: keyword.operator.set
- match: '(?<!\.)(\.)[ \t]*([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)(?![\p{L}\p{Nd}_\$\#])'
captures:
1: punctuation.accessor
2: variable.other.member
- match: '(?<!\.)(\.)(?=[ \t]*(?:#*"|//|$))'
captures:
1: punctuation.accessor
- match: '(?<![\p{L}\p{Nd}_\$\#])_(?!\|)(?![\p{L}\p{Nd}_\$\#])'
scope: constant.language.top
- match: '(?<![\p{L}\p{Nd}_\$\#])_\|_(?![\p{L}\p{Nd}_\$\#])'
scope: constant.language.bottom
- match: '(?<![\p{L}\p{Nd}_\$\#])null(?![\p{L}\p{Nd}_\$\#])'
scope: constant.language.null
- match: '(?<![\p{L}\p{Nd}_\$\#])(?:true|false)(?![\p{L}\p{Nd}_\$\#])'
scope: constant.language.bool
- match: '(?<![\p{L}\p{Nd}_\.])[0-9](?:_?[0-9])*\.(?:[0-9](?:_?[0-9])*)?(?:[eE][\+\-]?[0-9](?:_?[0-9])*)?(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.float.decimal
- match: '(?<![\p{L}\p{Nd}_\.])[0-9](?:_?[0-9])*[eE][\+\-]?[0-9](?:_?[0-9])*(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.float.decimal
- match: '(?<![\p{L}\p{Nd}_\.])\.[0-9](?:_?[0-9])*(?:[eE][\+\-]?[0-9](?:_?[0-9])*)?(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.float.decimal
- match: '(?<![\p{L}\p{Nd}_\.])(?:0|[1-9](?:_?[0-9])*)(?:\.[0-9](?:_?[0-9])*)?(?:[KMGTP]i?)(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.integer.other
- match: '(?<![\p{L}\p{Nd}_\.])\.[0-9](?:_?[0-9])*(?:[KMGTP]i?)(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.integer.other
- match: '(?<![\p{L}\p{Nd}_\.])(?:0|[1-9](?:_?[0-9])*)(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.integer.decimal
- match: '(?<![\p{L}\p{Nd}_\.])0b_?[0-1](?:_?[0-1])*(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.integer.binary
- match: '(?<![\p{L}\p{Nd}_\.])0[xX]_?[0-9a-fA-F](?:_?[0-9a-fA-F])*(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.integer.hexadecimal
- match: '(?<![\p{L}\p{Nd}_\.])0o_?[0-7](?:_?[0-7])*(?![\p{L}\p{Nd}_]|\.(?!\.\.))'
scope: constant.numeric.integer.octal
- include: string
- match: '(?<![\p{L}\p{Nd}_\$\#])(?:bool|u?int(?:8|16|32|64|128)?|float(?:32|64)?|string|bytes|number|rune)(?![\p{L}\p{Nd}_\$\#])'
scope: support.type
- match: '(?<![\p{L}\p{Nd}_\$\#])(?:__)?self(?![\p{L}\p{Nd}_\$\#])(?![ \t]*+(?:[?!][ \t]*+)?:|[ \t]*+~|[ \t]*+=(?![=~]))'
scope: variable.language.self
- match: '(?<![\p{L}\p{Nd}_\$\#])(len|close|and|or|div|mod|quo|rem|matchN|matchIf|error|validator)(\()'
captures:
1: support.function
2: punctuation.section.parens.begin
push:
- meta_scope: meta.function-call
- match: \)
captures:
0: punctuation.section.parens.end
pop: true
- include: whitespace
- include: comment
- include: punctuation_comma
- include: attribute
- include: alias_postfix
- include: punctuation_colon
- include: punctuation_option
- include: punctuation_bind
- include: punctuation_ellipsis
- include: expression
- include: invalid_in_parens
- match: '(?<![\p{L}\p{Nd}_\$\#])([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)(\.)(\p{Lu}[\p{L}\p{Nd}_\$\#]*)(\()'
captures:
1: support.module
2: punctuation
3: support.function
4: punctuation.section.parens.begin
push:
- meta_scope: meta.function-call
- match: \)
captures:
0: punctuation.section.parens.end
pop: true
- include: whitespace
- include: comment
- include: punctuation_comma
- include: attribute
- include: alias_postfix
- include: punctuation_colon
- include: punctuation_option
- include: punctuation_bind
- include: punctuation_ellipsis
- include: expression
- include: invalid_in_parens
- match: '(?<![\p{L}\p{Nd}_\$\#])(?:[\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)(?![\p{L}\p{Nd}_\$\#])'
scope: variable.other
- match: '\{'
captures:
0: punctuation.definition.struct.begin
push:
- meta_scope: meta.struct
- match: '\}'
captures:
0: punctuation.definition.struct.end
pop: true
- include: invalid_slash
- include: whitespace
- include: comment
- include: punctuation_comma
- include: punctuation_ellipsis
- include: declaration
- include: invalid_in_braces
- match: '\['
captures:
0: punctuation.section.brackets.begin
push:
- meta_scope: meta.brackets
- match: '\]'
captures:
0: punctuation.section.brackets.end
pop: true
- include: whitespace
- include: comment
- include: punctuation_colon
- include: punctuation_comma
- include: punctuation_ellipsis
- include: punctuation_option
- match: '(?<![\p{L}\p{Nd}_\$\#])([\p{L}\$\#][\p{L}\p{Nd}_\$\#]*|_[\p{L}\p{Nd}_\$\#]+)[ \t]*(=)(?![=~])'
captures:
1: variable.other
2: punctuation.alias
- include: expression
- match: '[^\s\]]'
scope: invalid
- match: \(
captures:
0: punctuation.section.parens.begin
push:
- meta_scope: meta.parens
- match: \)
captures:
0: punctuation.section.parens.end
pop: true
- include: whitespace
- include: comment
- include: punctuation_comma
- include: attribute
- include: alias_postfix
- include: punctuation_colon
- include: punctuation_option
- include: punctuation_bind
- include: punctuation_ellipsis
- include: expression
- include: invalid_in_parens
invalid_in_braces:
- match: '[^\s\}]'
scope: invalid
invalid_in_parens:
- match: '[^\s\)]'
scope: invalid
invalid_slash:
- match: '(?<=^|[\{,:])[ \t]*+(/)(?!/)'
captures:
1: invalid
punctuation_bind:
- match: "(?<![=!><])=(?![=~])"
scope: punctuation.bind
punctuation_colon:
- match: (?<!:):(?!:)
scope: punctuation.colon
punctuation_comma:
- match: ","
scope: punctuation.separator
punctuation_ellipsis:
- match: '(?<!\.)\.{3}(?!\.)'
scope: punctuation.ellipsis
punctuation_option:
- match: \?
scope: punctuation.option
string:
- match: '####"""'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double-multiline
- match: '"""####'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\####(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\####(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\####\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\####$
scope: constant.character.escape
- match: \\####.
scope: invalid.illegal
- match: '####"'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double
- match: '"####'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\####(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\####(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\####\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\####.
scope: invalid.illegal
- match: "####'''"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single-multiline
- match: "'''####"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\####(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\####(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\####\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\####$
scope: constant.character.escape
- match: \\####.
scope: invalid.illegal
- match: "####'"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single
- match: "'####"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\####(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\####(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\####\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\####.
scope: invalid.illegal
- match: '###"""'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double-multiline
- match: '"""###'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\###(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\###(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\###\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\###$
scope: constant.character.escape
- match: \\###.
scope: invalid.illegal
- match: '###"'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double
- match: '"###'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\###(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\###(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\###\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\###.
scope: invalid.illegal
- match: "###'''"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single-multiline
- match: "'''###"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\###(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\###(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\###\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\###$
scope: constant.character.escape
- match: \\###.
scope: invalid.illegal
- match: "###'"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single
- match: "'###"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\###(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\###(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\###\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\###.
scope: invalid.illegal
- match: '##"""'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double-multiline
- match: '"""##'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\##(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\##(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\##\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\##$
scope: constant.character.escape
- match: \\##.
scope: invalid.illegal
- match: '##"'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double
- match: '"##'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\##(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\##(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\##\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\##.
scope: invalid.illegal
- match: "##'''"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single-multiline
- match: "'''##"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\##(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\##(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\##\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\##$
scope: constant.character.escape
- match: \\##.
scope: invalid.illegal
- match: "##'"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single
- match: "'##"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\##(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\##(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\##\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\##.
scope: invalid.illegal
- match: '#"""'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double-multiline
- match: '"""#'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\#(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\#(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\#\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\#$
scope: constant.character.escape
- match: \\#.
scope: invalid.illegal
- match: '#"'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double
- match: '"#'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\#(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\#(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\#\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\#.
scope: invalid.illegal
- match: "#'''"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single-multiline
- match: "'''#"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\#(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\#(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\#\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\#$
scope: constant.character.escape
- match: \\#.
scope: invalid.illegal
- match: "#'"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single
- match: "'#"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\#(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\#(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\#\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\#.
scope: invalid.illegal
- match: '"""'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double-multiline
- match: '"""'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\$
scope: constant.character.escape
- match: \\.
scope: invalid.illegal
- match: '"'
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.double
- match: '"'
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\(?:"|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: invalid.illegal
- match: \\\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\.
scope: invalid.illegal
- match: "'''"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single-multiline
- match: "'''"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\$
scope: constant.character.escape
- match: \\.
scope: invalid.illegal
- match: "'"
captures:
0: punctuation.definition.string.begin
push:
- meta_scope: meta.string
- meta_content_scope: string.quoted.single
- match: "'"
captures:
0: punctuation.definition.string.end
pop: true
- match: '\\(?:''|/|\\|[abfnrtv]|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})'
scope: constant.character.escape
- match: '\\(?:[0-7]{3}|x[0-9A-Fa-f]{2})'
scope: constant.character.escape
- match: \\\(
captures:
0: punctuation.section.interpolation.begin
push:
- meta_scope: meta.interpolation
- meta_content_scope: source.cue.embedded
- match: \)
captures:
0: punctuation.section.interpolation.end
pop: true
- include: whitespace
- include: punctuation_option
- include: expression
- include: invalid_in_parens
- match: \\.
scope: invalid.illegal
whitespace:
- match: '[ \t\r\n]+'
+2 -6
View File
@@ -6,7 +6,7 @@ Should you find that a particular syntax is not available within `bat` and think
code. As a basis, syntect uses [Sublime Text](https://www.sublimetext.com/) syntax definitions
in the `.sublime-syntax` format.
**Important:** Before proceeding, verify that the syntax you wish to add meets the [criteria for inclusion](#criteria-for-inclusion-of-new-syntaxes).
**Important:** Before proceeding, verify that the syntax you wish to add meets the [criteria for inclusion](#Criteria-for-inclusion-of-new-syntaxes).
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`) using
@@ -28,7 +28,7 @@ in the `.sublime-syntax` format.
5. Use `bat --list-languages` to check if the new languages are available. You may want to do something like ``export PATH="`pwd`/target/debug:$PATH"`` to ensure the locally compiled version is the one being used.
6. Add a syntax test for the new language. See [below](#syntax-tests) for details.
6. Add a syntax test for the new language. See [below](#Syntax-tests) for details.
7. If you send a pull request with your changes, please do *not* include the changed `syntaxes.bin`
file. A new binary cache file will be created once before every new release of `bat`. This
@@ -103,7 +103,3 @@ The following files have been manually modified after converting from a `.tmLang
* `Cabal.sublime_syntax` has been added manually from
https://github.com/SublimeHaskell/SublimeHaskell/ - we don't want to include the whole submodule because it includes other syntaxes ("Haskell improved") as well.
* `Lean.sublime-syntax` has been added manually from https://github.com/leanprover/vscode-lean4/blob/master/vscode-lean4/syntaxes/lean4.json via conversion.
* `CUE.sublime-syntax` has been added manually from https://github.com/cue-lang/vscode-cue/blob/master/extension/syntaxes/cue.tmLanguage.json via conversion.
Sublime Text's converter rejects rules that only group other patterns (see https://github.com/cue-lang/cue/issues/3427), so they are inlined before converting:
`jq 'walk(if type == "object" and has("patterns") then .patterns |= [.[] | if type == "object" and keys == ["patterns"] then .patterns[] else . end] else . end)'`.
The converted syntax reproduces the token goldens in `extension/testdata/syntax` of that repository.
+15 -25
View File
@@ -1,5 +1,4 @@
use std::convert::TryInto;
use std::io::{self, Write};
use std::path::Path;
use syntect::highlighting::ThemeSet;
@@ -25,7 +24,7 @@ pub fn build(
let acknowledgements = build_acknowledgements(source_dir, include_acknowledgements)?;
print_unlinked_contexts(&syntax_set)?;
print_unlinked_contexts(&syntax_set);
write_assets(
&theme_set,
@@ -47,18 +46,16 @@ fn build_theme_set(source_dir: &Path, include_integrated_assets: bool) -> Result
if theme_dir.exists() {
let res = theme_set.add_from_folder(&theme_dir);
if let Err(err) = res {
writeln!(
io::stdout(),
println!(
"Failed to load one or more themes from '{}' (reason: '{err}')",
theme_dir.to_string_lossy(),
)?;
);
}
} else {
writeln!(
io::stdout(),
println!(
"No themes were found in '{}', using the default set",
theme_dir.to_string_lossy()
)?;
);
}
theme_set.try_into()
@@ -81,25 +78,23 @@ fn build_syntax_set_builder(
if syntax_dir.exists() {
syntax_set_builder.add_from_folder(syntax_dir, true)?;
} else {
writeln!(
io::stdout(),
println!(
"No syntaxes were found in '{}', using the default set.",
syntax_dir.to_string_lossy()
)?;
);
}
Ok(syntax_set_builder)
}
fn print_unlinked_contexts(syntax_set: &SyntaxSet) -> Result<()> {
fn print_unlinked_contexts(syntax_set: &SyntaxSet) {
let missing_contexts = syntax_set.find_unlinked_contexts();
if !missing_contexts.is_empty() {
writeln!(io::stdout(), "Some referenced contexts could not be found!")?;
println!("Some referenced contexts could not be found!");
for context in missing_contexts {
writeln!(io::stdout(), "- {context}")?;
println!("- {context}");
}
}
Ok(())
}
fn write_assets(
@@ -132,13 +127,12 @@ fn write_assets(
)?;
}
write!(
io::stdout(),
print!(
"Writing metadata to folder {} ... ",
target_dir.to_string_lossy()
)?;
);
crate::assets_metadata::AssetsMetadata::new(current_version).save_to_folder(target_dir)?;
writeln!(io::stdout(), "okay")?;
println!("okay");
Ok(())
}
@@ -167,14 +161,10 @@ fn asset_to_cache<T: serde::Serialize>(
description: &str,
compressed: bool,
) -> Result<()> {
write!(
io::stdout(),
"Writing {description} to {} ... ",
path.to_string_lossy()
)?;
print!("Writing {description} to {} ... ", path.to_string_lossy());
let contents = asset_to_contents(asset, description, compressed)?;
std::fs::write(path, &contents[..])
.map_err(|_| format!("Could not save {description} to {}", path.to_string_lossy()))?;
writeln!(io::stdout(), "okay")?;
println!("okay");
Ok(())
}
+22 -23
View File
@@ -65,20 +65,12 @@ pub struct App {
number_nonblank_from_cli: bool,
}
/// True if stdout is attached to an interactive terminal. On Windows this also
/// recognizes mintty/MSYS2 and Cygwin pseudo-terminals, which expose stdout as
/// a named pipe that `std::io::IsTerminal` does not report as a terminal.
/// Regular pipes (e.g. `bat file | grep`) are not affected.
fn is_interactive_output() -> bool {
std::io::stdout().is_terminal() || Term::stdout().features().is_msys_tty()
}
impl App {
pub fn new() -> Result<Self> {
#[cfg(windows)]
let _ = nu_ansi_term::enable_ansi_support();
let interactive_output = is_interactive_output();
let interactive_output = std::io::stdout().is_terminal();
// Parse the original command line separately from config and environment arguments.
// Using clap here keeps CLI-only detection consistent with the final parser, including
@@ -87,7 +79,7 @@ impl App {
let number_from_cli = cli_matches.get_flag("number");
let number_nonblank_from_cli = cli_matches.get_flag("number-nonblank");
let matches = Self::matches(interactive_output, &cli_matches)?;
let matches = Self::matches(interactive_output)?;
if matches.get_flag("help") {
let help_type = if wild::args_os().any(|arg| arg == "--help") {
@@ -177,8 +169,7 @@ impl App {
};
let cache_dir = PROJECT_DIRS.cache_dir();
let config_dir = PROJECT_DIRS.config_dir();
let assets = assets_from_cache_or_binary(use_custom_assets, config_dir, cache_dir)?;
let assets = assets_from_cache_or_binary(use_custom_assets, cache_dir)?;
Controller::new(&help_config, &assets)
.run(inputs, None)
.ok();
@@ -204,13 +195,20 @@ impl App {
clap_app::build_app(interactive_output).get_matches_from(wild::args_os())
}
fn matches(interactive_output: bool, cli_matches: &ArgMatches) -> Result<ArgMatches> {
let should_skip_config =
cli_matches.get_flag("no-config") || cli_matches.get_flag("diagnostic");
fn matches(interactive_output: bool) -> Result<ArgMatches> {
// Check if we should skip config file processing for special arguments
// that don't require full application setup (version, diagnostic)
let should_skip_config = wild::args_os().any(|arg| {
matches!(
arg.to_str(),
Some("-V" | "--version" | "--diagnostic" | "--diagnostics")
)
});
// Check if help was requested - help should read the config file but be
// forgiving of invalid arguments (so configured theme etc. can be used)
let help_requested = cli_matches.get_flag("help");
let help_requested =
wild::args_os().any(|arg| matches!(arg.to_str(), Some("-h" | "--help")));
if wild::args_os().nth(1) == Some("cache".into()) {
// Skip the config file and env vars
@@ -218,9 +216,9 @@ impl App {
return Ok(clap_app::build_app(interactive_output).get_matches_from(args));
}
if should_skip_config {
if wild::args_os().any(|arg| arg == "--no-config") || should_skip_config {
// Skip the arguments in bats config file when --no-config is present
// or when user requests diagnostic information
// or when user requests version or diagnostic information
let args = Self::build_args_without_config();
return Ok(clap_app::build_app(interactive_output).get_matches_from(args));
}
@@ -336,12 +334,13 @@ impl App {
// later args take precedence over earlier ones, hence `.rev()`
// see: https://github.com/sharkdp/bat/pull/2755#discussion_r1456416875
for from_to in values.rev() {
// The glob can contain colons itself, e.g. after a drive letter on Windows.
let Some((from, to)) = from_to.rsplit_once(':') else {
return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is '<glob-pattern>:<syntax-name>'. For example: '*.cpp:C++'.".into());
};
let parts: Vec<_> = from_to.split(':').collect();
syntax_mapping.insert(from, MappingTarget::MapTo(to))?;
if parts.len() != 2 {
return Err("Invalid syntax mapping. The format of the -m/--map-syntax option is '<glob-pattern>:<syntax-name>'. For example: '*.cpp:C++'.".into());
}
syntax_mapping.insert(parts[0], MappingTarget::MapTo(parts[1]))?;
}
}
+10 -61
View File
@@ -1,5 +1,5 @@
use std::fs;
use std::io::{self, Write};
use std::io;
use std::path::Path;
use std::path::PathBuf;
@@ -9,22 +9,16 @@ use bat::assets::HighlightingAssets;
use bat::assets_metadata::AssetsMetadata;
use bat::error::*;
pub fn clear_assets(cache_dir: &Path) -> Result<()> {
clear_asset(cache_dir.join("themes.bin"), "theme set cache")?;
clear_asset(cache_dir.join("syntaxes.bin"), "syntax set cache")?;
clear_asset(cache_dir.join("metadata.yaml"), "metadata file")?;
Ok(())
pub fn clear_assets(cache_dir: &Path) {
clear_asset(cache_dir.join("themes.bin"), "theme set cache");
clear_asset(cache_dir.join("syntaxes.bin"), "syntax set cache");
clear_asset(cache_dir.join("metadata.yaml"), "metadata file");
}
pub fn assets_from_cache_or_binary(
use_custom_assets: bool,
config_dir: &Path,
cache_dir: &Path,
) -> Result<HighlightingAssets> {
if use_custom_assets {
ensure_cache_built(config_dir, cache_dir);
}
if let Some(metadata) = AssetsMetadata::load_from_folder(cache_dir)? {
if !metadata.is_compatible_with(crate_version!()) {
return Err(format!(
@@ -49,60 +43,15 @@ pub fn assets_from_cache_or_binary(
Ok(custom_assets.unwrap_or_else(HighlightingAssets::from_binary))
}
fn clear_asset(path: PathBuf, description: &str) -> Result<()> {
write!(io::stdout(), "Clearing {description} ... ")?;
fn clear_asset(path: PathBuf, description: &str) {
print!("Clearing {description} ... ");
match fs::remove_file(&path) {
Err(err) if err.kind() == io::ErrorKind::NotFound => {
writeln!(io::stdout(), "skipped (not present)")?;
println!("skipped (not present)");
}
Err(err) => {
writeln!(
io::stdout(),
"could not remove the cache file {path:?}: {err}"
)?;
println!("could not remove the cache file {path:?}: {err}");
}
Ok(_) => writeln!(io::stdout(), "okay")?,
}
Ok(())
}
/// Build the cache on first run when custom themes or syntaxes are present
/// but no cache exists yet (see #4017). A failed build is not fatal: warn
/// and fall back to the integrated assets.
#[cfg(feature = "build-assets")]
fn ensure_cache_built(config_dir: &Path, cache_dir: &Path) {
if cache_exists(cache_dir) || !custom_sources_exist(config_dir) {
return;
}
eprintln!("bat: custom themes or syntaxes found, building cache ...");
if let Err(err) = bat::assets::build(config_dir, true, false, cache_dir, crate_version!()) {
eprintln!("bat: automatic cache build failed ({err}), falling back to integrated assets.");
Ok(_) => println!("okay"),
}
}
#[cfg(not(feature = "build-assets"))]
fn ensure_cache_built(_config_dir: &Path, _cache_dir: &Path) {}
#[cfg(feature = "build-assets")]
fn cache_exists(cache_dir: &Path) -> bool {
cache_dir.join("themes.bin").is_file()
&& cache_dir.join("syntaxes.bin").is_file()
&& cache_dir.join("metadata.yaml").is_file()
}
#[cfg(feature = "build-assets")]
fn custom_sources_exist(config_dir: &Path) -> bool {
dir_has_files(&config_dir.join("themes")) || dir_has_files(&config_dir.join("syntaxes"))
}
#[cfg(feature = "build-assets")]
fn dir_has_files(dir: &Path) -> bool {
fs::read_dir(dir)
.map(|entries| {
entries
.filter_map(|entry| entry.ok())
.any(|entry| entry.path().is_file())
})
.unwrap_or(false)
}
-5
View File
@@ -65,7 +65,6 @@ pub fn build_app(interactive_output: bool) -> Command {
.arg(
Arg::new("nonprintable-notation")
.long("nonprintable-notation")
.overrides_with("nonprintable-notation")
.action(ArgAction::Set)
.default_value("unicode")
.value_parser(["unicode", "caret"])
@@ -82,7 +81,6 @@ pub fn build_app(interactive_output: bool) -> Command {
.arg(
Arg::new("binary")
.long("binary")
.overrides_with("binary")
.action(ArgAction::Set)
.default_value("no-printing")
.value_parser(["no-printing", "as-text"])
@@ -127,7 +125,6 @@ pub fn build_app(interactive_output: bool) -> Command {
.arg(
Arg::new("fallback-syntax")
.long("fallback-syntax")
.overrides_with("fallback-syntax")
.visible_alias("fallback-language")
.help("Set a fallback language for undetected syntaxes.")
.long_help(
@@ -320,7 +317,6 @@ pub fn build_app(interactive_output: bool) -> Command {
.arg(
Arg::new("italic-text")
.long("italic-text")
.overrides_with("italic-text")
.value_name("when")
.value_parser(["always", "never"])
.default_value("never")
@@ -488,7 +484,6 @@ pub fn build_app(interactive_output: bool) -> Command {
.arg(
Arg::new("squeeze-limit")
.long("squeeze-limit")
.overrides_with("squeeze-limit")
.value_parser(|s: &str| s.parse::<usize>().map_err(|_| "Requires a non-negative number".to_owned()))
.long_help("Set the maximum number of consecutive empty lines to be printed.")
.hide_short_help(true)
+7 -15
View File
@@ -32,13 +32,12 @@ pub fn config_file() -> PathBuf {
pub fn generate_config_file() -> bat::error::Result<()> {
let config_file = config_file();
if config_file.is_file() {
writeln!(
io::stdout(),
println!(
"A config file already exists at: {}",
config_file.to_string_lossy()
)?;
);
write!(io::stdout(), "Overwrite? (y/N): ")?;
print!("Overwrite? (y/N): ");
io::stdout().flush()?;
let mut decision = String::new();
io::stdin().read_line(&mut decision)?;
@@ -94,11 +93,10 @@ pub fn generate_config_file() -> bat::error::Result<()> {
)
})?;
writeln!(
io::stdout(),
println!(
"Success! Config file written to {}",
config_file.to_string_lossy()
)?;
);
Ok(())
}
@@ -110,7 +108,7 @@ pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseEr
let user_config = config_file();
if let Ok(c) = fs::read_to_string(&system_config) {
config.push_str(strip_bom(&c));
config.push_str(&c);
config.push('\n');
}
@@ -118,7 +116,7 @@ pub fn get_args_from_config_file() -> Result<Vec<OsString>, shell_words::ParseEr
// which can happen when BAT_CONFIG_DIR is set to e.g. "/etc/bat". See #3589.
if !same_file(&system_config, &user_config) {
if let Ok(c) = fs::read_to_string(&user_config) {
config.push_str(strip_bom(&c));
config.push_str(&c);
}
}
@@ -136,12 +134,6 @@ pub fn get_args_from_env_opts_var() -> Option<Result<Vec<OsString>, shell_words:
env::var("BAT_OPTS").ok().map(|s| get_args_from_str(&s))
}
/// Editors on Windows (e.g. `Set-Content -Encoding UTF8` in Windows PowerShell)
/// may prepend a UTF-8 byte order mark, which would otherwise end up in the first argument.
fn strip_bom(content: &str) -> &str {
content.strip_prefix('\u{feff}').unwrap_or(content)
}
fn get_args_from_str(content: &str) -> Result<Vec<OsString>, shell_words::ParseError> {
let args_per_line = content
.split('\n')
+26 -38
View File
@@ -74,9 +74,9 @@ fn run_cache_subcommand(
#[cfg(feature = "build-assets")]
build_assets(matches, config_dir, cache_dir)?;
#[cfg(not(feature = "build-assets"))]
writeln!(io::stdout(), "bat has been built without the 'build-assets' feature. The 'cache --build' option is not available.")?;
println!("bat has been built without the 'build-assets' feature. The 'cache --build' option is not available.");
} else if matches.get_flag("clear") {
clear_assets(cache_dir)?;
clear_assets(cache_dir);
}
Ok(())
@@ -97,10 +97,10 @@ where
map
}
pub fn get_languages(config: &Config, config_dir: &Path, cache_dir: &Path) -> Result<String> {
pub fn get_languages(config: &Config, cache_dir: &Path) -> Result<String> {
let mut result: String = String::new();
let assets = assets_from_cache_or_binary(config.use_custom_assets, config_dir, cache_dir)?;
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
let mut languages = assets
.get_syntaxes()?
.iter()
@@ -205,7 +205,7 @@ pub fn list_themes(
cache_dir: &Path,
theme_options: ThemeOptions,
) -> Result<()> {
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, config_dir, cache_dir)?;
let assets = assets_from_cache_or_binary(cfg.use_custom_assets, cache_dir)?;
let mut config = cfg.clone();
let mut style = HashSet::new();
style.insert(StyleComponent::Plain);
@@ -262,17 +262,13 @@ pub fn list_themes(
Ok(())
}
fn set_terminal_title_to(new_terminal_title: String) -> Result<()> {
fn set_terminal_title_to(new_terminal_title: String) {
let osc_command_for_setting_terminal_title = "\x1b]0;";
let osc_end_command = "\x07";
// Prevent BEL/ESC/C1 bytes in the title from terminating or nesting the OSC.
let safe_title = bat::sanitize_for_terminal(&new_terminal_title);
write!(
io::stdout(),
"{osc_command_for_setting_terminal_title}{safe_title}{osc_end_command}"
)?;
io::stdout().flush()?;
Ok(())
print!("{osc_command_for_setting_terminal_title}{safe_title}{osc_end_command}");
io::stdout().flush().unwrap();
}
fn get_new_terminal_title(inputs: &Vec<Input>) -> String {
@@ -286,22 +282,17 @@ fn get_new_terminal_title(inputs: &Vec<Input>) -> String {
new_terminal_title
}
fn run_controller(
inputs: Vec<Input>,
config: &Config,
config_dir: &Path,
cache_dir: &Path,
) -> Result<bool> {
let assets = assets_from_cache_or_binary(config.use_custom_assets, config_dir, cache_dir)?;
fn run_controller(inputs: Vec<Input>, config: &Config, cache_dir: &Path) -> Result<bool> {
let assets = assets_from_cache_or_binary(config.use_custom_assets, cache_dir)?;
let controller = Controller::new(config, &assets);
if config.paging_mode != PagingMode::Never && config.set_terminal_title {
set_terminal_title_to(get_new_terminal_title(&inputs))?;
set_terminal_title_to(get_new_terminal_title(&inputs));
}
controller.run(inputs, None)
}
#[cfg(feature = "bugreport")]
fn invoke_bugreport(app: &App, cache_dir: &Path) -> Result<()> {
fn invoke_bugreport(app: &App, cache_dir: &Path) {
use bugreport::{bugreport, collector::*, format::Markdown, report::ReportEntry};
struct ColorSchemeCollector;
@@ -338,7 +329,6 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) -> Result<()> {
.info(CommandLine::default())
.info(EnvironmentVariables::list(&[
"BAT_CACHE_PATH",
"BAT_CONFIG_DIR",
"BAT_CONFIG_PATH",
"BAT_OPTS",
"BAT_PAGER",
@@ -380,8 +370,7 @@ fn invoke_bugreport(app: &App, cache_dir: &Path) -> Result<()> {
))
};
writeln!(io::stdout(), "{}", report.format::<Markdown>())?;
Ok(())
report.print::<Markdown>();
}
/// Returns `Err(..)` upon fatal errors. Otherwise, returns `Ok(true)` on full success and
@@ -393,22 +382,21 @@ fn run() -> Result<bool> {
if app.matches.get_flag("diagnostic") {
#[cfg(feature = "bugreport")]
invoke_bugreport(&app, cache_dir)?;
invoke_bugreport(&app, cache_dir);
#[cfg(not(feature = "bugreport"))]
writeln!(io::stdout(), "bat has been built without the 'bugreport' feature. The '--diagnostic' option is not available.")?;
println!("bat has been built without the 'bugreport' feature. The '--diagnostic' option is not available.");
return Ok(true);
}
#[cfg(feature = "application")]
if let Some(shell) = app.matches.get_one::<String>("completion") {
let completion = match shell.as_str() {
"bash" => completions::BASH_COMPLETION,
"fish" => completions::FISH_COMPLETION,
"ps1" => completions::PS1_COMPLETION,
"zsh" => completions::ZSH_COMPLETION,
match shell.as_str() {
"bash" => println!("{}", completions::BASH_COMPLETION),
"fish" => println!("{}", completions::FISH_COMPLETION),
"ps1" => println!("{}", completions::PS1_COMPLETION),
"zsh" => println!("{}", completions::ZSH_COMPLETION),
_ => unreachable!("No completion for shell '{shell}' available."),
};
writeln!(io::stdout(), "{completion}")?;
}
return Ok(true);
}
@@ -429,7 +417,7 @@ fn run() -> Result<bool> {
let inputs = vec![Input::ordinary_file("cache")];
let config = app.config(&inputs)?;
run_controller(inputs, &config, config_dir, cache_dir)
run_controller(inputs, &config, cache_dir)
}
}
_ => {
@@ -437,17 +425,17 @@ fn run() -> Result<bool> {
let config = app.config(&inputs)?;
if app.matches.get_flag("list-languages") {
let languages: String = get_languages(&config, config_dir, cache_dir)?;
let languages: String = get_languages(&config, cache_dir)?;
let inputs: Vec<Input> = vec![Input::from_reader(Box::new(languages.as_bytes()))];
let mut plain_config = config.clone();
plain_config.style_components =
StyleComponents::new(StyleComponent::Plain.components(false));
run_controller(inputs, &plain_config, config_dir, cache_dir)
run_controller(inputs, &plain_config, cache_dir)
} else if app.matches.get_flag("list-themes") {
list_themes(&config, config_dir, cache_dir, app.theme_options())?;
Ok(true)
} else if app.matches.get_flag("config-file") {
writeln!(io::stdout(), "{}", config_file().to_string_lossy())?;
println!("{}", config_file().to_string_lossy());
Ok(true)
} else if app.matches.get_flag("generate-config-file") {
generate_config_file()?;
@@ -462,7 +450,7 @@ fn run() -> Result<bool> {
writeln!(io::stdout(), "{}", bat::assets::get_acknowledgements())?;
Ok(true)
} else {
run_controller(inputs, &config, config_dir, cache_dir)
run_controller(inputs, &config, cache_dir)
}
}
}
+4 -4
View File
@@ -98,20 +98,20 @@ pub struct Config<'a> {
/// cached assets) are not available, assets from the binary will be used instead.
pub use_custom_assets: bool,
/// Whether or not to use $LESSOPEN if set
// Whether or not to use $LESSOPEN if set
#[cfg(feature = "lessopen")]
pub use_lessopen: bool,
/// Whether or not to set terminal title when using a pager
// Whether or not to set terminal title when using a pager
pub set_terminal_title: bool,
/// The maximum number of consecutive empty lines to display
pub squeeze_lines: Option<usize>,
/// Whether or not to strip ANSI escape codes from the input
// Whether or not to strip ANSI escape codes from the input
pub strip_ansi: StripAnsiMode,
/// Substitute terminal-active and spoofing-relevant bytes; implies strip_ansi.
// Substitute terminal-active and spoofing-relevant bytes; implies strip_ansi.
pub sanitize: StripAnsiMode,
/// Whether or not to produce no output when input is empty
+3 -17
View File
@@ -18,8 +18,6 @@ use std::io::{self, BufRead, Write};
use std::mem;
use clircle::{Clircle, Identifier};
use content_inspector::ContentType;
use encoding_rs::{UTF_16BE, UTF_16LE};
pub struct Controller<'a> {
config: &'a Config<'a>,
@@ -282,21 +280,9 @@ impl Controller<'_> {
// line counter for empty lines (content-free lines that contain
// only the line terminator).
if self.config.number_nonblank
&& match reader.content_type {
Some(ContentType::UTF_16LE) => UTF_16LE
.decode_with_bom_removal(&current_line_buffer)
.0
.trim_end_matches(['\r', '\n'])
.is_empty(),
Some(ContentType::UTF_16BE) => UTF_16BE
.decode_with_bom_removal(&current_line_buffer)
.0
.trim_end_matches(['\r', '\n'])
.is_empty(),
_ => current_line_buffer
.iter()
.all(|&b| b == b'\r' || b == b'\n'),
}
&& current_line_buffer
.iter()
.all(|&b| b == b'\r' || b == b'\n')
{
buffered_lines
.push_back((mem::take(&mut current_line_buffer), current_line_number));
+2 -13
View File
@@ -53,17 +53,6 @@ impl From<String> for Error {
pub type Result<T> = std::result::Result<T, Error>;
/// Paint a `[bat error]`/`[bat warning]` style prefix, unless the user asked
/// for no colors by setting the `NO_COLOR` environment variable.
#[doc(hidden)]
pub fn paint_prefix(color: nu_ansi_term::Color, prefix: &str) -> String {
if std::env::var_os("NO_COLOR").is_some_and(|x| !x.is_empty()) {
prefix.to_owned()
} else {
color.paint(prefix).to_string()
}
}
pub fn default_error_handler(error: &Error, output: &mut dyn Write) {
use nu_ansi_term::Color::Red;
@@ -75,7 +64,7 @@ pub fn default_error_handler(error: &Error, output: &mut dyn Write) {
writeln!(
output,
"{}: Error while parsing metadata.yaml file: {error}",
paint_prefix(Red, "[bat error]"),
Red.paint("[bat error]"),
)
.ok();
}
@@ -83,7 +72,7 @@ pub fn default_error_handler(error: &Error, output: &mut dyn Write) {
writeln!(
&mut std::io::stderr().lock(),
"{}: {error}",
paint_prefix(Red, "[bat error]"),
Red.paint("[bat error]"),
)
.ok();
}
+1 -40
View File
@@ -291,12 +291,7 @@ impl<'a> InputReader<'a> {
if content_type == Some(ContentType::UTF_16LE) {
read_utf16_line(&mut reader, &mut first_line, 0x00, 0x0A)?;
} else if content_type == Some(ContentType::UTF_16BE)
// In UTF-16BE a newline is encoded as 0x00 0x0A, so `read_until(b'\n')` above
// may already have read a complete first line. Only keep reading if it did not
// (e.g. the 0x0A byte was part of another character such as U+4E0A).
&& !(first_line.len() % 2 == 0 && first_line.ends_with(&[0x00, 0x0A]))
{
} else if content_type == Some(ContentType::UTF_16BE) {
read_utf16_line(&mut reader, &mut first_line, 0x0A, 0x00)?;
}
@@ -380,7 +375,6 @@ fn read_utf16_line<R: BufRead>(
}
buf.extend_from_slice(&temp);
if buf.len() >= 2
&& buf.len().is_multiple_of(2)
&& buf[buf.len() - 2] == preceded_by_char
&& buf[buf.len() - 1] == read_until_char
{
@@ -617,39 +611,6 @@ fn unbuffered_eof_handling() {
assert!(buffer.is_empty());
}
#[test]
fn utf16_newlines_are_code_unit_aligned() {
let cases: &[&[&[u8]]] = &[
&[b"\xFF\xFE\x85\x0A\x00\x25\x0A\x00", b"\x7A\x00"],
&[
b"\xFF\xFE\x73\x00\x0A\x00",
b"\x85\x0A\x00\x25\x0A\x00",
b"\x7A\x00",
],
&[
b"\xFE\xFF\x4E\x0A\x00\x0A",
b"\x25\x00\x0A\x85\x00\x0A",
b"\x00\x7A",
],
];
for &lines in cases {
let content = lines.concat();
let mut reader = InputReader::new(&content[..]);
assert_eq!(lines[0], &reader.first_line[..]);
for &line in lines {
let mut buffer = vec![];
assert!(reader.read_line(&mut buffer).unwrap());
assert_eq!(line, &buffer[..]);
}
let mut buffer = vec![];
assert!(!reader.read_line(&mut buffer).unwrap());
assert!(buffer.is_empty());
}
}
#[test]
fn utf16le_issue3367() {
let content = b"\xFF\xFE\x0A\x4E\x00\x4E\x0A\x4F\x00\x52\x0A\x00\
+3 -3
View File
@@ -355,13 +355,13 @@ fn test_parse_context_fail() {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum RangeCheckResult {
/// Within one of the given ranges
// Within one of the given ranges
InRange,
/// Before the first range or within two ranges
// Before the first range or within two ranges
BeforeOrBetweenRanges,
/// Line number is outside of all ranges and larger than the last range.
// Line number is outside of all ranges and larger than the last range.
AfterLastRange,
}
+2 -5
View File
@@ -1,10 +1,7 @@
#[macro_export]
macro_rules! bat_warning {
($($arg:tt)*) => ({
eprintln!(
"{}: {}",
$crate::error::paint_prefix(nu_ansi_term::Color::Yellow, "[bat warning]"),
format!($($arg)*)
);
use nu_ansi_term::Color::Yellow;
eprintln!("{}: {}", Yellow.paint("[bat warning]"), format!($($arg)*));
})
}
+1 -81
View File
@@ -108,7 +108,7 @@ pub(crate) fn get_pager(config_pager: Option<&str>) -> Result<Option<Pager>, Par
_ => ("less", PagerSource::Default),
};
let parts = split_command(cmd)?;
let parts = shell_words::split(cmd)?;
match parts.split_first() {
Some((bin, args)) => {
let kind = PagerKind::from_bin(bin);
@@ -136,83 +136,3 @@ pub(crate) fn get_pager(config_pager: Option<&str>) -> Result<Option<Pager>, Par
None => Ok(None),
}
}
/// Splits a pager command into the program and its arguments.
fn split_command(cmd: &str) -> Result<Vec<String>, ParseError> {
if cfg!(windows) {
split_command_windows(cmd)
} else {
shell_words::split(cmd)
}
}
/// Like `shell_words::split`, but `\` is not an escape character, since it is
/// the path separator on Windows: `C:\tools\less.exe` must not become
/// `C:toolsless.exe`. Single and double quotes still group words.
fn split_command_windows(cmd: &str) -> Result<Vec<String>, ParseError> {
let mut words = Vec::new();
let mut word = String::new();
let mut in_word = false;
let mut quote = None;
for c in cmd.chars() {
match quote {
Some(q) if c == q => quote = None,
Some(_) => word.push(c),
None if c == '"' || c == '\'' => {
quote = Some(c);
in_word = true;
}
None if c.is_whitespace() => {
if in_word {
words.push(std::mem::take(&mut word));
in_word = false;
}
}
None => {
word.push(c);
in_word = true;
}
}
}
if quote.is_some() {
return Err(ParseError);
}
if in_word {
words.push(word);
}
Ok(words)
}
#[cfg(test)]
mod tests {
use super::split_command_windows;
#[test]
fn split_command_windows_keeps_backslashes() {
let cases: &[(&str, &[&str])] = &[
(r"C:\tools\less.exe", &[r"C:\tools\less.exe"]),
(r"C:\tools\less.exe -R", &[r"C:\tools\less.exe", "-R"]),
(
r#""C:\Program Files\Less\less.exe" -R"#,
&[r"C:\Program Files\Less\less.exe", "-R"],
),
(
r"'C:\Program Files\Less\less.exe'",
&[r"C:\Program Files\Less\less.exe"],
),
(r#"less --prompt="a b" """#, &["less", "--prompt=a b", ""]),
(" less -R ", &["less", "-R"]),
("", &[]),
];
for (cmd, expected) in cases {
assert_eq!(split_command_windows(cmd).unwrap(), *expected, "{cmd}");
}
}
#[test]
fn split_command_windows_rejects_unclosed_quote() {
assert!(split_command_windows(r#""C:\tools\less.exe"#).is_err());
}
}
-10
View File
@@ -302,21 +302,11 @@ pub fn strip_overstrike(line: &str, first_backspace: usize) -> String {
output
}
/// Specify when to strip ANSI escape sequences from the input.
///
/// Used for both the `--strip-ansi` and the `--sanitize` option, the latter
/// implying the former with the same value.
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub enum StripAnsiMode {
/// Keep escape sequences in the output
#[default]
Never,
/// Strip escape sequences from the output
Always,
/// Strip escape sequences unless the syntax highlighting language is plain
/// text, in which case they may be part of what the input wants to display
Auto,
}
+15 -25
View File
@@ -16,7 +16,7 @@ use content_inspector::ContentType;
use encoding_rs::{UTF_16BE, UTF_16LE};
use unicode_segmentation::UnicodeSegmentation;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use unicode_width::UnicodeWidthChar;
use crate::assets::{HighlightingAssets, SyntaxReferenceInSet};
use crate::config::Config;
@@ -50,14 +50,6 @@ fn char_width(c: char) -> usize {
c.width().unwrap_or(if c.is_control() { 2 } else { 0 })
}
fn grapheme_width(grapheme: &str) -> usize {
if grapheme.chars().any(char::is_control) {
grapheme.chars().map(char_width).sum()
} else {
grapheme.width()
}
}
const ANSI_UNDERLINE_ENABLE: EscapeSequence = EscapeSequence::CSI {
raw_sequence: "\x1B[4m",
parameters: "4",
@@ -728,15 +720,13 @@ impl Printer for InteractivePrinter<'_> {
let mut cursor_total: usize = 0;
let mut panel_wrap: Option<String> = None;
// Line highlighting. It is done purely with escape sequences (background
// color or underline), so it must be skipped when colors are disabled.
let highlight_this_line = self.config.colored_output
&& self
.config
.highlighted_lines
.0
.check(line_number, max_buffered_line_number)
== RangeCheckResult::InRange;
// Line highlighting
let highlight_this_line = self
.config
.highlighted_lines
.0
.check(line_number, max_buffered_line_number)
== RangeCheckResult::InRange;
if highlight_this_line && self.config.theme == "ansi" {
self.ansi_style.update(ANSI_UNDERLINE_ENABLE);
@@ -836,7 +826,7 @@ impl Printer for InteractivePrinter<'_> {
let text = self
.preprocess(text.trim_end_matches(['\r', '\n']), &mut cursor_total);
let mut max_width = cursor_max.saturating_sub(cursor);
let mut max_width = cursor_max - cursor;
// line buffer (avoid calling write! for every character)
let mut line_buf = String::with_capacity(max_width * 4);
@@ -849,13 +839,13 @@ impl Printer for InteractivePrinter<'_> {
// For word wrapping, track last whitespace position.
let mut last_ws_idx: Option<usize> = None;
for grapheme in text.graphemes(true) {
for c in text.chars() {
// calculate the displayed width for next character
let cw = grapheme_width(grapheme);
let cw = char_width(c);
current_width += cw;
// Track whitespace positions for word wrapping.
if word_wrap && grapheme.starts_with(char::is_whitespace) {
if word_wrap && c.is_whitespace() {
last_ws_idx = Some(line_buf.len());
}
@@ -927,7 +917,7 @@ impl Printer for InteractivePrinter<'_> {
// Word wrap: carry remainder to next line.
let remainder = line_buf[rs..].to_string();
let rem_width: usize =
remainder.graphemes(true).map(grapheme_width).sum();
remainder.chars().map(char_width).sum();
line_buf.clear();
line_buf.push_str(&remainder);
current_width = rem_width + cw;
@@ -938,7 +928,7 @@ impl Printer for InteractivePrinter<'_> {
last_ws_idx = None;
}
line_buf.push_str(grapheme);
line_buf.push(c);
}
// flush the buffer
@@ -975,7 +965,7 @@ impl Printer for InteractivePrinter<'_> {
write!(
handle,
"{}",
ansi_style.paint(" ".repeat(cursor_max.saturating_sub(cursor)))
ansi_style.paint(" ".repeat(cursor_max - cursor))
)?;
}
writeln!(handle)?;
+2 -122
View File
@@ -1,5 +1,5 @@
use std::{
path::{Path, PathBuf},
path::Path,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
@@ -24,19 +24,6 @@ pub(crate) enum Case {
Insensitive,
}
/// On Windows, `PathAbs` and `fs::canonicalize` return verbatim paths (`\\?\C:\dir\file`,
/// `\\?\UNC\server\share\file`), which a pattern such as `C:/dir/*` would not match.
fn without_verbatim_prefix(path: &Path) -> Option<PathBuf> {
if !cfg!(windows) {
return None;
}
let path = path.to_str()?;
match path.strip_prefix(r"\\?\UNC\") {
Some(rest) => Some(format!(r"\\{rest}").into()),
None => path.strip_prefix(r"\\?\").map(PathBuf::from),
}
}
fn make_glob_matcher(from: &str, case: Case) -> Result<GlobMatcher> {
let matcher = GlobBuilder::new(from)
.case_insensitive(matches!(case, Case::Insensitive))
@@ -175,14 +162,9 @@ impl<'a> SyntaxMapping<'a> {
pub fn get_syntax_for(&self, path: impl AsRef<Path>) -> Option<MappingTarget<'a>> {
// Try matching on the file name as-is.
let candidate = Candidate::new(&path);
let simplified_path = without_verbatim_prefix(path.as_ref());
let candidate_simplified = simplified_path.as_ref().map(Candidate::new);
let candidate_filename = path.as_ref().file_name().map(Candidate::new);
for (glob, syntax) in self.all_mappings() {
if glob.is_match_candidate(&candidate)
|| candidate_simplified
.as_ref()
.is_some_and(|simplified| glob.is_match_candidate(simplified))
|| candidate_filename
.as_ref()
.is_some_and(|filename| glob.is_match_candidate(filename))
@@ -194,10 +176,7 @@ impl<'a> SyntaxMapping<'a> {
let file_name = path.as_ref().file_name()?;
self.ignored_suffixes
.try_with_stripped_suffix(file_name, |stripped_file_name| {
if Path::new(stripped_file_name).file_name().is_none() {
return Ok(self.get_syntax_for(stripped_file_name));
}
Ok(self.get_syntax_for(path.as_ref().with_file_name(stripped_file_name)))
Ok(self.get_syntax_for(stripped_file_name))
})
.ok()?
}
@@ -267,105 +246,6 @@ mod tests {
);
}
#[cfg(windows)]
#[test]
fn custom_mappings_match_verbatim_paths() {
let mut map = SyntaxMapping::new();
map.insert("C:/dir/*.myconf", MappingTarget::MapTo("INI"))
.unwrap();
map.insert(r"\\server\share\*.myconf", MappingTarget::MapTo("TOML"))
.unwrap();
map.insert(r"\\?\C:\literal\*.myconf", MappingTarget::MapTo("JSON"))
.unwrap();
for (path, expected) in [
(r"C:\dir\file.myconf", Some(MappingTarget::MapTo("INI"))),
(r"\\?\C:\dir\file.myconf", Some(MappingTarget::MapTo("INI"))),
(r"\\?\D:\dir\file.myconf", None),
(
r"\\?\C:\literal\file.myconf",
Some(MappingTarget::MapTo("JSON")),
),
(
r"\\?\UNC\server\share\file.myconf",
Some(MappingTarget::MapTo("TOML")),
),
] {
assert_eq!(map.get_syntax_for(path), expected, "{path}");
}
}
#[test]
fn custom_mappings_with_ignored_suffix_preserve_paths() {
for (pattern, parent) in [
("/path/to/file.demo", "/path/to"),
("/path/to/*.demo", "/path/to"),
("path/to/*.demo", "path/to"),
] {
let mut map = SyntaxMapping::new();
map.insert(pattern, MappingTarget::MapTo("JSON")).unwrap();
map.insert_ignored_suffix(".custom");
for suffix in ["", ".bak", ".bak.old", ".custom.custom"] {
let path = Path::new(parent).join(format!("file.demo{suffix}"));
assert_eq!(
map.get_syntax_for(&path),
Some(MappingTarget::MapTo("JSON")),
"{pattern}: {}",
path.display()
);
}
assert_eq!(map.get_syntax_for("/path/elsewhere/file.demo.bak"), None);
}
}
#[test]
fn custom_mappings_with_ignored_suffix_preserve_precedence() {
for (first, second) in [("/path/to/*.demo", "*.demo"), ("*.demo", "/path/to/*.demo")] {
let mut map = SyntaxMapping::new();
map.insert(first, MappingTarget::MapTo("first")).unwrap();
map.insert(second, MappingTarget::MapTo("second")).unwrap();
assert_eq!(
map.get_syntax_for("/path/to/file.demo.bak"),
Some(MappingTarget::MapTo("first"))
);
map.insert("*.bak", MappingTarget::MapTo("backup")).unwrap();
assert_eq!(
map.get_syntax_for("/path/to/file.demo.bak"),
Some(MappingTarget::MapTo("backup"))
);
}
}
#[test]
fn custom_mappings_with_empty_stripped_filename() {
let mut map = SyntaxMapping::new();
map.insert("to", MappingTarget::MapTo("parent")).unwrap();
map.insert_ignored_suffix(".custom");
for name in [
"~", ".bak", ".custom", "~.bak", ".~", "..bak", "..custom", "...bak",
] {
assert_eq!(map.get_syntax_for(Path::new("/path/to").join(name)), None);
}
map.insert("", MappingTarget::MapTo("empty")).unwrap();
assert_eq!(
map.get_syntax_for("/path/to/~.bak"),
Some(MappingTarget::MapTo("empty"))
);
for (pattern, name) in [(".", ".~"), (".", "..bak"), ("..", "...bak")] {
let mut map = SyntaxMapping::new();
map.insert(pattern, MappingTarget::MapTo("dot")).unwrap();
assert_eq!(
map.get_syntax_for(Path::new("/path/to").join(name)),
Some(MappingTarget::MapTo("dot"))
);
}
}
#[test]
fn custom_mappings_override_builtin() {
let mut map = SyntaxMapping::new();
@@ -1,4 +0,0 @@
# kubectl's config file has no extension. The format is YAML.
# https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/
[mappings]
"YAML" = ["**/.kube/config"]
@@ -1,6 +1,5 @@
# see `man quadlet`
[mappings]
"INI" = [
"*.quadlets",
"**/containers/systemd/**/*.{container,volume,network,kube,image,build,pod,artifact}",
]
+1 -3
View File
@@ -39,9 +39,7 @@ impl Default for IgnoredSuffixes<'_> {
impl<'a> IgnoredSuffixes<'a> {
pub fn add_suffix(&mut self, suffix: &'a str) {
if !suffix.is_empty() {
self.values.push(suffix)
}
self.values.push(suffix)
}
pub fn strip_suffix(&self, file_name: &'a str) -> Option<&'a str> {
+1 -1
View File
@@ -2,7 +2,7 @@
pub enum WrappingMode {
Character,
Word,
/// The bool specifies whether wrapping has been explicitly disabled by the user via --wrap=never
// The bool specifies whether wrapping has been explicitly disabled by the user via --wrap=never
NoWrapping(bool),
}
File diff suppressed because it is too large. Load diff
-184
View File
@@ -1,184 +0,0 @@
// Package example demonstrates CUE syntax.
package example
import (
 "strings"
 "list"
 yamlpkg "encoding/yaml"
)
// Definitions and constraints
#Port: int & >0 & <=65535
#Service: {
 name!: string & =~"^[a-z][a-z0-9-]*$"
 image: string
 replicas?: uint | *1
 ports: [...#Port]
 env: [string]: string
 labels: {
 app: name
 ...
 }
 _hidden: true
 #nested: bool
}
// Concrete values
services: [Name=string]: #Service & {name: Name}
services: frontend: {
 image: "nginx:1.25"
 replicas: 3
 ports: [80, 443]
 env: {
 LOG_LEVEL: "info"
 TIMEOUT: "30s"
 }
}
services: backend: {
 image: "registry.example.com/backend:\(version)"
 ports: [8080]
 env: DATABASE_URL: "postgres://db:5432/app"
}
version: "v1.2.3"
// Numbers
numbers: {
 decimal: 1_000_000
 float: 3.14159
 exponent: 6.022e23
 hex: 0xFF_FF
 octal: 0o755
 binary: 0b1010_1010
 multiplier: 1.5Gi
 kilo: 4K
}
// Strings and bytes
strs: {
 simple: "hello, world"
 escaped: "tab\tnewline\nunicode\u00e9"
 bytes: 'raw bytes \x00\xff'
 raw: #"no \(interpolation) here"#
 multiline: """
 multiple
 lines with \(version)
 """
 multiBytes: '''
 byte
 lines
 '''
}
// Builtin types and values
types: {
 n: null
 b: bool | *false
 i: int8 | int16 | int32 | int64
 f: float32 | float64
 s: string
 y: bytes
 r: rune
 t: _
 x: number
}
// Bottom (error) value
bottom: _|_ | 1
// Operators and builtins
ops: {
 sum: 1 + 2*3 - 4/2
 divided: div(7, 2)
 modulo: mod(7, 2)
 quotient: quo(-7, 2)
 rest: rem(-7, 2)
 eq: 1 == 1
 ne: "a" != "b"
 both: true && false
 either: true || false
 not: !true
 match: "abc" =~ "^a"
 nomatch: "abc" !~ "^z"
 length: len([1, 2, 3])
 closed: close({a: 1})
 upper: strings.ToUpper("cue")
 sorted: list.Sort([3, 1, 2], list.Ascending)
 disj: or([1, 2, 3])
 conj: and([int, >0])
 asYAML: yamlpkg.Marshal({a: 1})
}
// Nested calls, grouping and interpolated calls
calls: {
 allPorts: list.Concat([
 [22, 25],
 list.Concat([for _, s in services {s.ports}]),
 ])
 nested: len(strings.Split("a,b", ","))
 grouped: ((1 + 2) * 3)
 interp: "x-\(strings.ToUpper("y"))-\(len([1, 2]))"
}
// Comprehensions
let prefix = "svc-"
serviceNames: [for k, v in services if v.replicas > 1 {prefix + k}]
portMap: {
 for name, svc in services {
 for i, p in svc.ports {
 "\(name)-\(i)": p
 }
 }
}
byPort: {
 for name, svc in services
 if len(svc.ports) > 0
 let first = svc.ports[0] {
 "\(first)": name
 (name + "-image"): svc.image
 }
}
if version != "" {
 hasVersion: true
}
// Aliases, optional and required fields, pattern constraints
X=outer: {
 inner: 1
 ref: X.inner
}
optional?: string
required!: int
[=~"^x-"]: string
// Attributes
config: {
 port: int @tag(port, type=int)
 debug: bool @json(",omitempty") @yaml("debug")
}
// Embedding and default values
#Base: {
 kind: *"base" | "derived"
}
derived: #Base & {kind: "derived"}
#Limits: {
 cpu: {min: number, max: number} | *{min: 0, max: 1}
 tags: [...string] | *[]
}
// Lists
emptyList: []
openList: [1, 2, ...]
typedList: [...string]
nestedList: [[1, 2], [3, 4]]
@@ -1,5 +0,0 @@
{a
 /* comment */
}
{a /* comment */}
-184
View File
@@ -1,184 +0,0 @@
// Package example demonstrates CUE syntax.
package example
import (
"strings"
"list"
yamlpkg "encoding/yaml"
)
// Definitions and constraints
#Port: int & >0 & <=65535
#Service: {
name!: string & =~"^[a-z][a-z0-9-]*$"
image: string
replicas?: uint | *1
ports: [...#Port]
env: [string]: string
labels: {
app: name
...
}
_hidden: true
#nested: bool
}
// Concrete values
services: [Name=string]: #Service & {name: Name}
services: frontend: {
image: "nginx:1.25"
replicas: 3
ports: [80, 443]
env: {
LOG_LEVEL: "info"
TIMEOUT: "30s"
}
}
services: backend: {
image: "registry.example.com/backend:\(version)"
ports: [8080]
env: DATABASE_URL: "postgres://db:5432/app"
}
version: "v1.2.3"
// Numbers
numbers: {
decimal: 1_000_000
float: 3.14159
exponent: 6.022e23
hex: 0xFF_FF
octal: 0o755
binary: 0b1010_1010
multiplier: 1.5Gi
kilo: 4K
}
// Strings and bytes
strs: {
simple: "hello, world"
escaped: "tab\tnewline\nunicode\u00e9"
bytes: 'raw bytes \x00\xff'
raw: #"no \(interpolation) here"#
multiline: """
multiple
lines with \(version)
"""
multiBytes: '''
byte
lines
'''
}
// Builtin types and values
types: {
n: null
b: bool | *false
i: int8 | int16 | int32 | int64
f: float32 | float64
s: string
y: bytes
r: rune
t: _
x: number
}
// Bottom (error) value
bottom: _|_ | 1
// Operators and builtins
ops: {
sum: 1 + 2*3 - 4/2
divided: div(7, 2)
modulo: mod(7, 2)
quotient: quo(-7, 2)
rest: rem(-7, 2)
eq: 1 == 1
ne: "a" != "b"
both: true && false
either: true || false
not: !true
match: "abc" =~ "^a"
nomatch: "abc" !~ "^z"
length: len([1, 2, 3])
closed: close({a: 1})
upper: strings.ToUpper("cue")
sorted: list.Sort([3, 1, 2], list.Ascending)
disj: or([1, 2, 3])
conj: and([int, >0])
asYAML: yamlpkg.Marshal({a: 1})
}
// Nested calls, grouping and interpolated calls
calls: {
allPorts: list.Concat([
[22, 25],
list.Concat([for _, s in services {s.ports}]),
])
nested: len(strings.Split("a,b", ","))
grouped: ((1 + 2) * 3)
interp: "x-\(strings.ToUpper("y"))-\(len([1, 2]))"
}
// Comprehensions
let prefix = "svc-"
serviceNames: [for k, v in services if v.replicas > 1 {prefix + k}]
portMap: {
for name, svc in services {
for i, p in svc.ports {
"\(name)-\(i)": p
}
}
}
byPort: {
for name, svc in services
if len(svc.ports) > 0
let first = svc.ports[0] {
"\(first)": name
(name + "-image"): svc.image
}
}
if version != "" {
hasVersion: true
}
// Aliases, optional and required fields, pattern constraints
X=outer: {
inner: 1
ref: X.inner
}
optional?: string
required!: int
[=~"^x-"]: string
// Attributes
config: {
port: int @tag(port, type=int)
debug: bool @json(",omitempty") @yaml("debug")
}
// Embedding and default values
#Base: {
kind: *"base" | "derived"
}
derived: #Base & {kind: "derived"}
#Limits: {
cpu: {min: number, max: number} | *{min: 0, max: 1}
tags: [...string] | *[]
}
// Lists
emptyList: []
openList: [1, 2, ...]
typedList: [...string]
nestedList: [[1, 2], [3, 4]]
@@ -1,5 +0,0 @@
{a
/* comment */
}
{a /* comment */}