diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9298f8..169561d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,9 @@ name: CI on: [push, pull_request] +env: + COLUMNS: 80 + defaults: run: shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index d58555f..8fe16e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ Noteble changes are documentated in this file. +## dev + +### Added + +- hide preview window on small window (when \$COLUMNS less than 80) + - If using default keybinds, use `alt-t` to re-open the preview +- verbose flag completion for bash +- `dotbare` can now be used as a generic fuzzy git tool, using `-g` or `--git flag` + - Sort of like a replacement for `forgit`, bascially just dynamiclly switching + `DOTBARE_DIR` and `DOTBARE_TREE` to the current git directory. + - Seems kind of wierd to make `dotbare` also a fuzzy git client, but since it's literally + like a few lines of changes, I figured why not .. +- options for `fgrep` to configure search behavior in fzf + - `-c, --col`: pass in argument to specify which column to start searching in fzf (`dotbare fgrep --col 2`), by default `fgrep` starts the search from column 3, column 1 is the file name, column2 is the line number and starting from column 3 is the actual content. + - `-f, --full`: configure the fzf search to include all columns, same as using `dotbare fgrep --col 1` which includes the file name, line number and the actual content. +- dedicated completion file to use for package installation + +### CHANGED + +- update the fzf header to make more sense, some wording issues + +### Fixed + +- bash completion raising unexpected git error + ## 1.2.3 (17/07/2020) ### Added @@ -10,11 +35,11 @@ Noteble changes are documentated in this file. - zsh completion for git commands - bash completion for git commands - fgrep: grep words within tracked dotfiles and edit them through EDITOR - More info is documented in wiki. + - More info is documented in wiki ### Changed -- Changed how help messages are printed to reduce some calls +- adjusted how help messages are printed to reduce some calls ### Fixed @@ -24,20 +49,20 @@ Noteble changes are documentated in this file. ### Fixed -- Fixed the dotbare crash when migrating a dotfile repo with over 100 files [#12](https://github.com/kazhala/dotbare/issues/12) -- Fixed dotbare fbackup crash when using cp command on symlink +- dotbare crash when migrating a dotfile repo with over 100 files [#12](https://github.com/kazhala/dotbare/issues/12) +- dotbare fbackup crash when using cp command on symlink ## 1.2.1 (09/07/2020) ### Added - dynamic preview function, detect bats, hightlight etc to provide syntax hightlighting when previewing files. -- Custom preview ENV variable (DOTBARE_PREVIEW) - Note: has to be this format `export DOTBARE_PREVIEW='cat -n {}'`, the `{}` is - used in preview functions to subsitute for the filepath. -- Added support for fancy diff tools like "diff-so-fancy" or "delta" - This is optional, only takes effect if installed and set as `git config core.pager` - Also configurable through DOTBARE_DIFF_PAGER, these are documentated in the README. +- custom preview ENV variable (DOTBARE_PREVIEW) + - Note: has to be this format `export DOTBARE_PREVIEW='cat -n {}'`, the `{}` is + used in preview functions to subsitute for the filepath. +- support for fancy diff tools like "diff-so-fancy" or "delta" + - This is optional, only takes effect if installed and set as `git config core.pager` + - Also configurable through DOTBARE_DIFF_PAGER, these are documentated in the README. ## 1.2.0 (01/07/2020) @@ -45,10 +70,10 @@ Noteble changes are documentated in this file. - `dotbare` now accept verbose type of argument e.g. `dotbare fadd --file` `dotbare fcheckout --branch`. More information please refer to each commands help manual -- Added support for handling files with spaces -- Improved unittest with mocking -- A more reliable `dotbare fupgrade` behavior -- Added version flag for `dotbare`, `dotbare --version` or `dotbare -v` +- support for handling files with spaces +- improved unittest with mocking +- more reliable `dotbare fupgrade` behavior +- version flag for `dotbare`, `dotbare --version` or `dotbare -v` ### Changed @@ -60,7 +85,7 @@ Noteble changes are documentated in this file. ### Removed -- Removed `-a` flag of `dotbare freset`. It's not working as intended because I misunderstand it, the intended +- removed `-a` flag of `dotbare freset`. It's not working as intended because I misunderstand it, the intended behavior is actually achieved by `dotbare fcheckout -a`, use `dotbare fcheckout -a` instead. (Edit: `dotbare fcheckout -a` is now `dotbare fcheckout -s` or `dotbare fcheckout --select`) @@ -68,20 +93,20 @@ Noteble changes are documentated in this file. ### Added -- Added zsh plugin [#4](https://github.com/kazhala/dotbare/pull/4) -- Added bash plugin -- Added drop-in functionality [#6](https://github.com/kazhala/dotbare/pull/6) +- zsh plugin [#4](https://github.com/kazhala/dotbare/pull/4) +- bash plugin +- drop-in functionality [#6](https://github.com/kazhala/dotbare/pull/6) - User can now place custom fzf scripts into scripts folder -- Added bash completion capabilities [#7](https://github.com/kazhala/dotbare/pull/7) -- Added option to clone submodule [#8](https://github.com/kazhala/dotbare/issues/8) +- bash completion capabilities [#7](https://github.com/kazhala/dotbare/pull/7) +- option to clone submodule [#8](https://github.com/kazhala/dotbare/issues/8) ### Fixed -- Fixed ambiguous argument error [#3](https://github.com/kazhala/dotbare/pull/3) +- ambiguous argument error [#3](https://github.com/kazhala/dotbare/pull/3) ### Removed -- Removed global .gitignore manipulation during migration, not needed. Added .gitignore tips to README and +- removed global .gitignore manipulation during migration, not needed. Added .gitignore tips to README and let user handle it ## 1.0.0 (20/05/2020) diff --git a/README.md b/README.md index 3e91009..861a596 100644 --- a/README.md +++ b/README.md @@ -7,27 +7,21 @@ ## Introduction -`dotbare` is a command line utility to help manage dotfiles. It wraps around git bare -repository and heavily utilises [fzf](https://github.com/junegunn/fzf) for an interactive experience. -It is inspired by [forgit](https://github.com/wfxr/forgit), a git wrapper using fzf. -`dotbare` uses a different implementation approach and focuses on managing and interacting with system dotfiles. -Don't worry about migration if you have a symlink/GNU stow setup, you can easily integrate `dotbare` with them. - -Pros: - -- No symlink -- Simple setup/remove -- Customizable -- Easy migration -- Flat learning curve -- Manage dotfiles anywhere in your system -- Integration with symlink/GNU stow setup +`dotbare` is a command line utility to help manage dotfiles and or as a generic fuzzy git client. As a dotfile management tool, +it wraps around git bare repository, query git information from it and display them through [fzf](https://github.com/junegunn/fzf) for an +interactive experience. It is originally inspired by [forgit](https://github.com/wfxr/forgit), a git wrapper using fzf. +`dotbare` uses a different implementation approach and focuses on managing and interacting with system dotfiles. Because +of the flexible implementation of `dotbare`, it can easily integrate with symlink/GNU stow setup or even as a generic +fuzzy git client to use in any git repository. + +As a generic fuzzy git client (usign `--git` flag), `dotbare` dynamically determine the top level `.git` folder and process git information +and perform git operation in the current working tree. You could find out how git bare repository could be used for managing dotfiles [here](https://www.atlassian.com/git/tutorials/dotfiles). Or a [video](https://www.youtube.com/watch?v=tBoLDpTWVOM&t=288s) explanation that helped me to get started. If you are currently using a symlink/GNU stow setup, checkout how to integrate `dotbare` with them [here](#migrating-from-a-generic-symlink-setup-or-gnu-stow). -Select and edit tracked dotfiles. +Select and edit dotfiles. ![fedit](https://user-images.githubusercontent.com/43941510/87669391-37f28180-c7b1-11ea-907d-3b26f363a279.png) Stage and unstage dotfiles. ![fstat](https://user-images.githubusercontent.com/43941510/87669408-43de4380-c7b1-11ea-8a31-fc702eb69804.png) @@ -51,7 +45,8 @@ to another system as you will have to manually resolve all the git checkout issu all the git information and display it through fzf. In addition, `dotbare` also comes with command line completion that you could choose to either to complete `git` commands or `dotbare` commands. `dotbare` also comes with the ability to integrate with GNU stow or any symlink set up as long as you are using git. -It is easy to migrate to any system with minimal set up required. +It is easy to migrate to any system with minimal set up required. In addition, with a simple flag `--git`, you can +now also use `dotbare` as a generic fuzzy git client to manage any git repository. ## Install @@ -405,7 +400,7 @@ All usage and commands are documented in **[wiki](https://github.com/kazhala/dot ## Changes -Latest changes are documented in CHANGELOG.md. View the upcoming changes in the [CHANGELOG](https://github.com/kazhala/dotbare/blob/dev/CHANGELOG.md) of dev branch. +Latest changes are documented in [CHANGELOG](https://github.com/kazhala/dotbare/blob/master/CHANGELOG.md). ## Testing diff --git a/buildspec.yml b/buildspec.yml index e1d0306..83e56c1 100644 --- a/buildspec.yml +++ b/buildspec.yml @@ -4,6 +4,8 @@ env: parameter-store: LOGIN_PASSWORD: /dotbare/dockerpassword LOGIN_USERNAME: /dotbare/dockerusername + exported-variables: + COLUMNS: 80 phases: install: @@ -17,7 +19,7 @@ phases: - 'echo $LOGIN_PASSWORD | docker login --username $LOGIN_USERNAME --password-stdin' build: commands: - - echo Checking script compliance + - echo Checking script compliance ... - ./tests/shellcheck.sh - echo Building docker url image ... - docker image build --build-arg MIGRATE=url -t kazhala/dotbare:testurl -f tests/Dockerfile . @@ -28,8 +30,10 @@ phases: - echo Running bats unittest ... - docker container run -i --rm --name dotbare kazhala/dotbare:testbare - echo Building docker image ... - - docker image build -t kazhala/dotbare:latest . + - DOTBARE_VERSION=$(./dotbare --version | awk -F ": v" '{print $2}') + - docker image build -t kazhala/dotbare:latest -t kazhala/fzfaws:$DOTBARE_VERSION . post_build: commands: - echo Deploying docker image ... - docker image push kazhala/dotbare:latest + - docker image push kazhala/dotbare:$DOTBARE_VERSION diff --git a/dotbare b/dotbare index 716da26..318927c 100755 --- a/dotbare +++ b/dotbare @@ -24,6 +24,7 @@ To see all dotbare specific COMMANDS, run dotbare without any arguments. Optional arguments: -h, --help\t\tshow this help message and exit. -v, --version\t\tshow dotbare current version. + -g, --git\t\tuse dotbare as a generic fuzzy git tool and operate in current git directory. Available commands: All git commands are available, treat dotbare as git. @@ -41,12 +42,25 @@ Available commands: fupgrade \t\tupdate dotbare to the latest master." } -# if no argument, display all possible actions -if [[ "$#" -eq 0 ]]; then - find "${mydir}"/scripts/* -type f -print0 \ - | xargs -I __ -0 basename __ \ +function list_dotbare_commands() { + find "${mydir}"/scripts/* -type f -exec basename {} + \ | fzf --no-multi --header='Available commands' --preview="${mydir}/dotbare {} -h" \ | xargs -I __ "${mydir}"/dotbare __ -h +} + +function execute_dotbare() { + [[ "$#" -eq 0 ]] \ + && list_dotbare_commands \ + && exit 0 + + if [[ -x "${mydir}/scripts/$1" ]]; then + exec "${mydir}/scripts/$1" "${@:2}" + fi +} + +# if no argument, display all possible actions +if [[ "$#" -eq 0 ]]; then + list_dotbare_commands exit 0 fi @@ -64,11 +78,28 @@ case "$1" in echo "Current dotbare version: ${DOTBARE_VERSION}" exit 0 ;; - *) - if [[ -x "${mydir}/scripts/$1" ]]; then - exec "${mydir}/scripts/$1" "${@:2}" + -g|--git) + if git rev-parse --is-inside-work-tree &>/dev/null; then + DOTBARE_TREE=$(git rev-parse --show-toplevel) + DOTBARE_DIR="${DOTBARE_TREE}/.git" + shift + case "$1" in + fbackup|finit|fupgrade) + echo "dotbare $1 is not supported when using dotbare as a generic fuzzy git tool" + exit 1 + ;; + *) + execute_dotbare "$@" + ;; + esac + else + echo "Not in a git directory" + exit 1 fi ;; + *) + execute_dotbare "$@" + ;; esac git --git-dir="${DOTBARE_DIR}" --work-tree="${DOTBARE_TREE}" "$@" diff --git a/dotbare.plugin.bash b/dotbare.plugin.bash index c095bf1..0a5a914 100644 --- a/dotbare.plugin.bash +++ b/dotbare.plugin.bash @@ -5,7 +5,7 @@ mydir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" __dotbare_completion() { - local IFS=$'\n' subcommands curr prev options suggestions + local IFS=$'\n' subcommands curr prev options verbose_options suggestions curr="${COMP_WORDS[$COMP_CWORD]}" prev="${COMP_WORDS[$COMP_CWORD-1]}" @@ -20,7 +20,6 @@ __dotbare_completion() } }' ) - options=$( "${mydir}"/dotbare -h \ | awk '{ @@ -33,22 +32,92 @@ __dotbare_completion() } }' ) - - if [[ "${curr}" == -* ]]; then + verbose_options=$("${mydir}"/dotbare -h | awk '$0 ~ /^ -.*/ {print $2}') + if [[ "${curr}" == --* ]]; then + suggestions=($(compgen -W "${verbose_options}" -- "${curr}")) + elif [[ "${curr}" == -* ]]; then suggestions=($(compgen -W "${options}" -- "${curr}")) else suggestions=($(compgen -W "${subcommands}" -- "${curr}")) fi - elif [[ "${COMP_WORDS[1]}" == "fbackup" && "${prev}" == "-p" ]]; then + elif [[ "${COMP_CWORD}" -eq 2 && "${prev}" == "-g" || "${prev}" == "--git" ]]; then + subcommands=$( + "${mydir}"/dotbare -h \ + | awk '{ + if ($0 ~ /^ f.*/) { + if ($0 ~ /^ f(backup|init|upgrade).*/) { + next + } + gsub(/^ /, "", $0) + gsub(/\t\t/, " ", $0) + print $0 + } + }' + ) + suggestions=($(compgen -W "${subcommands}" -- "${curr}")) + + elif [[ "${COMP_WORDS[1]}" == "-g" || "${COMP_WORDS[1]}" == "--git" ]] && [[ "${COMP_CWORD}" -gt 2 ]]; then + if [[ "${curr}" == --* && "${prev}" != "-h" && "${prev}" != "--help" ]]; then + verbose_options=$( + "${mydir}"/dotbare "${COMP_WORDS[2]}" -h 2> /dev/null \ + | awk '{ + if ($0 ~ /^ -p PATH/) { + next + } else if ($0 ~ /^ -u URL/) { + next + } else if ($0 ~ /^ -*/) { + print $2 + } + }' + ) + suggestions=($(compgen -W "${verbose_options}" -- "${curr}")) + elif [[ "${prev}" != "-h" && "${prev}" != "--help" ]]; then + options=$( + "${mydir}"/dotbare "${COMP_WORDS[2]}" -h 2> /dev/null \ + | awk '{ + gsub(/,/, " ", $0) + if ($0 ~ /^ -p PATH/) { + next + } else if ($0 ~ /^ -u URL/) { + next + } else if ($0 ~ /^ -*/) { + gsub(/^ /, "", $0) + gsub(/\t/, " ", $0) + $2="" + print $0 + } + }' + ) + suggestions=($(compgen -W "${options}" -- "${curr}")) + else + return + fi + + elif [[ "${COMP_WORDS[1]}" == "fbackup" ]] && [[ "${prev}" == "-p" || "${prev}" == "--path" ]]; then COMPREPLY=($(compgen -d -- "${curr}")) return - elif [[ "${COMP_WORDS[1]}" == "finit" && "${prev}" == "-u" ]]; then + elif [[ "${COMP_WORDS[1]}" == "finit" ]] && [[ "${prev}" == "-u" || "${prev}" == "--url" ]]; then return - + elif [[ "${curr}" == --* && "${prev}" != "-h" && "${prev}" != "--help" ]]; then + verbose_options=$( + "${mydir}"/dotbare "${COMP_WORDS[1]}" -h 2> /dev/null \ + | awk '{ + if ($0 ~ /^ -p PATH/) { + print "--path" + } else if ($0 ~ /^ -u URL/) { + print "--url" + } else if ($0 ~ /^ -c COL/) { + print "--col" + } else if ($0 ~ /^ -*/) { + print $2 + } + }' + ) + suggestions=($(compgen -W "${verbose_options}" -- "${curr}")) elif [[ "${prev}" != "-h" && "${prev}" != "--help" ]]; then options=$( - "${mydir}"/dotbare "${COMP_WORDS[1]}" -h \ + "${mydir}"/dotbare "${COMP_WORDS[1]}" -h 2> /dev/null \ | awk '{ gsub(/,/, " ", $0) if ($0 ~ /^ -p PATH/) { @@ -59,6 +128,10 @@ __dotbare_completion() gsub(/^ -u URL --url URL/, "-u", $0) gsub(/\t/, " ", $0) print $0 + } else if ($0 ~ /^ -c COL/) { + gsub(/^ -c COL --col COL/, "-c", $0) + gsub(/\t/, " ", $0) + print $0 } else if ($0 ~ /^ -*/) { gsub(/^ /, "", $0) gsub(/\t/, " ", $0) diff --git a/dotbare.plugin.zsh b/dotbare.plugin.zsh index d343725..e1867c9 100644 --- a/dotbare.plugin.zsh +++ b/dotbare.plugin.zsh @@ -14,6 +14,7 @@ __dotbare_completion() { _arguments -C \ '(- : *)'{-h,--help}'[show help information]' \ '(- : *)'{-v,--version}'[display dotbare version]' \ + '(-g --git)'{-g,--git}'[use dotbare as a generic fuzzy git tool and operate in current git directory]' \ '1:cmds:->cmds' \ '*::options:->options' \ && ret=0 @@ -74,6 +75,8 @@ __dotbare_completion() { fgrep) _arguments \ '(- : *)'{-h,--help}'[show help information]' \ + '(-f --full -c --col)'{-f,--full}'[include all columns during fzf search, as if using "--col 1"]' \ + '(-f --full -c --col)'{-c,--col}'[specify a column number to start searching in fzf]: :->cols' \ && ret=0 ;; finit) diff --git a/helper/git_query.sh b/helper/git_query.sh index 24f04b0..92520ae 100644 --- a/helper/git_query.sh +++ b/helper/git_query.sh @@ -195,18 +195,20 @@ function get_stash() { # all tracked files in the bare repo. # Arguments: # $1: the help message to display in header -# $2: if exists, don't do multi select, only allow single selection +# $2: the fzf delimiter to start searching, default is 3 +# $3: if exists, don't do multi select, only allow single selection # Outputs: # the selected file name with it's line number and line, seperated by ":" # e.g. .bash_profile:1:echo hello ####################################### -function grep_lines() { - local header="${1:-select lines to edit}" +function grep_words() { + local header="${1:-select matches to edit}" + local delimiter="${2:-3}" set_fzf_multi "$2" cd "${DOTBARE_TREE}" || exit git --git-dir="${DOTBARE_DIR}" --work-tree="${DOTBARE_TREE}" \ grep --line-number -- . \ - | fzf --delimiter : --nth 3.. --header="${header}" \ + | fzf --delimiter : --nth "${delimiter}".. --header="${header}" \ --preview "${mydir}/../helper/preview.sh ${DOTBARE_TREE}/{}" \ | awk -F ":" -v home="${DOTBARE_TREE}" '{ print home "/" $1 ":" $2 diff --git a/helper/search_file.sh b/helper/search_file.sh index 7850e4c..cff39a1 100644 --- a/helper/search_file.sh +++ b/helper/search_file.sh @@ -15,7 +15,7 @@ function search_file() { if [[ "${search_type}" == "f" ]]; then find . -maxdepth 1 -type f | sed "s|\./||g" | fzf --multi --preview "${mydir}/preview.sh {}" elif [[ "${search_type}" == "d" ]]; then - if tree --version &>/dev/null; then + if command -v tree &>/dev/null; then find . -maxdepth 1 -type d | awk '{if ($0 != "." && $0 != "./.git"){gsub(/^\.\//, "", $0);print $0}}' | fzf --multi --preview "tree -L 1 -C --dirsfirst {}" else find . -maxdepth 1 -type d | awk '{if ($0 != "." && $0 != "./.git"){gsub(/^\.\//, "", $0);print $0}}' | fzf --multi diff --git a/helper/set_variable.sh b/helper/set_variable.sh index 5b44200..60c4da7 100644 --- a/helper/set_variable.sh +++ b/helper/set_variable.sh @@ -21,7 +21,7 @@ export DOTBARE_DIFF_PAGER="${DOTBARE_DIFF_PAGER:-$(git config core.pager || echo export EDITOR="${EDITOR:-vim}" if [[ -z "${DOTBARE_KEY}" ]]; then - export DOTBARE_KEY=" + DOTBARE_KEY=" --bind=alt-a:toggle-all --bind=alt-w:jump --bind=alt-0:top @@ -32,7 +32,7 @@ fi [[ -z "${FZF_DEFAULT_OPTS}" ]] && export FZF_DEFAULT_OPTS='--cycle' -export FZF_DEFAULT_OPTS=" +FZF_DEFAULT_OPTS=" $FZF_DEFAULT_OPTS --ansi --cycle @@ -41,6 +41,11 @@ export FZF_DEFAULT_OPTS=" $DOTBARE_KEY " +[[ -z "${COLUMNS}" ]] \ + && COLUMNS=$(stty size < /dev/tty | cut -d' ' -f2) +[[ "${COLUMNS}" -lt 80 ]] \ + && FZF_DEFAULT_OPTS="$FZF_DEFAULT_OPTS --preview-window=hidden" + ####################################### # determine to set multi selection or not # Globals: diff --git a/pkg/completion/bash/dotbare b/pkg/completion/bash/dotbare new file mode 100644 index 0000000..7742359 --- /dev/null +++ b/pkg/completion/bash/dotbare @@ -0,0 +1,153 @@ +# shellcheck disable=SC2207 + +_dotbare_completion() +{ + local IFS=$'\n' subcommands curr prev options verbose_options suggestions + curr="${COMP_WORDS[$COMP_CWORD]}" + prev="${COMP_WORDS[$COMP_CWORD-1]}" + + if [[ "$COMP_CWORD" -eq "1" ]]; then + subcommands=$( + dotbare -h \ + | awk '{ + if ($0 ~ /^ f.*/) { + gsub(/^ /, "", $0) + gsub(/\t\t/, " ", $0) + print $0 + } + }' + ) + options=$( + dotbare -h \ + | awk '{ + if ($0 ~ /^ -.*/) { + gsub(/,/, " ", $0) + gsub(/^ /, "", $0) + gsub(/\t\t/, " ", $0) + $2="" + print $0 + } + }' + ) + verbose_options=$(dotbare -h | awk '$0 ~ /^ -.*/ {print $2}') + if [[ "${curr}" == --* ]]; then + suggestions=($(compgen -W "${verbose_options}" -- "${curr}")) + elif [[ "${curr}" == -* ]]; then + suggestions=($(compgen -W "${options}" -- "${curr}")) + else + suggestions=($(compgen -W "${subcommands}" -- "${curr}")) + fi + + elif [[ "${COMP_CWORD}" -eq 2 && "${prev}" == "-g" || "${prev}" == "--git" ]]; then + subcommands=$( + dotbare -h \ + | awk '{ + if ($0 ~ /^ f.*/) { + if ($0 ~ /^ f(backup|init|upgrade).*/) { + next + } + gsub(/^ /, "", $0) + gsub(/\t\t/, " ", $0) + print $0 + } + }' + ) + suggestions=($(compgen -W "${subcommands}" -- "${curr}")) + + elif [[ "${COMP_WORDS[1]}" == "-g" || "${COMP_WORDS[1]}" == "--git" ]] && [[ "${COMP_CWORD}" -gt 2 ]]; then + if [[ "${curr}" == --* && "${prev}" != "-h" && "${prev}" != "--help" ]]; then + verbose_options=$( + dotbare "${COMP_WORDS[2]}" -h 2> /dev/null \ + | awk '{ + if ($0 ~ /^ -p PATH/) { + next + } else if ($0 ~ /^ -u URL/) { + next + } else if ($0 ~ /^ -*/) { + print $2 + } + }' + ) + suggestions=($(compgen -W "${verbose_options}" -- "${curr}")) + elif [[ "${prev}" != "-h" && "${prev}" != "--help" ]]; then + options=$( + dotbare "${COMP_WORDS[2]}" -h 2> /dev/null \ + | awk '{ + gsub(/,/, " ", $0) + if ($0 ~ /^ -p PATH/) { + next + } else if ($0 ~ /^ -u URL/) { + next + } else if ($0 ~ /^ -*/) { + gsub(/^ /, "", $0) + gsub(/\t/, " ", $0) + $2="" + print $0 + } + }' + ) + suggestions=($(compgen -W "${options}" -- "${curr}")) + else + return + fi + + elif [[ "${COMP_WORDS[1]}" == "fbackup" ]] && [[ "${prev}" == "-p" || "${prev}" == "--path" ]]; then + COMPREPLY=($(compgen -d -- "${curr}")) + return + elif [[ "${COMP_WORDS[1]}" == "finit" ]] && [[ "${prev}" == "-u" || "${prev}" == "--url" ]]; then + return + elif [[ "${curr}" == --* && "${prev}" != "-h" && "${prev}" != "--help" ]]; then + verbose_options=$( + dotbare "${COMP_WORDS[1]}" -h 2> /dev/null \ + | awk '{ + if ($0 ~ /^ -p PATH/) { + print "--path" + } else if ($0 ~ /^ -u URL/) { + print "--url" + } else if ($0 ~ /^ -c COL/) { + print "--col" + } else if ($0 ~ /^ -*/) { + print $2 + } + }' + ) + suggestions=($(compgen -W "${verbose_options}" -- "${curr}")) + elif [[ "${prev}" != "-h" && "${prev}" != "--help" ]]; then + options=$( + dotbare "${COMP_WORDS[1]}" -h 2> /dev/null \ + | awk '{ + gsub(/,/, " ", $0) + if ($0 ~ /^ -p PATH/) { + gsub(/^ -p PATH --path PATH/, "-p", $0) + gsub(/\t/, " ", $0) + print $0 + } else if ($0 ~ /^ -u URL/) { + gsub(/^ -u URL --url URL/, "-u", $0) + gsub(/\t/, " ", $0) + print $0 + } else if ($0 ~ /^ -c COL/) { + gsub(/^ -c COL --col COL/, "-c", $0) + gsub(/\t/, " ", $0) + print $0 + } else if ($0 ~ /^ -*/) { + gsub(/^ /, "", $0) + gsub(/\t/, " ", $0) + $2="" + print $0 + } + }' + ) + suggestions=($(compgen -W "${options}" -- "${curr}")) + fi + + if [[ "${#suggestions[*]}" -eq 1 ]]; then + COMPREPLY=("${suggestions[@]%% *}") + else + for i in "${!suggestions[@]}"; do + suggestions[$i]="$(printf '%*s' "-$COLUMNS" "${suggestions[$i]}")" + done + COMPREPLY=("${suggestions[@]}") + fi +} + +complete -F _dotbare_completion dotbare diff --git a/pkg/completion/zsh/_dotbare b/pkg/completion/zsh/_dotbare new file mode 100644 index 0000000..9599754 --- /dev/null +++ b/pkg/completion/zsh/_dotbare @@ -0,0 +1,137 @@ +#compdef dotbare + +_dotbare() { + local context state state_descr line ret curcontext + local -A opt_args + ret=1 + curcontext="${curcontext}" + + _arguments -C \ + '(- : *)'{-h,--help}'[show help information]' \ + '(- : *)'{-v,--version}'[display dotbare version]' \ + '(-g --git)'{-g,--git}'[use dotbare as a generic fuzzy git tool and operate in current git directory]' \ + '1:cmds:->cmds' \ + '*::options:->options' \ + && ret=0 + + case "${state}" in + cmds) + local subcommands + subcommands=( + 'fadd:stage files' + 'fbackup:backup files' + 'fcheckout:checkout file/branch/commit' + 'fedit:edit files' + 'fgrep:grep within tracked files' + 'finit:init/migrate dotbare' + 'flog:interactive log viewer' + 'freset:reset files/commit' + 'fstash:stage management' + 'fstat:toggle stage/unstage of files' + 'funtrack:untrack files' + 'fupgrade:update dotbare' + ) + _describe 'command' subcommands \ + && ret=0 + ;; + options) + case "${line[1]}" in + fadd) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-f --file -d --dir -h --help)'{-f,--file}'[select files from PWD and stage]' \ + '(-d --dir -f --file -h --help)'{-d,--dir}'[select directory from PWD and stage]' \ + && ret=0 + ;; + fbackup) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-s --select -p --path -h --help)'{-s,--select}'[select tracked files to backup]' \ + '(-p --path -s --select -h --help)'{-p,--path}'[sepcify path of files to backup]:filename:_files' \ + '(-m --move -h --help)'{-m,--move}'[use mv cmd instead of cp cmd]' \ + && ret=0 + ;; + fcheckout) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-s --select -b --branch -c --commit -h --help)'{-s,--select}'[select files and then checkout them in selected commits]' \ + '(-b --branch -s --select -c --commit -h --help)'{-b,--branch}'[checkout branch]' \ + '(-c --commit -b --branch -s --select -h --help)'{-c,--commit}'[checkout commit]' \ + '(-y --yes -h --help)'{-y,--yes}'[acknowledge all actions and skip confirmation]' \ + && ret=0 + ;; + fedit) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-m --modified -c --commit -h --help)'{-m,--modified}'[edit modified files]' \ + '(-c --commit -m --modified -h --help)'{-c,--commit}'[edit commits]' \ + && ret=0 + ;; + fgrep) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-f --full -c --col)'{-f,--full}'[include all columns during fzf search, as if using "--col 1"]' \ + '(-f --full -c --col)'{-c,--col}'[specify a column number to start searching in fzf]: :->cols' \ + && ret=0 + ;; + finit) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-u --url -h --help)'{-u,--url}'[migrate remote dotfiles to current system]: :->url' \ + '(-s --submodule -h --help)'{-s,--submodule}'[clone submodules during migration]' \ + '(-y --yes -h --help)'{-y,--yes}'[acknowledge all actions and skip confirmation]' \ + && ret=0 + ;; + flog) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-r --revert -R --reset -e --edit -c --checkout -h --help)'{-r,--revert}'[revert the selected commit and skip action menu]' \ + '(-r --revert -R --reset -e --edit -c --checkout -h --help)'{-R,--reset}'[reset the selected commit and skip action menu]' \ + '(-r --revert -R --reset -e --edit -c --checkout -h --help)'{-e,--edit}'[edit the selected commit and skip action menu]' \ + '(-r --revert -R --reset -e --edit -c --checkout -h --help)'{-c,--checkout}'[checkout the selected commit and skip action menu]' \ + '(-y --yes -h --help)'{-y,--yes}'[acknowledge all actions and skip confirmation]' \ + && ret=0 + ;; + freset) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-c --commit -h --help)'{-c,--commit}'[reset HEAD to certain commit]' \ + '(-S --soft -H --hard -h --help)'{-S,--soft}'[reset commit using --soft flag]' \ + '(-H --hard -S --soft -h --help)'{-H,--hard}'[reset commit using --hard flag]' \ + '(-y --yes -h --help)'{-y,--yes}'[acknowledge all actions and skip confirmation]' \ + && ret=0 + ;; + fstash) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-s --select -d --delete -p --pop -h --help)'{-s,--select}'[list modified files and stash the selected files]' \ + '(-s --select -d --delete -p --pop -h --help)'{-d,--delete}'[list stash and delete the selected stash]' \ + '(-s --select -d --delete -p --pop -h --help)'{-p,--pop}'[use "stash pop" instead of "stash apply"]' \ + && ret=0 + ;; + fstat) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + && ret=0 + ;; + funtrack) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + '(-t --temp -r --resume -h --help)'{-t,--temp}'[temporarily ignore changes of the selected files]' \ + '(-t --temp -r --resume -h --help)'{-r,--resume}'[resume tracking changes of the selected files]' \ + '(-y --yes -h --help)'{-y,--yes}'[acknowledge all actions and skip confirmation]' \ + && ret=0 + ;; + fupgrade) + _arguments \ + '(- : *)'{-h,--help}'[show help information]' \ + && ret=0 + ;; + esac + ;; + esac + + return "${ret}"; +} + +_dotbare "$@" diff --git a/pkg/dotbare.aur b/pkg/dotbare.aur new file mode 100755 index 0000000..fe2a2f9 --- /dev/null +++ b/pkg/dotbare.aur @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=/opt/dotbare +${SCRIPT_DIR}/dotbare "$@" diff --git a/pkg/dotbare.brew b/pkg/dotbare.brew new file mode 100755 index 0000000..51745fd --- /dev/null +++ b/pkg/dotbare.brew @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +SCRIPT_DIR=/usr/local/Cellar/dotbare/1.3.0 +${SCRIPT_DIR}/dotbare "$@" diff --git a/scripts/fcheckout b/scripts/fcheckout index 4584b33..1d572d5 100755 --- a/scripts/fcheckout +++ b/scripts/fcheckout @@ -92,7 +92,7 @@ elif [[ "${action_type}" == "modified" ]]; then # checkout modified file back to version in HEAD while IFS= read -r line; do selected_files+=("${line}") - done < <(get_modified_file 'select a file to checkout version in HEAD') + done < <(get_modified_file 'select files to checkout version in HEAD') [[ "${#selected_files[@]}" -eq 0 ]] && exit 1 [[ -z "${confirm}" ]] && echo "(dryrun) dotbare checkout --" "${selected_files[@]}" [[ -z "${confirm}" ]] && confirm=$(get_confirmation "Confirm?") @@ -102,7 +102,7 @@ elif [[ "${action_type}" == "select" ]]; then # checkout selected files to a selected commit while IFS= read -r line; do selected_files+=("${line}") - done < <(get_git_file 'select a file to checkout') + done < <(get_git_file 'select files to checkout to previous commit') [[ "${#selected_files[@]}" -eq 0 ]] && exit 1 # continue select a commit and then checkout the file back to the selected commit selected_commit=$(get_commit 'select the target commit' "${selected_files[@]}") diff --git a/scripts/fedit b/scripts/fedit index acca636..0db34f4 100755 --- a/scripts/fedit +++ b/scripts/fedit @@ -60,7 +60,7 @@ while [[ "$#" -gt 0 ]]; do done if [[ "${edit_type}" == "commit" ]]; then - selected_commit=$(get_commit "select a commit to rename") + selected_commit=$(get_commit "select a commit to edit") [[ -z "${selected_commit}" ]] && exit 1 git --git-dir="${DOTBARE_DIR}" --work-tree="${DOTBARE_TREE}" rebase -i "${selected_commit}"~ else @@ -68,9 +68,9 @@ else selected_files+=("${line}") done < <( if [[ "${edit_type}" == "modified" ]]; then - get_modified_file "select modified files to edit" + get_modified_file "select files to edit" else - get_git_file "select tracked files to edit" + get_git_file "select files to edit" fi ) [[ "${#selected_files[@]}" -eq 0 ]] && exit 1 diff --git a/scripts/fgrep b/scripts/fgrep index 3eb80a4..b839d23 100755 --- a/scripts/fgrep +++ b/scripts/fgrep @@ -6,8 +6,11 @@ # Globals # mydir: path to this current script # selected_lines: selected lines to edit +# fzf_search_delimiter: the col to start searching in fzf # Arguments # -h|--help: show help message and exit +# -c COL| --col COL: use a different delimiter in fzf search +# -f|--full: include file name in fzf searching, as if using --col 1 set -e set -f @@ -17,18 +20,32 @@ source "${mydir}"/../helper/set_variable.sh source "${mydir}"/../helper/git_query.sh function usage() { - echo -e "Usage: dotbare fgrep [-h] ... + echo -e "Usage: dotbare fgrep [-h] [-c] [-f] ... -Grep words within tracked files and select to edit them through fzf. +Grep words within tracked files and select to edit matches. + +Default: start searching from 3rd column (excluding the file name during search). Optional arguments: - -h, --help\t\tshow this help message and exit." + -h, --help\t\tshow this help message and exit. + -c COL, --col COL\tspecify the column number to start searching. + -f, --full\t\tinclude all column during search, as if using '--col 1'." } selected_lines=() +fzf_search_delimiter=3 while [[ "$#" -gt 0 ]]; do case "$1" in + -c|--col) + fzf_search_delimiter="$2" + shift + shift + ;; + -f|--full) + fzf_search_delimiter=1 + shift + ;; -h|--help) usage exit 0 @@ -52,7 +69,7 @@ while IFS= read -r line; do selected_lines+=("${line}") ;; esac -done < <(grep_lines) +done < <(grep_words "select matches to edit" "${fzf_search_delimiter}") [[ "${#selected_lines[@]}" -eq 0 ]] && exit 1 diff --git a/scripts/freset b/scripts/freset index 1bc5cb6..8c12e53 100755 --- a/scripts/freset +++ b/scripts/freset @@ -79,7 +79,7 @@ while [[ "$#" -gt 0 ]]; do done if [[ "${reset_type}" == "commit" ]]; then - selected_commit=$(get_commit "select the target commit for HEAD") + selected_commit=$(get_commit "select the target commit") [[ -z "${selected_commit}" ]] && exit 1 [[ -z "${confirm}" ]] && confirm=$(get_confirmation "Reset HEAD to ${selected_commit} ${reset_option}?") [[ "${confirm}" != 'y' ]] && exit 1 diff --git a/tests/dotbare.bats b/tests/dotbare.bats index d8b0f3c..62d86ff 100755 --- a/tests/dotbare.bats +++ b/tests/dotbare.bats @@ -34,6 +34,60 @@ no_argument() { "${BATS_TEST_DIRNAME}"/../dotbare } +generic_git_operation_block_finit() { + "${BATS_TEST_DIRNAME}"/../dotbare --git finit +} + +generic_git_operation_block_fbackup() { + "${BATS_TEST_DIRNAME}"/../dotbare --git fbackup +} + +generic_git_operation_block_fupgrade() { + "${BATS_TEST_DIRNAME}"/../dotbare --git fupgrade +} + +generic_git_operation_init() { + export PATH="${BATS_TEST_DIRNAME}:$PATH" + "${BATS_TEST_DIRNAME}"/../dotbare --git init +} + +generic_git_operation_fadd() { + export PATH="${BATS_TEST_DIRNAME}:$PATH" + "${BATS_TEST_DIRNAME}"/../dotbare --git fadd +} + +@test "main generic git fadd" { + run generic_git_operation_fadd + [ "${status}" -eq 0 ] + [[ "${output}" =~ "--git-dir=rev-parse --show-toplevel/.git --work-tree=rev-parse --show-toplevel" ]] + [[ "${output}" =~ "fadd_stage_modified" ]] +} + +@test "main generic git commands" { + run generic_git_operation_init + [ "${status}" -eq 0 ] + [[ "${output}" =~ "--git-dir=rev-parse --show-toplevel/.git --work-tree=rev-parse --show-toplevel" ]] + [[ "${output}" =~ "init" ]] +} + +@test "main generic git flag block finit" { + run generic_git_operation_block_finit + [ "${status}" -eq 1 ] + [ "${output}" = "dotbare finit is not supported when using dotbare as a generic fuzzy git tool" ] +} + +@test "main generic git flag block fbackup" { + run generic_git_operation_block_fbackup + [ "${status}" -eq 1 ] + [ "${output}" = "dotbare fbackup is not supported when using dotbare as a generic fuzzy git tool" ] +} + +@test "main generic git flag block fupgrade" { + run generic_git_operation_block_fupgrade + [ "${status}" -eq 1 ] + [ "${output}" = "dotbare fupgrade is not supported when using dotbare as a generic fuzzy git tool" ] +} + @test "main help" { run help [ "${status}" -eq 0 ] diff --git a/tests/fgrep.bats b/tests/fgrep.bats index 5b5ef81..b1375d4 100755 --- a/tests/fgrep.bats +++ b/tests/fgrep.bats @@ -14,10 +14,32 @@ edit_lines() { bash "${BATS_TEST_DIRNAME}"/../dotbare fgrep } +full_deli() { + export PATH="${BATS_TEST_DIRNAME}:$PATH" + export EDITOR="echo" + bash "${BATS_TEST_DIRNAME}"/../dotbare fgrep --full +} + +option_deli() { + export PATH="${BATS_TEST_DIRNAME}:$PATH" + export EDITOR="echo" + bash "${BATS_TEST_DIRNAME}"/../dotbare fgrep --col 2 +} + +@test "fgrep option delimiter" { + run option_deli + [[ "${output}" =~ "--nth 2.. --header=select matches to edit" ]] +} + +@test "fgrep full delimiter" { + run full_deli + [[ "${output}" =~ "--nth 1.. --header=select matches to edit" ]] +} + @test "fgrep help" { run help [ "${status}" -eq 0 ] - [ "${lines[0]}" = "Usage: dotbare fgrep [-h] ..." ] + [ "${lines[0]}" = "Usage: dotbare fgrep [-h] [-c] [-f] ..." ] } @test "fgrep invalid option" { @@ -28,5 +50,5 @@ edit_lines() { @test "fgrep edit lines" { run edit_lines - [[ "${output}" =~ "fgrep_lines" ]] + [[ "${output}" =~ "--nth 3.. --header=select matches to edit" ]] } diff --git a/tests/fzf b/tests/fzf index afceaf7..484a617 100755 --- a/tests/fzf +++ b/tests/fzf @@ -21,22 +21,22 @@ elif [[ "$*" =~ "--no-multi --header=select a branch to checkout" ]]; then elif [[ "$*" =~ "--header=select a commit to checkout" ]] && [[ "$*" =~ "show --color" ]]; then # dotbare fcheckout --c -- "./fcheckout.bats" @test "fcheckout commit" echo "fcheckout_commit" -elif [[ "$*" =~ "--header=select a file to checkout version in HEAD" ]] && [[ "$*" =~ "diff HEAD --color=always" ]]; then +elif [[ "$*" =~ "--header=select files to checkout version in HEAD" ]] && [[ "$*" =~ "diff HEAD --color=always" ]]; then # dotbare fcheckout -y -- "./fcheckout.bats" @test "fcheckout modified" echo "-- fcheckout_modified" elif [[ "$*" =~ '--header=select the target commit' ]] && [[ "$*" =~ "diff --color" ]]; then # dotbare fcheckout --yes -s -- "./fcheckout.bats" @test "fcheckout select" echo "fcheckout_select_commitdiff" -elif [[ "$*" =~ '--header=select a file to checkout' ]] && [[ "$*" =~ "preview.sh ${DOTBARE_TREE}/{}" ]]; then +elif [[ "$*" =~ '--header=select files to checkout' ]] && [[ "$*" =~ "preview.sh ${DOTBARE_TREE}/{}" ]]; then # dotbare fcheckout --yes -s -- "./fcheckout.bats" @test "fcheckout select" echo "fcheckout_select_gitfile" -elif [[ "$*" =~ "--header=select a commit to rename --no-multi" ]] && [[ "$*" =~ "show --color=always" ]]; then +elif [[ "$*" =~ "--header=select a commit to edit --no-multi" ]] && [[ "$*" =~ "show --color=always" ]]; then # dotbare fedit --commit -- "./fedit.bats" @test "fedit edit commits" echo "fedit_commits" -elif [[ "$*" =~ "--header=select tracked files to edit" ]]; then +elif [[ "$*" =~ "--header=select files to edit" ]]; then # dotbare fedit -- "./fedit.bats" @test "fedit edit files" echo "fedit_files" -elif [[ "$*" =~ '--header=select the target commit for HEAD --no-multi' ]] && [[ "$*" =~ "show --color" ]]; then +elif [[ "$*" =~ '--header=select the target commit --no-multi' ]] && [[ "$*" =~ "show --color" ]]; then # dotbare freset --commit -y -- "./freset.bats" @test "freset select commit" echo "freset_commit" elif [[ "$*" =~ "--header=select files to unstage" ]] && [[ "$*" =~ "diff HEAD --color=always" ]]; then @@ -60,9 +60,6 @@ elif [[ "$*" =~ '--header=select stash to delete' ]] && [[ "$*" =~ "show -p __ - elif [[ "$*" =~ '--header=select stash to apply' ]] && [[ "$*" =~ "show -p __ --color=always" ]]; then # dotbare fstash -- "./fstash.bats" @test "fstash apply stash" echo "fstash_apply" -elif [[ "$*" =~ '--delimiter : --nth 3..' ]]; then - # dotbare fgrep -- "./fgrep.bats" @test "fgrep edit lines" - echo "fgrep_lines" else echo "$@" fi diff --git a/tests/shellcheck.sh b/tests/shellcheck.sh index cbced7a..352114f 100755 --- a/tests/shellcheck.sh +++ b/tests/shellcheck.sh @@ -22,7 +22,9 @@ done < <( shellcheck -e SC1090 "${scripts[@]}" shellcheck -e SC1090 --shell=bash "dotbare.plugin.bash" +shellcheck --shell=bash "pkg/completion/bash/dotbare" # \shellcheck does not have zsh support yet, hence using bash for now shellcheck -e SC2034 --shell=bash "dotbare.plugin.zsh" +shellcheck -e SC2034 --shell=bash "pkg/completion/zsh/_dotbare" exit $?