diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 167b9e17..7a141412 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -22,6 +22,12 @@ jobs: toolchain: ${{ env.MIN_SUPPORTED_RUST_VERSION }} default: true profile: minimal # minimal component installation (ie, no documentation) + components: clippy + - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --all-targets --all-features - name: Test uses: actions-rs/cargo@v1 with: @@ -152,6 +158,8 @@ jobs: echo ::set-output name=PKG_NAME::${PKG_NAME} # deployable tag? (ie, leading "vM" or "M"; M == version number) unset DEPLOY ; if [[ $REF_TAG =~ ^[vV]?[0-9].* ]]; then DEPLOY='true' ; fi + # unset deploy on ubuntu-18.04 x64 - we will deploy the tarball/deb built on ubuntu-16.04 x64 + if [ "${{ matrix.job.os }}" = "ubuntu-18.04" ] && [ "${{ matrix.job.target }}" = "x86_64-unknown-linux-gnu" ]; then unset DEPLOY; fi echo set-output name=DEPLOY::${DEPLOY:-/false} echo ::set-output name=DEPLOY::${DEPLOY} # DPKG architecture? @@ -274,6 +282,7 @@ jobs: shell: bash run: | ARCHIVE_DIR='${{ steps.vars.outputs.STAGING }}/${{ steps.vars.outputs.PKG_BASENAME }}/' + COPYRIGHT_YEARS="2018 - "$(date "+%Y") # Binary cp 'target/${{ matrix.job.target }}/release/${{ env.PROJECT_NAME }}${{ steps.vars.outputs.EXE_suffix }}' "$ARCHIVE_DIR" @@ -283,8 +292,8 @@ jobs: # Man page cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/manual/bat.1 "$ARCHIVE_DIR" - # README and LICENSE files - cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "$ARCHIVE_DIR" + # README, LICENSE and CHANGELOG files + cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR" # Autocompletion files cp 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.fish "$ARCHIVE_DIR/autocomplete/${{ env.PROJECT_NAME }}.fish" @@ -308,24 +317,27 @@ jobs: # Man page install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/manual/bat.1 "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" - gzip --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" + gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ env.PROJECT_NAME }}.1" # Autocompletion files install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.fish "${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ env.PROJECT_NAME }}.fish" install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ env.PROJECT_NAME }}'-*/out/assets/completions/bat.zsh "${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ env.PROJECT_NAME }}" # README and LICENSE - install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${{ env.PROJECT_NAME }}/README.md" - install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${{ env.PROJECT_NAME }}/LICENSE-MIT" - install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${{ env.PROJECT_NAME }}/LICENSE-APACHE" + install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${{ steps.vars.outputs.DPKG_BASENAME }}/README.md" + install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${{ steps.vars.outputs.DPKG_BASENAME }}/LICENSE-MIT" + install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${{ steps.vars.outputs.DPKG_BASENAME }}/LICENSE-APACHE" + install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${{ steps.vars.outputs.DPKG_BASENAME }}/changelog" + gzip -n --best "${DPKG_DIR}/usr/share/doc/${{ steps.vars.outputs.DPKG_BASENAME }}/changelog" - cat > "${DPKG_DIR}/usr/share/doc/${{ env.PROJECT_NAME }}/copyright" < "${DPKG_DIR}/usr/share/doc/${{ steps.vars.outputs.DPKG_BASENAME }}/copyright" < batcat` symlink or alias to prevent any issues that may come up because of this and to be consistent with other distributions: ``` bash mkdir -p ~/.local/bin @@ -256,6 +256,13 @@ You can install `bat` via xbps-install: xbps-install -S bat ``` +### On Termux + +You can install `bat` via pkg: +```bash +pkg install bat +``` + ### On FreeBSD You can install a precompiled [`bat` package](https://www.freshports.org/textproc/bat) with pkg: @@ -287,6 +294,15 @@ You can install `bat` with zypper: zypper install bat ``` +### Via snap package + +``` +sudo snap install batcat +``` + +[Get it from the Snap Store](https://snapcraft.io/batcat) + + ### On macOS (or Linux) via Homebrew You can install `bat` with [Homebrew on MacOS](https://formulae.brew.sh/formula/bat) or [Homebrew on Linux](https://formulae.brew.sh/formula-linux/bat): @@ -374,12 +390,11 @@ You can also use a custom theme by following the ### 8-bit themes -`bat` has four themes that always use [8-bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors), +`bat` has three themes that always use [8-bit colors](https://en.wikipedia.org/wiki/ANSI_escape_code#Colors), even when truecolor support is available: -- `ansi-dark` looks decent on any terminal with a dark background. It uses 3-bit colors: black, red, - green, yellow, blue, magenta, cyan, and white. -- `ansi-light` is like `ansi-dark`, but for terminals with a light background. +- `ansi` looks decent on any terminal. It uses 3-bit colors: black, red, green, + yellow, blue, magenta, cyan, and white. - `base16` is designed for [base16](https://github.com/chriskempson/base16) terminal themes. It uses 4-bit colors (3-bit colors plus bright variants) in accordance with the [base16 styling guidelines](https://github.com/chriskempson/base16/blob/master/styling.md). diff --git a/assets/syntaxes.bin b/assets/syntaxes.bin index 71c64c84..c1f3051e 100644 Binary files a/assets/syntaxes.bin and b/assets/syntaxes.bin differ diff --git a/assets/syntaxes/02_Extra/AWK b/assets/syntaxes/02_Extra/AWK index e593eb6d..e23926ec 160000 --- a/assets/syntaxes/02_Extra/AWK +++ b/assets/syntaxes/02_Extra/AWK @@ -1 +1 @@ -Subproject commit e593eb6d42a5f1881ba6c75698bd0fb78a9b8871 +Subproject commit e23926eca1b6a0bd0b572b7191aead9f77c748d1 diff --git a/assets/syntaxes/02_Extra/GraphQL b/assets/syntaxes/02_Extra/GraphQL index c9d84587..59304d6c 160000 --- a/assets/syntaxes/02_Extra/GraphQL +++ b/assets/syntaxes/02_Extra/GraphQL @@ -1 +1 @@ -Subproject commit c9d84587eb1a6eb34457a875f21b9b1a29306be3 +Subproject commit 59304d6c7b5019091b532a3197251e393e1db7b2 diff --git a/assets/syntaxes/02_Extra/Svelte b/assets/syntaxes/02_Extra/Svelte index bf92f5b7..aee0676f 160000 --- a/assets/syntaxes/02_Extra/Svelte +++ b/assets/syntaxes/02_Extra/Svelte @@ -1 +1 @@ -Subproject commit bf92f5b7b69c8ea641d6822fd6d12cc2d9341956 +Subproject commit aee0676f379c0503f9c557e24aa2816575e0a4d1 diff --git a/assets/syntaxes/02_Extra/VimL b/assets/syntaxes/02_Extra/VimL index ed40c3bc..23afc890 160000 --- a/assets/syntaxes/02_Extra/VimL +++ b/assets/syntaxes/02_Extra/VimL @@ -1 +1 @@ -Subproject commit ed40c3bc814cd0c8107dbbb8fcd95946b33217f0 +Subproject commit 23afc890977bb1fd43fd05e7d983f994993d4982 diff --git a/assets/syntaxes/02_Extra/VimL.sublime-syntax b/assets/syntaxes/02_Extra/VimL.sublime-syntax index f2b202c3..3f96a985 100644 --- a/assets/syntaxes/02_Extra/VimL.sublime-syntax +++ b/assets/syntaxes/02_Extra/VimL.sublime-syntax @@ -75,10 +75,10 @@ contexts: - match: '''(''''|\n[^\S\n]*\\|[^\n''])*''' scope: string.quoted.single.viml string_regex: - - match: '/(\\\\|\\/|\n[^\S\n]*\\|[^\n/])*/' + - match: '[gvs]{1}/(\\\\|\\/|\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 + - match: \b(set(local|global)?|let|command|filetype|syntax|colorscheme|\w*map|\w*a(b|brev)?|syn|exe(c|cute)?|ec(ho|)?|au(tocmd|)?)\b scope: support.function.viml support_type: - match: <.*?> diff --git a/assets/syntaxes/02_Extra/ssh-config b/assets/syntaxes/02_Extra/ssh-config index 1ddcb320..201816b6 160000 --- a/assets/syntaxes/02_Extra/ssh-config +++ b/assets/syntaxes/02_Extra/ssh-config @@ -1 +1 @@ -Subproject commit 1ddcb320aca1bf31b0048a3109540475d5af64e2 +Subproject commit 201816b609abf7ccf583f7e888f6dc4121410d70 diff --git a/assets/themes.bin b/assets/themes.bin index bc291724..ae04c566 100644 Binary files a/assets/themes.bin and b/assets/themes.bin differ diff --git a/assets/themes/Coldark b/assets/themes/Coldark index b4a1c74d..e44750b2 160000 --- a/assets/themes/Coldark +++ b/assets/themes/Coldark @@ -1 +1 @@ -Subproject commit b4a1c74d8d5bdd136ec530e5905b810272472545 +Subproject commit e44750b2a9629dd12d8ed3ad9fd50c77232170b9 diff --git a/assets/themes/ansi-light.tmTheme b/assets/themes/ansi-light.tmTheme deleted file mode 100644 index 5dfb94e8..00000000 --- a/assets/themes/ansi-light.tmTheme +++ /dev/null @@ -1,504 +0,0 @@ - - - - - - author - Template: Chris Kempson, Scheme: Mitchell Kember - name - ANSI Light - colorSpaceName - sRGB - settings - - - settings - - background - #07000000 - caret - #00000000 - foreground - #00000000 - invisibles - #00000000 - lineHighlight - #00000000 - selection - #00000000 - gutter - #07000000 - gutterForeground - #00000000 - - - - name - Text - scope - variable.parameter.function - settings - - foreground - #00000000 - - - - name - Comments - scope - comment, punctuation.definition.comment - settings - - foreground - #02000000 - - - - name - Punctuation - scope - punctuation.definition.string, punctuation.definition.variable, punctuation.definition.string, punctuation.definition.parameters, punctuation.definition.string, punctuation.definition.array - settings - - foreground - #00000000 - - - - name - Delimiters - scope - none - settings - - foreground - #00000000 - - - - name - Operators - scope - keyword.operator - settings - - foreground - #00000000 - - - - name - Keywords - scope - keyword - settings - - foreground - #05000000 - - - - name - Variables - scope - variable - settings - - foreground - #00000000 - - - - name - Functions - scope - entity.name.function, meta.require, support.function.any-method - settings - - foreground - #04000000 - - - - name - Labels - scope - entity.name.label - settings - - foreground - #06000000 - - - - name - Classes - scope - support.class, entity.name.class, entity.name.type.class - settings - - foreground - #03000000 - - - - name - Classes - scope - meta.class - settings - - foreground - #00000000 - - - - name - Methods - scope - keyword.other.special-method - settings - - foreground - #04000000 - - - - name - Storage - scope - storage - settings - - foreground - #05000000 - - - - name - Support - scope - support.function - settings - - foreground - #06000000 - - - - name - Strings, Inherited Class - scope - string, constant.other.symbol, entity.other.inherited-class - settings - - foreground - #02000000 - - - - name - Integers - scope - constant.numeric - settings - - foreground - #03000000 - - - - name - Floats - scope - none - settings - - foreground - #03000000 - - - - name - Boolean - scope - none - settings - - foreground - #03000000 - - - - name - Constants - scope - constant - settings - - foreground - #03000000 - - - - name - Tags - scope - entity.name.tag - settings - - foreground - #01000000 - - - - name - Attributes - scope - entity.other.attribute-name - settings - - foreground - #03000000 - - - - name - Attribute IDs - scope - entity.other.attribute-name.id, punctuation.definition.entity - settings - - foreground - #04000000 - - - - name - Selector - scope - meta.selector - settings - - foreground - #05000000 - - - - name - Values - scope - none - settings - - foreground - #03000000 - - - - name - Headings - scope - markup.heading punctuation.definition.heading, entity.name.section - settings - - fontStyle - - foreground - #04000000 - - - - name - Units - scope - keyword.other.unit - settings - - foreground - #03000000 - - - - name - Bold - scope - markup.bold, punctuation.definition.bold - settings - - fontStyle - bold - foreground - #03000000 - - - - name - Italic - scope - markup.italic, punctuation.definition.italic - settings - - fontStyle - italic - foreground - #05000000 - - - - name - Code - scope - markup.raw.inline - settings - - foreground - #02000000 - - - - name - Link Text - scope - string.other.link, punctuation.definition.string.end.markdown, punctuation.definition.string.begin.markdown - settings - - foreground - #01000000 - - - - name - Link Url - scope - meta.link - settings - - foreground - #03000000 - - - - name - Quotes - scope - markup.quote - settings - - foreground - #03000000 - - - - name - Inserted - scope - markup.inserted - settings - - foreground - #02000000 - - - - name - Deleted - scope - markup.deleted - settings - - foreground - #01000000 - - - - name - Changed - scope - markup.changed - settings - - foreground - #05000000 - - - - name - Colors - scope - constant.other.color - settings - - foreground - #06000000 - - - - name - Regular Expressions - scope - string.regexp - settings - - foreground - #06000000 - - - - name - Escape Characters - scope - constant.character.escape - settings - - foreground - #06000000 - - - - name - Embedded - scope - punctuation.section.embedded, variable.interpolation - settings - - foreground - #05000000 - - - - name - Illegal - scope - invalid.illegal - settings - - background - #01000000 - foreground - #00000000 - - - - name - Broken - scope - invalid.broken - settings - - background - #03000000 - foreground - #07000000 - - - - uuid - uuid - - diff --git a/assets/themes/ansi-dark.tmTheme b/assets/themes/ansi.tmTheme similarity index 81% rename from assets/themes/ansi-dark.tmTheme rename to assets/themes/ansi.tmTheme index a7976951..957f42bb 100644 --- a/assets/themes/ansi-dark.tmTheme +++ b/assets/themes/ansi.tmTheme @@ -3,14 +3,14 @@ author Template: Chris Kempson, Scheme: Mitchell Kember name - ANSI Dark + ANSI colorSpaceName sRGB settings @@ -19,32 +19,17 @@ settings background - #00000000 - caret - #07000000 + #00000001 foreground - #07000000 - invisibles - #07000000 - lineHighlight - #07000000 - selection - #07000000 + #00000001 + gutter - #00000000 + #00000001 gutterForeground - #07000000 - - - - name - Text - scope - variable.parameter.function - settings - - foreground - #07000000 + #00000001 @@ -58,39 +43,6 @@ #02000000 - - name - Punctuation - scope - punctuation.definition.string, punctuation.definition.variable, punctuation.definition.string, punctuation.definition.parameters, punctuation.definition.string, punctuation.definition.array - settings - - foreground - #07000000 - - - - name - Delimiters - scope - none - settings - - foreground - #07000000 - - - - name - Operators - scope - keyword.operator - settings - - foreground - #07000000 - - name Keywords @@ -102,17 +54,6 @@ #05000000 - - name - Variables - scope - variable - settings - - foreground - #07000000 - - name Functions @@ -146,17 +87,6 @@ #03000000 - - name - Classes - scope - meta.class - settings - - foreground - #07000000 - - name Methods @@ -480,8 +410,6 @@ background #01000000 - foreground - #07000000 @@ -493,8 +421,6 @@ background #03000000 - foreground - #00000000 diff --git a/assets/themes/gruvbox b/assets/themes/gruvbox index e3db74d0..64c47250 160000 --- a/assets/themes/gruvbox +++ b/assets/themes/gruvbox @@ -1 +1 @@ -Subproject commit e3db74d0e5de7bc09cab76377723ccf6bcc64e8c +Subproject commit 64c47250e54298b91e2cf8d401320009aba9f991 diff --git a/diagnostics/info.sh b/diagnostics/info.sh index bf4eb4d8..b8d99983 100755 --- a/diagnostics/info.sh +++ b/diagnostics/info.sh @@ -58,7 +58,8 @@ _bat_:run() { _out "$BAT" --version _out env | grep '^BAT_\|^PAGER=' - local cache_dir="$($BAT --cache-dir)" + local cache_dir + cache_dir="$($BAT --cache-dir)" if [[ -f "${cache_dir}/syntaxes.bin" ]]; then _print_command "$BAT" "--list-languages" echo "Found custom syntax set." @@ -79,8 +80,8 @@ _bat_config_:run() { _bat_wrapper_:run() { _bat_wrapper_:detect_wrapper() { local bat="$1" - if file "$(which "${bat}")" | grep "text executable" &> /dev/null; then - _out_fence cat "$(which "${bat}")" + if file "$(command -v "${bat}")" | grep "text executable" &> /dev/null; then + _out_fence cat "$(command -v "${bat}")" return fi @@ -104,7 +105,8 @@ _bat_wrapper_function_:run() { fi ;; *bash* | *zsh*) - local type="$("$SHELL" --login -i -c "type ${command}" 2>&1)" + local type + type="$("$SHELL" --login -i -c "type ${command}" 2>&1)" if grep 'function' <<< "$type" &> /dev/null; then _out_fence "$SHELL" --login -i -c "declare -f ${command}" return diff --git a/src/assets.rs b/src/assets.rs index eecb0693..5e0c0644 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -11,6 +11,7 @@ use syntect::parsing::{SyntaxReference, SyntaxSet, SyntaxSetBuilder}; use path_abs::PathAbs; use crate::assets_metadata::AssetsMetadata; +use crate::bat_warning; use crate::error::*; use crate::input::{InputReader, OpenedInput, OpenedInputKind}; use crate::syntax_mapping::{MappingTarget, SyntaxMapping}; @@ -189,13 +190,12 @@ impl HighlightingAssets { match self.theme_set.themes.get(theme) { Some(theme) => theme, None => { + if theme == "ansi-light" || theme == "ansi-dark" { + bat_warning!("Theme '{}' is deprecated, using 'ansi' instead.", theme); + return self.get_theme("ansi"); + } if theme != "" { - use ansi_term::Colour::Yellow; - eprintln!( - "{}: Unknown theme '{}', using default.", - Yellow.paint("[bat warning]"), - theme - ); + bat_warning!("Unknown theme '{}', using default.", theme) } &self.theme_set.themes[self.fallback_theme.unwrap_or_else(|| Self::default_theme())] } diff --git a/src/bin/bat/app.rs b/src/bin/bat/app.rs index a25e41d2..3d0df0ec 100644 --- a/src/bin/bat/app.rs +++ b/src/bin/bat/app.rs @@ -16,6 +16,7 @@ use console::Term; use crate::input::{new_file_input, new_stdin_input}; use bat::{ assets::HighlightingAssets, + bat_warning, config::{Config, VisibleLines}, error::*, input::Input, @@ -82,10 +83,9 @@ impl App { Some("always") => PagingMode::Always, Some("never") => PagingMode::Never, Some("auto") | None => { - if self.matches.occurrences_of("plain") > 1 { - // If we have -pp as an option when in auto mode, the pager should be disabled. - PagingMode::Never - } else if self.matches.is_present("no-paging") { + // If we have -pp as an option when in auto mode, the pager should be disabled. + let extra_plain = self.matches.occurrences_of("plain") > 1; + if extra_plain || self.matches.is_present("no-paging") { PagingMode::Never } else if inputs.iter().any(Input::is_stdin) { // If we are reading from stdin, only enable paging if we write to an @@ -323,11 +323,7 @@ impl App { // If `grid` is set, remove `rule` as it is a subset of `grid`, and print a warning. if styled_components.grid() && styled_components.0.remove(&StyleComponent::Rule) { - use ansi_term::Colour::Yellow; - eprintln!( - "{}: Style 'rule' is a subset of style 'grid', 'rule' will not be visible.", - Yellow.paint("[bat warning]"), - ); + bat_warning!("Style 'rule' is a subset of style 'grid', 'rule' will not be visible."); } Ok(styled_components) diff --git a/src/bin/bat/main.rs b/src/bin/bat/main.rs index eb6f75bc..af8ca787 100644 --- a/src/bin/bat/main.rs +++ b/src/bin/bat/main.rs @@ -36,6 +36,7 @@ use bat::{ input::Input, style::{StyleComponent, StyleComponents}, MappingTarget, + PagingMode, }; const THEME_PREVIEW_DATA: &[u8] = include_bytes!("../../../assets/theme_preview.rs"); @@ -78,7 +79,9 @@ fn get_syntax_mapping_to_paths<'a>( map } -pub fn list_languages(config: &Config) -> Result<()> { +pub fn get_languages(config: &Config) -> Result { + let mut result: String = String::new(); + let assets = assets_from_cache_or_binary()?; let mut languages = assets .syntaxes() @@ -119,12 +122,9 @@ pub fn list_languages(config: &Config) -> Result<()> { } } - let stdout = io::stdout(); - let mut stdout = stdout.lock(); - if config.loop_through { for lang in languages { - writeln!(stdout, "{}:{}", lang.name, lang.file_extensions.join(","))?; + result += &format!("{}:{}\n", lang.name, lang.file_extensions.join(",")); } } else { let longest = languages @@ -145,7 +145,7 @@ pub fn list_languages(config: &Config) -> Result<()> { }; for lang in languages { - write!(stdout, "{:width$}{}", lang.name, separator, width = longest)?; + result += &format!("{:width$}{}", lang.name, separator, width = longest); // Number of characters on this line so far, wrap before `desired_width` let mut num_chars = 0; @@ -156,20 +156,20 @@ pub fn list_languages(config: &Config) -> Result<()> { let new_chars = word.len() + comma_separator.len(); if num_chars + new_chars >= desired_width { num_chars = 0; - write!(stdout, "\n{:width$}{}", "", separator, width = longest)?; + result += &format!("\n{:width$}{}", "", separator, width = longest); } num_chars += new_chars; - write!(stdout, "{}", style.paint(&word[..]))?; + result += &format!("{}", style.paint(&word[..])); if extension.peek().is_some() { - write!(stdout, "{}", comma_separator)?; + result += comma_separator; } } - writeln!(stdout)?; + result += "\n"; } } - Ok(()) + Ok(result) } fn theme_preview_file<'a>() -> Input<'a> { @@ -200,19 +200,19 @@ pub fn list_themes(cfg: &Config) -> Result<()> { .ok(); writeln!(stdout)?; } + writeln!( + stdout, + "Further themes can be installed to '{}', \ + and are added to the cache with `bat cache --build`. \ + For more information, see:\n\n \ + https://github.com/sharkdp/bat#adding-new-themes", + config_file().join("themes").to_string_lossy() + )?; } else { for theme in assets.themes() { writeln!(stdout, "{}", theme)?; } } - writeln!( - stdout, - "Further themes can be installed to '{}', \ - and are added to the cache with `bat cache --build`. \ - For more information, see:\n\n \ - https://github.com/sharkdp/bat#adding-new-themes", - config_file().join("themes").to_string_lossy() - )?; Ok(()) } @@ -248,8 +248,14 @@ fn run() -> Result { let config = app.config(&inputs)?; if app.matches.is_present("list-languages") { - list_languages(&config)?; - Ok(true) + let languages: String = get_languages(&config)?; + let inputs: Vec = vec![Input::from_reader(Box::new(languages.as_bytes()))]; + let plain_config = Config { + style_components: StyleComponents::new(StyleComponent::Plain.components(false)), + paging_mode: PagingMode::QuitIfOneScreen, + ..Default::default() + }; + run_controller(inputs, &plain_config) } else if app.matches.is_present("list-themes") { list_themes(&config)?; Ok(true) diff --git a/src/controller.rs b/src/controller.rs index babea0c9..8dd73986 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -197,6 +197,8 @@ impl<'b> Controller<'b> { let mut first_range: bool = true; let mut mid_range: bool = false; + let style_snip = self.config.style_components.snip(); + while reader.read_line(&mut line_buffer)? { match line_ranges.check(line_number) { RangeCheckResult::BeforeOrBetweenRanges => { @@ -207,7 +209,7 @@ impl<'b> Controller<'b> { } RangeCheckResult::InRange => { - if self.config.style_components.snip() { + if style_snip { if first_range { first_range = false; mid_range = true; diff --git a/src/lib.rs b/src/lib.rs index 3b89159f..0c41716d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,8 @@ //! .unwrap(); //! ``` +mod macros; + pub mod assets; pub mod assets_metadata; pub mod config; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..beb63ea7 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,7 @@ +#[macro_export] +macro_rules! bat_warning { + ($($arg:tt)*) => ({ + use ansi_term::Colour::Yellow; + eprintln!("{}: {}", Yellow.paint("[bat warning]"), format!($($arg)*)); + }) +} diff --git a/src/preprocessor.rs b/src/preprocessor.rs index eb878890..7001ec88 100644 --- a/src/preprocessor.rs +++ b/src/preprocessor.rs @@ -72,7 +72,7 @@ pub fn replace_nonprintable(input: &[u8], tab_width: usize) -> String { } } // line feed - '\x0A' => output.push('␊'), + '\x0A' => output.push_str("␊\x0A"), // carriage return '\x0D' => output.push('␍'), // null diff --git a/src/printer.rs b/src/printer.rs index ba74872a..a4b143d4 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -91,9 +91,6 @@ impl<'a> Printer for SimplePrinter<'a> { if self.config.show_nonprintable { let line = replace_nonprintable(line_buffer, self.config.tab_width); write!(handle, "{}", line)?; - if line_buffer.last() == Some(&b'\n') { - writeln!(handle)?; - } } else { handle.write_all(line_buffer)? }; @@ -451,7 +448,7 @@ impl<'a> Printer for InteractivePrinter<'a> { if text.len() != text_trimmed.len() { if let Some(background_color) = background_color { let mut ansi_style = Style::default(); - ansi_style.background = Some(to_ansi_color(background_color, true_color)); + ansi_style.background = to_ansi_color(background_color, true_color); let width = if cursor_total <= cursor_max { cursor_max - cursor_total + 1 } else { @@ -463,7 +460,7 @@ impl<'a> Printer for InteractivePrinter<'a> { } } - if line.bytes().next_back() != Some(b'\n') { + if !self.config.style_components.plain() && line.bytes().next_back() != Some(b'\n') { writeln!(handle)?; } } else { @@ -592,8 +589,7 @@ impl<'a> Printer for InteractivePrinter<'a> { if let Some(background_color) = background_color { let mut ansi_style = Style::default(); - ansi_style.background = - Some(to_ansi_color(background_color, self.config.true_color)); + ansi_style.background = to_ansi_color(background_color, self.config.true_color); write!( handle, @@ -627,20 +623,27 @@ impl Colors { } fn colored(theme: &Theme, true_color: bool) -> Self { - let gutter_color = theme - .settings - .gutter_foreground - .map(|c| to_ansi_color(c, true_color)) - .unwrap_or(Fixed(DEFAULT_GUTTER_COLOR)); + let gutter_style = Style { + foreground: match theme.settings.gutter_foreground { + // If the theme provides a gutter foreground color, use it. + // Note: It might be the special value #00000001, in which case + // to_ansi_color returns None and we use an empty Style + // (resulting in the terminal's default foreground color). + Some(c) => to_ansi_color(c, true_color), + // Otherwise, use a specific fallback color. + None => Some(Fixed(DEFAULT_GUTTER_COLOR)), + }, + ..Style::default() + }; Colors { - grid: gutter_color.normal(), - rule: gutter_color.normal(), + grid: gutter_style, + rule: gutter_style, filename: Style::new().bold(), git_added: Green.normal(), git_removed: Red.normal(), git_modified: Yellow.normal(), - line_number: gutter_color.normal(), + line_number: gutter_style, } } } diff --git a/src/terminal.rs b/src/terminal.rs index b744ba9a..b7159347 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -3,13 +3,13 @@ use ansi_term::{self, Style}; use syntect::highlighting::{self, FontStyle}; -pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term::Color { +pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> Option { if color.a == 0 { // Themes can specify one of the user-configurable terminal colors by // encoding them as #RRGGBBAA with AA set to 00 (transparent) and RR set - // to the 8-bit color palette number. The built-in themes ansi-light, - // ansi-dark, base16, and base16-256 use this. - match color.r { + // to the 8-bit color palette number. The built-in themes ansi, base16, + // and base16-256 use this. + Some(match color.r { // For the first 8 colors, use the Color enum to produce ANSI escape // sequences using codes 30-37 (foreground) and 40-47 (background). // For example, red foreground is \x1b[31m. This works on terminals @@ -31,11 +31,18 @@ pub fn to_ansi_color(color: highlighting::Color, true_color: bool) -> ansi_term: // 90-97 (foreground) and 100-107 (background), we should use those // for values 0x08 to 0x0f and only use Fixed for 0x10 to 0xff. n => Fixed(n), - } + }) + } else if color.a == 1 { + // Themes can specify the terminal's default foreground/background color + // (i.e. no escape sequence) using the encoding #RRGGBBAA with AA set to + // 01. The built-in theme ansi uses this. + None } else if true_color { - RGB(color.r, color.g, color.b) + Some(RGB(color.r, color.g, color.b)) } else { - Fixed(ansi_colours::ansi256_from_rgb((color.r, color.g, color.b))) + Some(Fixed(ansi_colours::ansi256_from_rgb(( + color.r, color.g, color.b, + )))) } } @@ -54,7 +61,10 @@ pub fn as_terminal_escaped( let mut style = if !colored { Style::default() } else { - let mut color = Style::from(to_ansi_color(style.foreground, true_color)); + let mut color = Style { + foreground: to_ansi_color(style.foreground, true_color), + ..Style::default() + }; if style.font_style.contains(FontStyle::BOLD) { color = color.bold(); } @@ -67,6 +77,6 @@ pub fn as_terminal_escaped( color }; - style.background = background_color.map(|c| to_ansi_color(c, true_color)); + style.background = background_color.and_then(|c| to_ansi_color(c, true_color)); style.paint(text).to_string() } diff --git a/tests/assets.rs b/tests/assets.rs index 55b5399e..d5de9015 100644 --- a/tests/assets.rs +++ b/tests/assets.rs @@ -30,13 +30,11 @@ fn all_themes_are_present() { "Solarized (light)", "Sublime Snazzy", "TwoDark", - "ansi-dark", - "ansi-light", + "ansi", "base16", "base16-256", - "gruvbox", + "gruvbox-dark", "gruvbox-light", - "gruvbox-white", "zenburn" ] ); diff --git a/tests/benchmarks/comparison.sh b/tests/benchmarks/comparison.sh index c9eb5237..be1479e6 100755 --- a/tests/benchmarks/comparison.sh +++ b/tests/benchmarks/comparison.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -cd "$(dirname "${BASH_SOURCE[0]}")" +cd "$(dirname "${BASH_SOURCE[0]}")" || exit -if ! which hyperfine > /dev/null 2>&1; then +if ! command -v 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 diff --git a/tests/benchmarks/run-benchmarks.sh b/tests/benchmarks/run-benchmarks.sh index c3a15509..f74bdf24 100755 --- a/tests/benchmarks/run-benchmarks.sh +++ b/tests/benchmarks/run-benchmarks.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash -cd "$(dirname "${BASH_SOURCE[0]}")" +cd "$(dirname "${BASH_SOURCE[0]}")" || exit -if ! which hyperfine > /dev/null 2>&1; then +if ! command -v 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 diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 18c4dcc6..eace816d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -837,3 +837,41 @@ fn show_all_mode() { .stdout("hello·world␊\n├──┤␍␀␇␈␛") .stderr(""); } + +#[test] +fn plain_mode_does_not_add_nonexisting_newline() { + bat() + .arg("--paging=never") + .arg("--color=never") + .arg("--decorations=always") + .arg("--style=plain") + .arg("single-line.txt") + .assert() + .success() + .stdout("Single Line"); +} + +// Regression test for https://github.com/sharkdp/bat/issues/299 +#[test] +fn grid_for_file_without_newline() { + bat() + .arg("--paging=never") + .arg("--color=never") + .arg("--terminal-width=80") + .arg("--wrap=never") + .arg("--decorations=always") + .arg("--style=full") + .arg("single-line.txt") + .assert() + .success() + .stdout( + "\ +───────┬──────────────────────────────────────────────────────────────────────── + │ File: single-line.txt +───────┼──────────────────────────────────────────────────────────────────────── + 1 │ Single Line +───────┴──────────────────────────────────────────────────────────────────────── +", + ) + .stderr(""); +} diff --git a/tests/syntax-tests/highlighted/Plaintext/plaintext.txt b/tests/syntax-tests/highlighted/Plaintext/plaintext.txt index 807728e1..1f27a79f 100644 --- a/tests/syntax-tests/highlighted/Plaintext/plaintext.txt +++ b/tests/syntax-tests/highlighted/Plaintext/plaintext.txt @@ -175,4 +175,4 @@ \u{ad}␊ \u{ae}␊ ␊ -Here's·a·line·with·multiple·characters. +Here's·a·line·with·multiple·characters.␊ diff --git a/tests/syntax-tests/highlighted/VimL/source.vim b/tests/syntax-tests/highlighted/VimL/source.vim new file mode 100644 index 00000000..77a5638c --- /dev/null +++ b/tests/syntax-tests/highlighted/VimL/source.vim @@ -0,0 +1,92 @@ +if &compatible + set nocompatible +endif + +if has('win32') || has ('win64') + let $VIMHOME = $HOME . "/vimfiles" +elseif v:false && v:true + echo "Can't get here" +else + let $VIMHOME = $HOME . "/.vim" +endif + +" show existing tab with 2 spaces width +set tabstop=2 +" when indenting with '>', use 2 spaces width +set shiftwidth=2 +" always set autoindenting on +set autoindent + +autocmd VimEnter * echo "Hello Vim" + +" Allow :W and :Wq to save too +command! Wq :wq +command! W :w + +augroup vimrc + autocmd! + autocmd FileType * echo "New filetype" +augroup END + +function! s:echo(what) + return a:what +endfunction + +function! HelloWorld(name) + let l:function_local = "function_local_var" + let l:parts = split(l:function_local, "_") + let l:greeting = "Hello " . a:name + return s:echo(l:greeting) +endfunction + +function! source#Hello() + return "Hello from namespace" +endfunction + +function! EchoFunc(...) + for s in a:000 + echon ' ' . s + endfor +endfunction + +imap  =HelloWorld("World") + +command! -nargs=? Echo :call EchoFunc() + +" TODO test stuff +let g:global = "global var" +let s:script_var = "script var" +let w:window_var = "window war" +let b:buffer_var = "buffer war" +let t:tab_var = "tab war" +echo v:false + +3 + 5 + +echo "Hello" ==# "Hello2" +echo "Hello" ==? "Hello2" +echo "Hello" == "Hello2" +echo "Hello" is "Hello2" +echo "Hello" isnot "Hello2" +echo "Hello" =~ 'xx*' +echo "Hello" !~ "Hello2" +echo "Hello" !~ "Hello2" + +echo "/This/should/not/be/a/regex" + +" Error case from issue #1604 (https://github.com/sharkdp/bat/issues/1064) +set runtimepath=~/foo/bar + +silent g/Aap/p + +let g:dict = {} +let g:dict.item = ['l1', 'l2'] + +let g:dict2 = {'dict_item': ['l1', 'l2'], 'di2': 'x'} + +silent g/regex/ +silent v/regex/ +silent %s/regex/not_regex/ + +filetype plugin indent on +syntax enable diff --git a/tests/syntax-tests/regression_test.sh b/tests/syntax-tests/regression_test.sh index 25acb256..45e2e3ea 100755 --- a/tests/syntax-tests/regression_test.sh +++ b/tests/syntax-tests/regression_test.sh @@ -4,7 +4,7 @@ set -eou pipefail script_directory="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -output_directory=$(mktemp -d --suffix=.bat-syntax-regression-test) +output_directory=$(mktemp -d) "$script_directory"/create_highlighted_versions.py --output="$output_directory" diff --git a/tests/syntax-tests/source/ActionScript/test.as b/tests/syntax-tests/source/ActionScript/test.as index 1f7a1936..f1828bbc 100644 --- a/tests/syntax-tests/source/ActionScript/test.as +++ b/tests/syntax-tests/source/ActionScript/test.as @@ -72,4 +72,4 @@ package TestSyntax { var sndChannel:SoundChannel = mySound.play(); } } -} \ No newline at end of file +} diff --git a/tests/syntax-tests/source/Batch/build.bat b/tests/syntax-tests/source/Batch/build.bat index 56a262e2..acf9f7cd 100644 --- a/tests/syntax-tests/source/Batch/build.bat +++ b/tests/syntax-tests/source/Batch/build.bat @@ -56,4 +56,4 @@ set LDLIBS= ^ @set "LINK_FILES=%LINK_FILES% %%~f" ) -lld-link.exe %LINK_FILES% -out:"%OUTPUT%" %LDFLAGS% %LDLIBS% \ No newline at end of file +lld-link.exe %LINK_FILES% -out:"%OUTPUT%" %LDFLAGS% %LDLIBS% diff --git a/tests/syntax-tests/source/Clojure/test.clj b/tests/syntax-tests/source/Clojure/test.clj index ea24e822..b4010da1 100644 --- a/tests/syntax-tests/source/Clojure/test.clj +++ b/tests/syntax-tests/source/Clojure/test.clj @@ -55,4 +55,4 @@ (println (factorial 5)) (log) (log "Message")) - \ No newline at end of file + diff --git a/tests/syntax-tests/source/Dockerfile/Dockerfile b/tests/syntax-tests/source/Dockerfile/Dockerfile index 54a3b2c8..b38bce26 100644 --- a/tests/syntax-tests/source/Dockerfile/Dockerfile +++ b/tests/syntax-tests/source/Dockerfile/Dockerfile @@ -16,4 +16,4 @@ EXPOSE 80/tcp VOLUME [/var/lib/mysql/data] -ENTRYPOINT ["/usr/bin/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/usr/bin/entrypoint.sh"] diff --git a/tests/syntax-tests/source/Git Attributes/example.gitattributes b/tests/syntax-tests/source/Git Attributes/example.gitattributes index 9b8e58b8..8b3502b7 100644 --- a/tests/syntax-tests/source/Git Attributes/example.gitattributes +++ b/tests/syntax-tests/source/Git Attributes/example.gitattributes @@ -13,4 +13,4 @@ *.patch -text .gitattributes linguist-language=gitattributes -.gitkeep export-ignore \ No newline at end of file +.gitkeep export-ignore diff --git a/tests/syntax-tests/source/Git Config/text.gitconfig b/tests/syntax-tests/source/Git Config/text.gitconfig index 45de0fe5..47224f0b 100644 --- a/tests/syntax-tests/source/Git Config/text.gitconfig +++ b/tests/syntax-tests/source/Git Config/text.gitconfig @@ -104,4 +104,4 @@ [user] email = f.nord@example.com name = Frank Nord - signingkey = AAAAAAAAAAAAAAAA \ No newline at end of file + signingkey = AAAAAAAAAAAAAAAA diff --git a/tests/syntax-tests/source/Hosts/hosts b/tests/syntax-tests/source/Hosts/hosts index d7d4307d..e9d24c2d 100644 --- a/tests/syntax-tests/source/Hosts/hosts +++ b/tests/syntax-tests/source/Hosts/hosts @@ -5,4 +5,4 @@ 192.160.0.200 try.sample.test try #another comment 216.58.223.238 google.com -::1 localhost.try ip6-localhost \ No newline at end of file +::1 localhost.try ip6-localhost diff --git a/tests/syntax-tests/source/Makefile/Makefile b/tests/syntax-tests/source/Makefile/Makefile index 7acc5b2d..87379733 100644 --- a/tests/syntax-tests/source/Makefile/Makefile +++ b/tests/syntax-tests/source/Makefile/Makefile @@ -382,4 +382,4 @@ install: all @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME) uninstall: - rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)} \ No newline at end of file + rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)} diff --git a/tests/syntax-tests/source/PHP/test.php b/tests/syntax-tests/source/PHP/test.php index 26413597..1f0a581f 100644 --- a/tests/syntax-tests/source/PHP/test.php +++ b/tests/syntax-tests/source/PHP/test.php @@ -108,4 +108,4 @@ $doe->setName('John Doe'); $ending = 2 > 3 ? "yep" : "nah"; -?> \ No newline at end of file +?> diff --git a/tests/syntax-tests/source/Plaintext/plaintext.txt b/tests/syntax-tests/source/Plaintext/plaintext.txt index 42da1207..112ef7e9 100644 Binary files a/tests/syntax-tests/source/Plaintext/plaintext.txt and b/tests/syntax-tests/source/Plaintext/plaintext.txt differ diff --git a/tests/syntax-tests/source/PowerShell/test.ps1 b/tests/syntax-tests/source/PowerShell/test.ps1 index 434414df..315149a8 100755 Binary files a/tests/syntax-tests/source/PowerShell/test.ps1 and b/tests/syntax-tests/source/PowerShell/test.ps1 differ diff --git a/tests/syntax-tests/source/RequirementsTXT/requirements.txt b/tests/syntax-tests/source/RequirementsTXT/requirements.txt index 83a41dc8..83d71e0e 100644 --- a/tests/syntax-tests/source/RequirementsTXT/requirements.txt +++ b/tests/syntax-tests/source/RequirementsTXT/requirements.txt @@ -5,4 +5,4 @@ pywheels>=12.4 #a whitespace followed by comments Nuitka<0.6.8.4 wxPython>=1.0, <=2.1 -#this is another comment \ No newline at end of file +#this is another comment diff --git a/tests/syntax-tests/source/VimL/source.vim b/tests/syntax-tests/source/VimL/source.vim new file mode 100644 index 00000000..3b1f35f8 --- /dev/null +++ b/tests/syntax-tests/source/VimL/source.vim @@ -0,0 +1,92 @@ +if &compatible + set nocompatible +endif + +if has('win32') || has ('win64') + let $VIMHOME = $HOME . "/vimfiles" +elseif v:false && v:true + echo "Can't get here" +else + let $VIMHOME = $HOME . "/.vim" +endif + +" show existing tab with 2 spaces width +set tabstop=2 +" when indenting with '>', use 2 spaces width +set shiftwidth=2 +" always set autoindenting on +set autoindent + +autocmd VimEnter * echo "Hello Vim" + +" Allow :W and :Wq to save too +command! Wq :wq +command! W :w + +augroup vimrc + autocmd! + autocmd FileType * echo "New filetype" +augroup END + +function! s:echo(what) + return a:what +endfunction + +function! HelloWorld(name) + let l:function_local = "function_local_var" + let l:parts = split(l:function_local, "_") + let l:greeting = "Hello " . a:name + return s:echo(l:greeting) +endfunction + +function! source#Hello() + return "Hello from namespace" +endfunction + +function! EchoFunc(...) + for s in a:000 + echon ' ' . s + endfor +endfunction + +imap =HelloWorld("World") + +command! -nargs=? Echo :call EchoFunc() + +" TODO test stuff +let g:global = "global var" +let s:script_var = "script var" +let w:window_var = "window war" +let b:buffer_var = "buffer war" +let t:tab_var = "tab war" +echo v:false + +3 + 5 + +echo "Hello" ==# "Hello2" +echo "Hello" ==? "Hello2" +echo "Hello" == "Hello2" +echo "Hello" is "Hello2" +echo "Hello" isnot "Hello2" +echo "Hello" =~ 'xx*' +echo "Hello" !~ "Hello2" +echo "Hello" !~ "Hello2" + +echo "/This/should/not/be/a/regex" + +" Error case from issue #1604 (https://github.com/sharkdp/bat/issues/1064) +set runtimepath=~/foo/bar + +silent g/Aap/p + +let g:dict = {} +let g:dict.item = ['l1', 'l2'] + +let g:dict2 = {'dict_item': ['l1', 'l2'], 'di2': 'x'} + +silent g/regex/ +silent v/regex/ +silent %s/regex/not_regex/ + +filetype plugin indent on +syntax enable diff --git a/tests/syntax-tests/source/YAML/example.yaml b/tests/syntax-tests/source/YAML/example.yaml index 1b215705..d7b4c912 100644 --- a/tests/syntax-tests/source/YAML/example.yaml +++ b/tests/syntax-tests/source/YAML/example.yaml @@ -31,4 +31,4 @@ emails: - bob@example.com - bill@example.com supervisors: - - george@example.com \ No newline at end of file + - george@example.com diff --git a/tests/syntax-tests/source/reStructuredText/reference.rst b/tests/syntax-tests/source/reStructuredText/reference.rst index e07f54c4..5c42e540 100644 --- a/tests/syntax-tests/source/reStructuredText/reference.rst +++ b/tests/syntax-tests/source/reStructuredText/reference.rst @@ -317,4 +317,4 @@ blank lines before and after.) .. So this block is not "lost", - despite its indentation. \ No newline at end of file + despite its indentation. diff --git a/tests/syntax-tests/update.sh b/tests/syntax-tests/update.sh index b4752f05..8db1f3d5 100755 --- a/tests/syntax-tests/update.sh +++ b/tests/syntax-tests/update.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -cd "$(dirname "${BASH_SOURCE[0]}")" +cd "$(dirname "${BASH_SOURCE[0]}")" || exit python="python3" if ! command -v python3 &>/dev/null; then python="python"; fi