sharkdp / bat

A cat(1) clone with wings.
Apache License 2.0
49.08k stars 1.24k forks source link

possibility to suppress output if stdin is empty and set header manually #2916

Open marslo opened 6 months ago

marslo commented 6 months ago

I'm using sed commandline to fetch all of TODO in source code and shows via batcat as below:

$ fd -tf --color never |
  xargs -r -I{} bash -c "sed -ne '/TODO:/,/^\s*$/p' {} | bat -l groovy"

However, the bat shows STDIN <EMPTY> if sed command returns nothing.

currently I'm using if..else.. to show content via bat as below:

$ while read -r file; do
  _content=$(sed -ne '/TODO:/,/^\s*$/p' "${file}");
  [[ -n "${_content}" ]] && echo "${_content}" | bat -l groovy;
done < <(fd -tf --color never)

Any better solution to suppress output if stdin is empty, and by the way, it's nice to have a feature to setup header manually if content is from stdin to easier identify where the output comes from

thanks.

eth-p commented 6 months ago

I think having an extra opt-in command line flag to disable printing the header for binary and/or empty files would be fine. It would be especially useful when using bat in bash scripts, like with what you're doing here.

It's nice to have a feature to setup header manually if content is from stdin to easier identify where the output comes from

The --file-name option may be helpful here. It's even used to detect the language type :)

image


For your specific example of finding TODOs in source code files, ripgrep might be a more performant approach compared to fd+sed:

rg --vimgrep --with-filename 'TODO' | cut -d':' -f1 | uniq | xargs bat

There's also batgrep if you want a bit more of a tailored experience:

image


marslo commented 5 months ago

Hi @eth-p ,

for me, it's just show ALL contents of files contains TODO, for my understanding:


using sed -ne '/TODO:/,/^\s*$/p', means print lines between TODO line to first empty line ^\s*$, since almost of all TODO is added before function, so using sed will show whole function body

base on your example, following commands resolved STDIN <EMPTY> issue:

rg --vimgrep --with-filename 'TODO' --color never |
   cut -d':' -f1 |
   uniq |
   xargs -r -I{} bash -c "sed -ne '/TODO:/,/^\s*$/p' {} |
   bat -l groovy"

However, it still didn't show the exact file name... i.e.:

TODO

here is my batconfig

$ cat ~/.config/bat/config | sed -r '/^(#.*)$/d' | sed -r '/^\s*$/d'
--theme="gruvbox-dark"
--style="numbers,changes,header"
--italic-text=always
--pager="less --RAW-CONTROL-CHARS --quit-if-one-screen --mouse"
--map-syntax "*.ino:C++"
--map-syntax ".ignore:Git Ignore"
--map-syntax='*.conf:INI'
--map-syntax='/etc/apache2/**/*.conf:Apache Conf'
--map-syntax "**/jenkinsfile/**/*:Groovy"

more on sed && sed+bat:

TODO-with-sed

eth-p commented 5 months ago

Hi @marslo

Your understanding of both of those is correct, yes.

For your issue with bat displaying the file name as "STDIN", you can use the --file-name argument in bat to have it change the name in the header. Let me know if the modified code below works for you:

rg --vimgrep --with-filename 'TODO' --color never |
   cut -d':' -f1 |
   uniq |
   xargs -r -I{} bash -c "sed -ne '/TODO:/,/^\s*$/p' {} |
   bat -l groovy --file-name='{}'"
marslo commented 5 months ago

ops... forgot to let you know, your solution works like charm, you can close this issue. thanks again.

image
marslo commented 5 months ago

btw to show the correct line number in the file, it can be handled by nl {} and without -l groovy to let bat automatic use:

rg --vimgrep --with-filename 'TODO:' --color never |
   cut -d':' -f1 |
   uniq |
   xargs -r -I{} bash -c "sed -ne '/TODO:/,/^\s*$/p' < <(nl {}) |
   bat --style="grid,changes,header" --file-name='{}'"

Since we haven lot of files/dotfiles has not extension, so, to identify the language automatically, I'm using the following function ( a little bit ugly, but I don't how to handle via complex shell logic in xargs, so using while read instead of ):

function showTODO() {
  local option='--style="grid,changes,header"'
  while [[ $# -gt 0 ]]; do
    case "$1" in
         -p | --plain ) option+=" $1"    ; shift 1 ;;
                   -* ) option+=" $1 $2" ; shift 2 ;;
    esac
  done

  rg --vimgrep --with-filename 'TODO:' --color never |
     cut -d':' -f1 |
     uniq |
     while read -r _file; do
       # identify language automatically
       local lang='';
       lang="$(sed -r 's/^.+\.(.+)$/\1/' <<< "${_file}")";
       if ! bat --list-languages | command grep -iE -q "[,:]${lang}|${lang},"; then
         # check shebang and reset to empty if shebang not found
         lang=$(sed -rn 's/^#!.*[/\ ](\w+)$/\1/p' < <(head -n1 "${_file}"));
       fi
       sed -ne '/TODO:/,/^\s*$/p' < <(nl "${_file}") |
           eval "bat -l ${lang:-groovy} ${option} --file-name=\"${_file}\"" ;
     done

if bat can detect filetype via shebang line ( i.e.: #!/usr/bin/env bash or #!/bin/bash, via ^#!.*[/\ ](\w+)$), then the simple version will be the best answer.

Screenshot 2024-05-02 at 16 36 43

btw, just curious why bat will using _fzf_path_completion as completion function by default.