bat icon indicating copy to clipboard operation
bat copied to clipboard

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

Open marslo opened this issue 10 months ago • 5 comments

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.

marslo avatar Mar 27 '24 03:03 marslo

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


eth-p avatar Mar 30 '24 21:03 eth-p

Hi @eth-p ,

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

  • rg --vimgrep --with-filename 'TODO' | cut -d':' -f1 | uniq: list all filepath contains TODO
  • xargs bat: bat all contents

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

marslo avatar Apr 16 '24 06:04 marslo

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='{}'"

eth-p avatar Apr 17 '24 17:04 eth-p

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

image

marslo avatar May 02 '24 21:05 marslo

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.

marslo avatar May 02 '24 22:05 marslo