From d460debc6f2747699a54b7ea0e25f4ec69bf1b3d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 03:03:58 +0200 Subject: Adding upstream version 1:2.13.0. Signed-off-by: Daniel Baumann --- .github/release-please-manifest.json | 2 +- .github/workflows/ci.yaml | 14 +- .gitignore | 4 + .pre-commit-config.yaml | 8 +- .typos.toml | 18 ++ CHANGELOG.md | 31 ++++ bash_completion | 4 +- completions/Makefile.am | 1 + completions/_rg | 8 + completions/curl | 5 +- completions/ip | 189 +++++++++++++++++---- completions/tar | 2 +- completions/xmllint | 2 +- completions/xmlwf | 2 +- configure.ac | 2 +- doc/Makefile.am | 1 + doc/api-and-naming.md | 14 +- pyproject.toml | 8 +- test/docker/entrypoint.sh | 1 + test/fallback/completions/rg | 1 + test/fixtures/_comp_load/bin/cmd1 | 1 - test/fixtures/_comp_load/bin/cmd2 | 1 - test/requirements-dev.txt | 2 +- test/t/conftest.py | 12 +- test/t/test_curl.py | 4 + test/t/test_ip.py | 34 ++++ test/t/test_java.py | 2 +- test/t/test_perldoc.py | 6 +- test/t/unit/Makefile.am | 3 + test/t/unit/test_unit_compgen.py | 5 +- .../unit/test_unit_compgen_available_interfaces.py | 25 +++ test/t/unit/test_unit_load.py | 130 ++++++++++++++ test/t/unit/test_unit_load_completion.py | 94 ---------- version.txt | 2 +- 34 files changed, 471 insertions(+), 167 deletions(-) create mode 100644 completions/_rg create mode 120000 test/fallback/completions/rg delete mode 120000 test/fixtures/_comp_load/bin/cmd1 delete mode 120000 test/fixtures/_comp_load/bin/cmd2 create mode 100644 test/t/unit/test_unit_compgen_available_interfaces.py create mode 100644 test/t/unit/test_unit_load.py delete mode 100644 test/t/unit/test_unit_load_completion.py diff --git a/.github/release-please-manifest.json b/.github/release-please-manifest.json index 95b1793..6f6cd64 100644 --- a/.github/release-please-manifest.json +++ b/.github/release-please-manifest.json @@ -1 +1 @@ -{".":"2.12.0"} +{".":"2.13.0"} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 27480fa..60f9adb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -82,13 +82,17 @@ jobs: env: DIST: ${{matrix.dist}} NETWORK: ${{matrix.network}} + - uses: actions/upload-artifact@v4 + with: + path: | + bash-completion-*.tar.xz + sha256sums.txt + if: matrix.dist == 'alpine' - name: Upload release assets run: | set -x gh release upload ${{steps.release.outputs.tag_name}} \ - bash-completion-$(cat version.txt).tar.xz + bash-completion-$(cat version.txt).tar.xz sha256sums.txt + env: + GH_TOKEN: ${{github.token}} if: steps.release.outputs.release_created - - uses: actions/upload-artifact@v4 - with: - path: bash-completion-*.tar.xz - if: matrix.dist == 'alpine' diff --git a/.gitignore b/.gitignore index ab43e5e..c6b4b43 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,10 @@ pytestdebug.log /bash-completion-config.cmake /bash-completion.pc /bash_completion.sh +/.mise.local.toml +/.mise.*.local.toml +/sha256sums.txt +/venv/ # Files generated by autotools Makefile diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ba2382..52eea9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: exclude: ^completions/(\.gitignore|Makefile.*)$ - repo: https://github.com/shellcheck-py/shellcheck-py - rev: v0.9.0.6 + rev: v0.10.0.1 hooks: - id: shellcheck args: [-f, gcc] @@ -43,7 +43,7 @@ repos: pass_filenames: false - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.2 + rev: v0.3.5 hooks: - id: ruff types: [text] @@ -83,7 +83,7 @@ repos: files: ^(helpers/perl|.+\.p[ml])$ - repo: https://github.com/jackdewinter/pymarkdown - rev: v0.9.17 + rev: v0.9.18 hooks: - id: pymarkdown entry: pymarkdown @@ -98,7 +98,7 @@ repos: - id: check-case-conflict - repo: https://github.com/crate-ci/typos - rev: v1.18.2 + rev: v1.20.3 hooks: - id: typos exclude: ^(CHANGELOG\.md|test/(test-cmd-list\.txt|fixtures/.+))$ diff --git a/.typos.toml b/.typos.toml index 327d012..c789e40 100644 --- a/.typos.toml +++ b/.typos.toml @@ -18,8 +18,14 @@ clea = "clea" clien = "clien" # completions/openssl ede = "ede" +# completions/patch +fior = "fior" # completions/make fo = "fo" +# completions/.gitignore, completions/Makefile.am, completions/_hexdump, +# completions/gnome-screenshot, completions/mii-diag, completions/mii-tool, +# completions/qemu, +hd = "hd" # test/t/test_ccache.py hel = "hel" # completions/bts @@ -30,16 +36,28 @@ iif = "iif" inout = "inout" # test/t/unit/test_unit_expand_glob.py ket = "ket" +# completions/hcitool, completions/ip +lst = "lst" # completions/tshark, test/t/test_screen.py nd = "nd" +# bash_completion +odf = "odf" +# completions/ip +oif = "oif" # completions/mplayer oly = "oly" # test/t/unit/test_unit_find_unique_completion_pair.py ot = "ot" # completions/modinfo parm = "parm" +# bash_completion, completions/eog, completions/gnome-screenshot, +# test/t/test_nmap.py +pn = "pn" # completions/wget referer = "referer" +# completions/_mount.linux, completions/tune2fs, test/t/test_curl.py, +# test/t/unit/test_unit_find_unique_completion_pair.py +ro = "ro" # completions/ps ser = "ser" diff --git a/CHANGELOG.md b/CHANGELOG.md index 933aec8..711ab85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -299,6 +299,37 @@ * pre-commit: anchor exclude patterns ([cc697a6](https://www.github.com/scop/bash-completion/commit/cc697a646c1bd1100890abd367bb8d0274a42275)) * extra/make-changelog: check and output usage message ([cf90b29](https://www.github.com/scop/bash-completion/commit/cf90b297696cee57650f4fb6c29cebe872f773a7)) +## [2.13.0](https://github.com/scop/bash-completion/compare/2.12.0...2.13.0) (2024-04-03) + + +### Features + +* **curl:** Complete protocols for --proto-default ([7051379](https://github.com/scop/bash-completion/commit/7051379e448147407c3fe43c89872dafb76ebb27)) +* **ip:** Add completion for netconf subcommand ([03a10ff](https://github.com/scop/bash-completion/commit/03a10ff63226782e61dae4407138c3240ff0c7c2)) +* **ip:** Complete commands for netns exec ([1f03796](https://github.com/scop/bash-completion/commit/1f03796cd930ddad6207d46814da820674f16edc)) +* **ip:** Complete help for unknown subcommands ([21f7e32](https://github.com/scop/bash-completion/commit/21f7e32f9009c2064d1659668425b6b6ccb537bd)) +* **ip:** Complete ip link property ([efa663c](https://github.com/scop/bash-completion/commit/efa663cd0dd63d54fd2d2987ee66fb954ccf4a86)) +* **ip:** Complete link types for address show ([ca5ea03](https://github.com/scop/bash-completion/commit/ca5ea037e2ec1b0b5ec4d31295df88125d51b43e)) +* **ip:** Complete neigh show and flush ([c7c3c03](https://github.com/scop/bash-completion/commit/c7c3c039bf5a462ea577e8fcc92ebd94d6afad49)) +* **ip:** Complete stats subcommand ([cd73e8c](https://github.com/scop/bash-completion/commit/cd73e8c1689e3e014c4a75d5101ee3d932013120)) +* **ip:** Create function to get link types ([8e60245](https://github.com/scop/bash-completion/commit/8e60245c7531e1615dc96b032035bb4f59972f4a)) +* **rg:** add fallback 3rd party completion loader ([7e4cc2f](https://github.com/scop/bash-completion/commit/7e4cc2fb199f1c88bd9a358a157fe06327fc2b28)) +* **xmllint,xmlwf:** also suggest *.rss files ([a89cde2](https://github.com/scop/bash-completion/commit/a89cde2216b1634521b4a264b2dbc5cda7522061)) + + +### Bug Fixes + +* **available_interfaces:** fix regression of unwanted trailing colons ([c2f83e0](https://github.com/scop/bash-completion/commit/c2f83e0436208ef2bfa9c762bc28ff6374ba0c73)) +* **ip:** Complete addrlabel add/del properties ([ea07616](https://github.com/scop/bash-completion/commit/ea076166e9a5cce9d22a27e63f95bbf00be9b894)) +* **ip:** Complete ip delete with type correctly ([f3a9be3](https://github.com/scop/bash-completion/commit/f3a9be3e2f6eaf0d94bb66220fd02cf0e3c76481)) +* **ip:** Complete more variations of subcommands ([c8920c5](https://github.com/scop/bash-completion/commit/c8920c57f83199a14230485cc44b426f028acafb)) +* **ip:** Complete netns attach subcommand ([bfb1de6](https://github.com/scop/bash-completion/commit/bfb1de64a53d1eba749c9921cea3809460cd2319)) +* **ip:** Complete only relevant addrlabel subcmds ([05147f1](https://github.com/scop/bash-completion/commit/05147f19d3d04040fa8dacbd9e1705bfc1432073)) +* **ip:** Keep completing after -netns name ([1a5df4f](https://github.com/scop/bash-completion/commit/1a5df4fe22eb374424b18e7db27b8446e919f4b2)) +* **ip:** Quote all instantiation of ip as "$1" ([ef25163](https://github.com/scop/bash-completion/commit/ef25163e6bd9095e528b57d44cb31d32f0321bb9)) +* **ip:** Quote network namespace names ([216734b](https://github.com/scop/bash-completion/commit/216734bed7ba02655128bf1dbc2f184420ad69ef)) +* **Makefile:** include api-and-naming.md in dist ([fdd8048](https://github.com/scop/bash-completion/commit/fdd80487ba6944e639baf32ba991f8665840728d)) + ## [2.12.0](https://github.com/scop/bash-completion/compare/v2.11.0...2.12.0) (2024-02-21) diff --git a/bash_completion b/bash_completion index e245cc5..42a5dca 100644 --- a/bash_completion +++ b/bash_completion @@ -25,7 +25,7 @@ BASH_COMPLETION_VERSINFO=( 2 # x-release-please-major - 12 # x-release-please-minor + 13 # x-release-please-minor 0 # x-release-please-patch ) @@ -1737,7 +1737,7 @@ _comp_compgen_available_interfaces() fi } 2>/dev/null | _comp_awk \ '/^[^ \t]/ { if ($1 ~ /^[0-9]+:/) { print $2 } else { print $1 } }')" && - _comp_compgen -U generated set "${generated[@]}" + _comp_compgen -U generated set "${generated[@]%%[[:punct:]]*}" } # Echo number of CPUs, falling back to 1 on failure. diff --git a/completions/Makefile.am b/completions/Makefile.am index 8f11291..ed1f215 100644 --- a/completions/Makefile.am +++ b/completions/Makefile.am @@ -377,6 +377,7 @@ bashcomp_DATA = 2to3 \ _reptyr \ resolvconf \ _rfkill \ + _rg \ ri \ rmlist \ rmmod \ diff --git a/completions/_rg b/completions/_rg new file mode 100644 index 0000000..4129371 --- /dev/null +++ b/completions/_rg @@ -0,0 +1,8 @@ +# 3rd party completion loader for commands emitting -*- shell-script -*- +# their completion using "$cmd --generate complete-bash". +# +# This serves as a fallback in case the completion is not installed otherwise. + +eval -- "$("$1" --generate complete-bash 2>/dev/null)" + +# ex: filetype=sh diff --git a/completions/curl b/completions/curl index 6cbbca4..55865c0 100644 --- a/completions/curl +++ b/completions/curl @@ -22,7 +22,7 @@ _comp_cmd_curl() --happy-eyeballs-timeout-ms | --hostpubmd5 | --keepalive-time | \ --limit-rate | --local-port | --login-options | --mail-auth | \ --mail-from | --mail-rcpt | --max-filesize | --max-redirs | \ - --max-time | --pass | --proto | --proto-default | --proto-redir | \ + --max-time | --pass | --proto | --proto-redir | \ --proxy-ciphers | --proxy-pass | --proxy-service-name | \ --proxy-tls13-ciphers | --proxy-tlspassword | --proxy-tlsuser | \ --proxy-user | --proxy1.0 | --quote | --range | --referer | \ @@ -127,6 +127,9 @@ _comp_cmd_curl() _comp_compgen_known_hosts -- "$cur" return ;; + --proto-default) + _comp_compgen_split "$("$1" --version 2>/dev/null | command sed -e '/Protocols/!d' -e 's/Protocols://')" + ;; --pubkey) _comp_compgen -x ssh identityfile pub return diff --git a/completions/ip b/completions/ip index 511f206..09bec5b 100644 --- a/completions/ip +++ b/completions/ip @@ -8,10 +8,45 @@ _comp_cmd_ip__iproute2_etc() _comp_cmd_ip__netns() { - _comp_compgen_split -- "$( + local unquoted + _comp_split -l unquoted "$( { ${1-ip} -c=never netns list 2>/dev/null || ${1-ip} netns list - } | _comp_awk '{print $1}' + } | command sed -e 's/ (.*//' + )" + # namespace names can have spaces, so we quote all of them if needed + local ns quoted=() + for ns in "${unquoted[@]}"; do + local namespace + printf -v namespace '%q' "$ns" + quoted+=("$namespace") + done + ((${#quoted[@]})) && _comp_compgen -- -W '"${quoted[@]}"' +} + +_comp_cmd_ip__link_types() +{ + _comp_compgen_split -- "$( + { + ${1-ip} -c=never link help || ${1-ip} link help + } 2>&1 | command sed -e \ + '/TYPE := /,/}/!d' -e \ + 's/.*{//' -e \ + 's/}.*//' -e \ + 's/|/ /g' + )" +} + +_comp_cmd_ip__neigh_states() +{ + _comp_compgen_split -- "$( + { + ${1-ip} -c=never neigh help || ${1-ip} neigh help + } 2>&1 | command sed -e \ + '/STATE := /,/}/!d' -e \ + 's/.*{//' -e \ + 's/}.*//' -e \ + 's/|/ /g' )" } @@ -47,7 +82,7 @@ _comp_cmd_ip() [[ ${words[subcword]} == -b?(atch) ]] && return [[ $has_cmd ]] && subcmd=${words[subcword]} && break [[ ${words[subcword]} != -* && - ${words[subcword - 1]} != -@(f?(amily)|rc?(vbuf)) ]] && + ${words[subcword - 1]} != -@(f?(amily)|rc?(vbuf)|n?(etns)) ]] && cmd=${words[subcword]} has_cmd=set done @@ -66,7 +101,7 @@ _comp_cmd_ip() *) _comp_compgen_split -- "help $( { - $1 -c=never help || $1 help + "$1" -c=never help || "$1" help } 2>&1 | command sed -e \ '/OBJECT := /,/}/!d' -e \ 's/.*{//' -e \ @@ -87,19 +122,12 @@ _comp_cmd_ip() # TODO ;; delete) - case $((cword - subcword)) in - 1) - _comp_compgen_available_interfaces - ;; - 2) - _comp_compgen -- -W 'type' - ;; - 3) - [[ $prev == type ]] && - _comp_compgen -- -W 'vlan veth vcan dummy ifb - macvlan can' - ;; - esac + if [[ $prev == type ]]; then + _comp_cmd_ip__link_types "$1" + else + _comp_compgen_available_interfaces + _comp_compgen -a -- -W 'type' + fi ;; set) if ((cword - subcword == 1)); then @@ -132,9 +160,20 @@ _comp_cmd_ip() _comp_cmd_ip__iproute2_etc group fi ;; + property) + if ((cword - 1 == subcword)); then + _comp_compgen -- -W 'add del' + elif [[ $prev == dev ]]; then + _comp_compgen_available_interfaces + elif [[ $prev == altname ]]; then + return + else + _comp_compgen -- -W 'dev altname' + fi + ;; *) ((cword == subcword)) && - _comp_compgen -- -W 'help add delete set show' + _comp_compgen -- -W 'help add delete set show property' ;; esac ;; @@ -164,11 +203,13 @@ _comp_cmd_ip() _comp_compgen_available_interfaces _comp_compgen -a -- -W 'dev scope to label dynamic permanent tentative deprecated dadfailed temporary - primary secondary up' + primary secondary up type' elif [[ $prev == dev ]]; then _comp_compgen_available_interfaces elif [[ $prev == scope ]]; then _comp_cmd_ip__iproute2_etc rt_scopes + elif [[ $prev == type ]]; then + _comp_cmd_ip__link_types "$1" fi ;; *) @@ -181,13 +222,16 @@ _comp_cmd_ip() addrlabel) case $subcmd in - list | add | del | flush) + add | del) if [[ $prev == dev ]]; then _comp_compgen_available_interfaces + elif [[ $prev == prefix || $prev == label ]]; then + : # TODO - Is there a way to complete these? else - : # TODO + _comp_compgen -- -W 'dev prefix label' fi ;; + list | flush | help) ;; *) ((cword == subcword)) && _comp_compgen -- -W 'help list add del flush' @@ -211,14 +255,14 @@ _comp_cmd_ip() if [[ $prev == via ]]; then _comp_compgen_split -- "$( { - $1 -c=never r 2>/dev/null || $1 r + "$1" -c=never r 2>/dev/null || "$1" r } | command sed -ne \ 's/.*via \([0-9.]*\).*/\1/p' )" elif [[ $prev == "$subcmd" ]]; then _comp_compgen_split -- "table default $( { - $1 -c=never r 2>/dev/null || $1 r + "$1" -c=never r 2>/dev/null || "$1" r } | cut -d ' ' -f 1 )" elif [[ $prev == dev ]]; then @@ -274,14 +318,62 @@ _comp_cmd_ip() ;; esac ;; + stats) + case "$subcmd" in + show) + if [[ $prev == dev ]]; then + _comp_compgen_available_interfaces + elif [[ $prev == group ]]; then + _comp_compgen -- -W "$( + # stats command was added after color, should always have it + "$1" -c=never stats help 2>&1 | + command sed -e \ + '/^GROUP := /,/}/!d' -e \ + 's/^GROUP := //g' -e \ + '/:=/d' -e \ + 's/[{}|]//g' + )" + elif [[ $prev == subgroup || $prev == suite ]]; then + : # TODO: complete subgroup and suite + else + _comp_compgen -- -W 'dev group subgroup suite' + fi + ;; + set) + if [[ $prev == dev ]]; then + _comp_compgen_available_interfaces + elif [[ $prev == l3_stats ]]; then + _comp_compgen -- -W 'on off' + else + _comp_compgen -- -W 'dev l3_stats' + fi + ;; + *) + _comp_compgen -- -W 'show set help' + ;; + esac + ;; - neigh) + n | neigh | neighbor | neighbour) case $subcmd in add | del | change | replace) # TODO ;; show | flush) - # TODO + case "$prev" in + nud) + _comp_cmd_ip__neigh_states "$1" + ;; + dev) + _comp_compgen_available_interfaces + ;; + nomaster | proxy | to | vrf) # TODO - Maybe we can complete vrf here? + : + ;; + *) + _comp_compgen -- -W 'proxy to nud vrf dev nomaster' + ;; + esac ;; *) ((cword == subcword)) && @@ -306,7 +398,7 @@ _comp_cmd_ip() esac ;; - tunnel) + tun | tunnel) case $subcmd in show) ;; @@ -369,24 +461,57 @@ _comp_cmd_ip() esac ;; - netns) + net | netns) case $subcmd in list | monitor) ;; add | identify | list-id) # TODO ;; - delete | exec | pids | set) + delete | pids | set) [[ $prev == "$subcmd" ]] && _comp_cmd_ip__netns "$1" ;; + exec) + local all_offset=0 i + for ((i = 1; i <= cword; i++)); do + case ${words[i]} in + -a | -all) + all_offset=1 + break + ;; + esac + done + if [[ $prev == "$subcmd" && $all_offset != 1 ]]; then + _comp_cmd_ip__netns "$1" + else + local offset + offset="$((subcword + 2 - all_offset))" + _comp_command_offset "$offset" + fi + ;; *) ((cword == subcword)) && - _comp_compgen -- -W 'help add delete exec identify list + _comp_compgen -- -W 'help add attach delete exec identify list list-id monitor pids set' ;; esac ;; + netconf) + case $subcmd in + show) + if ((cword == subcword + 1)); then + _comp_compgen -- -W 'dev' + elif [[ $prev == dev ]]; then + _comp_compgen_available_interfaces + fi + ;; + *) + ((cword == subcword)) && _comp_compgen -- -W 'help show' + ;; + esac + ;; + xfrm) case $subcmd in state | policy | monitor) @@ -394,10 +519,14 @@ _comp_cmd_ip() ;; *) ((cword == subcword)) && - _comp_compgen -- -W 'state policy monitor' + _comp_compgen -- -W 'help state policy monitor' ;; esac ;; + help) ;; + *) + _comp_compgen -- -W 'help' + ;; esac } && complete -F _comp_cmd_ip ip diff --git a/completions/tar b/completions/tar index 83a4073..b11bd9b 100644 --- a/completions/tar +++ b/completions/tar @@ -31,7 +31,7 @@ # FIXME: timeout on tarball listing # FIXME: cache 'tar --help' parsing results into global variables # FIXME: at least 'tar -' should show some helping text (apart from just -# pure option advices) +# pure option advice) # FIXME: short option completion should be more intuitive # - verbose mode option should be advised multiple times # - mode option should be advised only once diff --git a/completions/xmllint b/completions/xmllint index 57445bb..e424840 100644 --- a/completions/xmllint +++ b/completions/xmllint @@ -46,7 +46,7 @@ _comp_cmd_xmllint() return fi - _comp_compgen_filedir '@(*ml|htm|svg?(z)|xs[dl]|rng|wsdl|jnlp|tld|dbk|docbook|page)?(.gz)' + _comp_compgen_filedir '@(*ml|htm|svg?(z)|xs[dl]|rng|wsdl|jnlp|tld|dbk|docbook|page|rss)?(.gz)' } && complete -F _comp_cmd_xmllint xmllint diff --git a/completions/xmlwf b/completions/xmlwf index d047ee1..201734f 100644 --- a/completions/xmlwf +++ b/completions/xmlwf @@ -24,7 +24,7 @@ _comp_cmd_xmlwf() return fi - _comp_compgen_filedir '@(*ml|htm|svg|xs[dl]|rng|wsdl|jnlp|tld|dbk|docbook|page)' + _comp_compgen_filedir '@(*ml|htm|svg|xs[dl]|rng|wsdl|jnlp|tld|dbk|docbook|page|rss)' } && complete -F _comp_cmd_xmlwf xmlwf diff --git a/configure.ac b/configure.ac index 83a6f12..649ee3c 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.60]) -AC_INIT([bash-completion], [2.12.0]) dnl x-release-please-version +AC_INIT([bash-completion], [2.13.0]) dnl x-release-please-version dnl tar-pax for portable UTF-8 handling AM_INIT_AUTOMAKE([ foreign dist-xz no-dist-gzip tar-pax -Wall -Wno-portability -Werror diff --git a/doc/Makefile.am b/doc/Makefile.am index 8bac9e8..775ff4b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -1,4 +1,5 @@ EXTRA_DIST = \ + api-and-naming.md \ configuration.md \ styleguide.md \ testing.md diff --git a/doc/api-and-naming.md b/doc/api-and-naming.md index 59b2c36..e7f4ee6 100644 --- a/doc/api-and-naming.md +++ b/doc/api-and-naming.md @@ -13,8 +13,8 @@ specific names. `local`izing output variables before invoking a function that populates them is the caller's responsibility. Note that if calling multiple functions that assign output to the same variable during one completion function run, each result should be copied to another variable between the -calls to avoid it possibly being overwritten and lost on the next call. Also, -the variables should also be ensured to be clear before each call that +calls to avoid it possibly being overwritten and lost on the next call. +The variables should also be ensured to be clear before each call that references the value, variable name, or their existence, typically by `unset -v`ing them when multiple such calls are used, to avoid them interfering with each other. @@ -24,8 +24,8 @@ unconventional, but this choice of the name is intended to be consistent with the value substitutions `${| func; }`, which is originally supported by mksh and will be supported by Bash >= 5.3. The value substitutions are replaced by the contents of the output variable `REPLY` set by `func`. Although we cannot -currently assume Bash 5.3 in the codebase, but we can switch to the value -substitutions at the point Bash <= 5.2 disappear from the market. +currently assume Bash 5.3 in the codebase, we can switch to the value +substitutions at the point Bash <= 5.2 disappears from the market. Everything in fallback completion files (ones starting with an underscore) is considered private and is to be named accordingly. Fallback files are not @@ -164,9 +164,9 @@ append the results to the target variable, use `_comp_compgen_split -- "$(cmd A generator function should replace the existing content of the variable by default. When the appending behavior is favored, the caller should specify it -through `_comp_compgen -a NAME`. The generator function do not need to process -it because internal `_comp_compgen` calls automatically reflects the option -`-a` specified to the outer calls of `_comp_compgen`. +through `_comp_compgen -a NAME`. The generator function does not need to +process it because internal `_comp_compgen` calls automatically reflect the +option `-a` specified to the outer calls of `_comp_compgen`. The exit status is implementation-defined. diff --git a/pyproject.toml b/pyproject.toml index 83d0112..12d45e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.ruff] line-length = 79 target-version = "py37" -lint.select = ["E", "F", "B"] +lint.select = ["E", "F", "B", "I"] lint.ignore = [ # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules # (keep order of ignores here same as ^there for maintainability) @@ -21,8 +21,6 @@ lint.ignore = [ "ISC002", "E501", ] +lint.isort.known-first-party = ["conftest"] +lint.isort.known-third-party = ["pexpect", "pytest"] fix = true - -[tool.ruff.lint.isort] -known-first-party = ["conftest"] -known-third-party = ["pexpect", "pytest"] diff --git a/test/docker/entrypoint.sh b/test/docker/entrypoint.sh index c647782..88ba5ad 100755 --- a/test/docker/entrypoint.sh +++ b/test/docker/entrypoint.sh @@ -20,3 +20,4 @@ make -j xvfb-run make distcheck \ PYTESTFLAGS="${PYTESTFLAGS---verbose -p no:cacheprovider --numprocesses=auto --dist=loadfile}" cp -p bash-completion-*.tar.* "$oldpwd/" +sha256sum bash-completion-*.tar.* >"$oldpwd/sha256sums.txt" diff --git a/test/fallback/completions/rg b/test/fallback/completions/rg new file mode 120000 index 0000000..cb88b04 --- /dev/null +++ b/test/fallback/completions/rg @@ -0,0 +1 @@ +../../../completions/_rg \ No newline at end of file diff --git a/test/fixtures/_comp_load/bin/cmd1 b/test/fixtures/_comp_load/bin/cmd1 deleted file mode 120000 index 5e6359b..0000000 --- a/test/fixtures/_comp_load/bin/cmd1 +++ /dev/null @@ -1 +0,0 @@ -../prefix1/bin/cmd1 \ No newline at end of file diff --git a/test/fixtures/_comp_load/bin/cmd2 b/test/fixtures/_comp_load/bin/cmd2 deleted file mode 120000 index 933265d..0000000 --- a/test/fixtures/_comp_load/bin/cmd2 +++ /dev/null @@ -1 +0,0 @@ -../prefix1/sbin/cmd2 \ No newline at end of file diff --git a/test/requirements-dev.txt b/test/requirements-dev.txt index 7051ab7..68655c4 100644 --- a/test/requirements-dev.txt +++ b/test/requirements-dev.txt @@ -3,4 +3,4 @@ -r requirements.txt mypy==1.8.0 -ruff==0.2.2 +ruff==0.3.5 diff --git a/test/t/conftest.py b/test/t/conftest.py index 874ef1c..cb279cc 100644 --- a/test/t/conftest.py +++ b/test/t/conftest.py @@ -601,9 +601,9 @@ class bash_env_saved: def _unprotect_variable(self, varname: str): if varname not in self.saved_variables: - self.saved_variables[ - varname - ] = bash_env_saved.saved_state.ChangesDetected + self.saved_variables[varname] = ( + bash_env_saved.saved_state.ChangesDetected + ) self._copy_variable( varname, "%s_OLDVAR_%s" % (self.prefix, varname) ) @@ -695,9 +695,9 @@ class bash_env_saved: def save_variable(self, varname: str): self._unprotect_variable(varname) - self.saved_variables[ - varname - ] = bash_env_saved.saved_state.ChangesIgnored + self.saved_variables[varname] = ( + bash_env_saved.saved_state.ChangesIgnored + ) # TODO: We may restore the "export" attribute as well though it is # not currently tested in "diff_env" diff --git a/test/t/test_curl.py b/test/t/test_curl.py index 07050e9..2b86997 100644 --- a/test/t/test_curl.py +++ b/test/t/test_curl.py @@ -27,6 +27,10 @@ class TestCurl: assert completion == "d/" assert not completion.endswith(" ") + @pytest.mark.complete("curl --proto-default ", require_cmd=True) + def test_proto_default(self, completion): + assert completion + @pytest.mark.complete("curl --dont-fail-in-unset-mode") def test_unknown_option(self, completion): # Just see that it does not error out diff --git a/test/t/test_ip.py b/test/t/test_ip.py index 0be088c..552015d 100644 --- a/test/t/test_ip.py +++ b/test/t/test_ip.py @@ -14,6 +14,23 @@ class TestIp: def test_r_r(self, completion): assert completion + @pytest.mark.complete( + "ip stats show group ", + require_cmd=True, + skipif="ip stats help 2>/dev/null; (( $? != 255 ))", + ) + def test_stats(self, completion): + # "link" was one of the first groups added, should always be there + assert "link" in completion + + @pytest.mark.complete( + "ip neigh show nud ", + require_cmd=True, + skipif="ip neigh help 2>&1 | grep 'STATE :=' > /dev/null; (( $? != 0 ))", + ) + def test_neigh_state(self, completion): + assert "stale" in completion + @pytest.mark.complete( "ip monitor ", require_cmd=True, @@ -23,6 +40,23 @@ class TestIp: assert "neigh" in completion assert "all" in completion + @pytest.mark.complete("ip netconf ") + def test_netconf(self, completion): + assert "show" in completion + + @pytest.mark.complete("ip link property add ") + def test_link_property(self, completion): + assert "altname" in completion + assert "dev" in completion + + @pytest.mark.complete( + "ip addr show type ", + require_cmd=True, + skipif="ip link help 2>/dev/null; (( $? != 255 ))", + ) + def test_addr_type(self, completion): + assert "bridge" in completion + @pytest.mark.complete("ip -", require_cmd=True) def test_options(self, completion): assert "-family" in completion diff --git a/test/t/test_java.py b/test/t/test_java.py index 03f1520..407438b 100644 --- a/test/t/test_java.py +++ b/test/t/test_java.py @@ -1,6 +1,6 @@ import pytest -from conftest import is_bash_type, assert_bash_exec, bash_env_saved +from conftest import assert_bash_exec, bash_env_saved, is_bash_type @pytest.mark.bashcomp( diff --git a/test/t/test_perldoc.py b/test/t/test_perldoc.py index 282f824..a5b36d7 100644 --- a/test/t/test_perldoc.py +++ b/test/t/test_perldoc.py @@ -9,7 +9,11 @@ class TestPerldoc: assert "fixtures/" not in completion # Our fixtures/ dir assert not [x for x in completion if "File::File::" in x] - @pytest.mark.complete("perldoc -", require_cmd=True) + @pytest.mark.complete( + "perldoc -", + require_cmd=True, + skipif="! perldoc -V &>/dev/null", + ) def test_2(self, completion): assert completion diff --git a/test/t/unit/Makefile.am b/test/t/unit/Makefile.am index 54722de..5a1eb47 100644 --- a/test/t/unit/Makefile.am +++ b/test/t/unit/Makefile.am @@ -2,7 +2,9 @@ EXTRA_DIST = \ test_unit_abspath.py \ test_unit_command_offset.py \ test_unit_compgen.py \ + test_unit_compgen_available_interfaces.py \ test_unit_compgen_commands.py \ + test_unit_compgen_split.py \ test_unit_count_args.py \ test_unit_delimited.py \ test_unit_deprecate_func.py \ @@ -18,6 +20,7 @@ EXTRA_DIST = \ test_unit_initialize.py \ test_unit_ip_addresses.py \ test_unit_known_hosts.py \ + test_unit_load.py \ test_unit_longopt.py \ test_unit_looks_like_path.py \ test_unit_parse_help.py \ diff --git a/test/t/unit/test_unit_compgen.py b/test/t/unit/test_unit_compgen.py index cfdec8e..fb04d74 100644 --- a/test/t/unit/test_unit_compgen.py +++ b/test/t/unit/test_unit_compgen.py @@ -1,7 +1,8 @@ -import pytest import re -from conftest import assert_bash_exec, bash_env_saved, assert_complete +import pytest + +from conftest import assert_bash_exec, assert_complete, bash_env_saved @pytest.mark.bashcomp(cmd=None) diff --git a/test/t/unit/test_unit_compgen_available_interfaces.py b/test/t/unit/test_unit_compgen_available_interfaces.py new file mode 100644 index 0000000..5e93100 --- /dev/null +++ b/test/t/unit/test_unit_compgen_available_interfaces.py @@ -0,0 +1,25 @@ +import pytest + +from conftest import assert_bash_exec + + +@pytest.mark.bashcomp(cmd=None) +class TestUtilCompgenAvailableInterfaces: + @pytest.fixture + def functions(self, bash): + assert_bash_exec( + bash, + "_comp__test_dump() { ((${#arr[@]})) && printf '<%s>' \"${arr[@]}\"; echo; }", + ) + assert_bash_exec( + bash, + '_comp__test_compgen() { local -a arr=(00); _comp_compgen -v arr "$@"; _comp__test_dump; }', + ) + + def test_1_trailing_colons(self, bash, functions): + output = assert_bash_exec( + bash, + "_comp__test_compgen available_interfaces", + want_output=True, + ) + assert ":" not in output.strip() diff --git a/test/t/unit/test_unit_load.py b/test/t/unit/test_unit_load.py new file mode 100644 index 0000000..3515703 --- /dev/null +++ b/test/t/unit/test_unit_load.py @@ -0,0 +1,130 @@ +import os + +import pytest + +from conftest import assert_bash_exec, bash_env_saved, prepare_fixture_dir + + +@pytest.mark.bashcomp(cmd=None, cwd="_comp_load") +class TestCompLoad: + @pytest.fixture + def fixture_dir(self, request, bash): + """Construct the fixture directory in a temporary directory. + + Some of the tests use specific setups of symbolic links. However, if + we put the symbolic links in the static fixture directory, Automake + resolves them for tarballs. As a result, the tests fail when the files + are extracted from the tarballs. There does not seem to be any option + to change the behavior of Automake. + + We instead manually set up all symbolic links needed for the tests + here. The other normal files and directories are statically included + in the repository as "/test/fixtures/_comp_load". We first copy the + statically included files and directories to a temporary directory and + set up symbolic links. + """ + + tmpdir, _, _ = prepare_fixture_dir(request, files=[], dirs=[]) + assert_bash_exec(bash, "cp -R %s/* %s/" % (os.getcwd(), tmpdir)) + assert_bash_exec(bash, "mkdir -p %s/bin" % tmpdir) + assert_bash_exec( + bash, "ln -sf ../prefix1/bin/cmd1 %s/bin/cmd1" % tmpdir + ) + assert_bash_exec( + bash, "ln -sf ../prefix1/sbin/cmd2 %s/bin/cmd2" % tmpdir + ) + return str(tmpdir) + + def test_userdir_1(self, bash, fixture_dir): + with bash_env_saved(bash) as bash_env: + bash_env.chdir(fixture_dir) + bash_env.write_variable( + "BASH_COMPLETION_USER_DIR", + "$PWD/userdir1:$PWD/userdir2:$BASH_COMPLETION_USER_DIR", + quote=False, + ) + bash_env.write_variable( + "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False + ) + output = assert_bash_exec( + bash, "_comp_load cmd1", want_output=True + ) + assert output.strip() == "cmd1: sourced from userdir1" + output = assert_bash_exec( + bash, "_comp_load cmd2", want_output=True + ) + assert output.strip() == "cmd2: sourced from userdir2" + + def test_PATH_1(self, bash, fixture_dir): + with bash_env_saved(bash) as bash_env: + bash_env.chdir(fixture_dir) + bash_env.write_variable( + "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False + ) + output = assert_bash_exec( + bash, "_comp_load cmd1", want_output=True + ) + assert output.strip() == "cmd1: sourced from prefix1" + output = assert_bash_exec( + bash, "_comp_load cmd2", want_output=True + ) + assert output.strip() == "cmd2: sourced from prefix1" + output = assert_bash_exec( + bash, "complete -p cmd2", want_output=True + ) + assert " cmd2" in output + output = assert_bash_exec( + bash, 'complete -p "$PWD/prefix1/sbin/cmd2"', want_output=True + ) + assert "/prefix1/sbin/cmd2" in output + + def test_cmd_path_1(self, bash, fixture_dir): + with bash_env_saved(bash) as bash_env: + bash_env.chdir(fixture_dir) + assert_bash_exec(bash, "complete -r cmd1 || :", want_output=None) + output = assert_bash_exec( + bash, "_comp_load prefix1/bin/cmd1", want_output=True + ) + assert output.strip() == "cmd1: sourced from prefix1" + output = assert_bash_exec( + bash, 'complete -p "$PWD/prefix1/bin/cmd1"', want_output=True + ) + assert "/prefix1/bin/cmd1" in output + assert_bash_exec(bash, "! complete -p cmd1", want_output=None) + output = assert_bash_exec( + bash, "_comp_load prefix1/sbin/cmd2", want_output=True + ) + assert output.strip() == "cmd2: sourced from prefix1" + output = assert_bash_exec( + bash, "_comp_load bin/cmd1", want_output=True + ) + assert output.strip() == "cmd1: sourced from prefix1" + output = assert_bash_exec( + bash, "_comp_load bin/cmd2", want_output=True + ) + assert output.strip() == "cmd2: sourced from prefix1" + + def test_cmd_path_2(self, bash, fixture_dir): + with bash_env_saved(bash) as bash_env: + bash_env.chdir(fixture_dir) + bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False) + output = assert_bash_exec( + bash, "_comp_load cmd1", want_output=True + ) + assert output.strip() == "cmd1: sourced from prefix1" + output = assert_bash_exec( + bash, "_comp_load cmd2", want_output=True + ) + assert output.strip() == "cmd2: sourced from prefix1" + + def test_cmd_intree_precedence(self, bash, fixture_dir): + """ + Test in-tree, i.e. completions/$cmd relative to the main script + has precedence over location derived from PATH. + """ + with bash_env_saved(bash) as bash_env: + bash_env.chdir(fixture_dir) + bash_env.write_variable("PATH", "$PWD/prefix1/bin", quote=False) + # The in-tree `sh` completion should be loaded here, + # and cause no output, unlike our `$PWD/prefix1/bin/sh` canary. + assert_bash_exec(bash, "_comp_load sh", want_output=False) diff --git a/test/t/unit/test_unit_load_completion.py b/test/t/unit/test_unit_load_completion.py deleted file mode 100644 index 6272e5b..0000000 --- a/test/t/unit/test_unit_load_completion.py +++ /dev/null @@ -1,94 +0,0 @@ -import pytest - -from conftest import assert_bash_exec, bash_env_saved - - -@pytest.mark.bashcomp(cmd=None, cwd="_comp_load") -class TestLoadCompletion: - def test_userdir_1(self, bash): - with bash_env_saved(bash) as bash_env: - bash_env.write_variable( - "BASH_COMPLETION_USER_DIR", - "$PWD/userdir1:$PWD/userdir2:$BASH_COMPLETION_USER_DIR", - quote=False, - ) - bash_env.write_variable( - "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False - ) - output = assert_bash_exec( - bash, "_comp_load cmd1", want_output=True - ) - assert output.strip() == "cmd1: sourced from userdir1" - output = assert_bash_exec( - bash, "_comp_load cmd2", want_output=True - ) - assert output.strip() == "cmd2: sourced from userdir2" - - def test_PATH_1(self, bash): - with bash_env_saved(bash) as bash_env: - bash_env.write_variable( - "PATH", "$PWD/prefix1/bin:$PWD/prefix1/sbin", quote=False - ) - output = assert_bash_exec( - bash, "_comp_load cmd1", want_output=True - ) - assert output.strip() == "cmd1: sourced from prefix1" - output = assert_bash_exec( - bash, "_comp_load cmd2", want_output=True - ) - assert output.strip() == "cmd2: sourced from prefix1" - output = assert_bash_exec( - bash, "complete -p cmd2", want_output=True - ) - assert " cmd2" in output - output = assert_bash_exec( - bash, 'complete -p "$PWD/prefix1/sbin/cmd2"', want_output=True - ) - assert "/prefix1/sbin/cmd2" in output - - def test_cmd_path_1(self, bash): - assert_bash_exec(bash, "complete -r cmd1 || :", want_output=None) - output = assert_bash_exec( - bash, "_comp_load prefix1/bin/cmd1", want_output=True - ) - assert output.strip() == "cmd1: sourced from prefix1" - output = assert_bash_exec( - bash, 'complete -p "$PWD/prefix1/bin/cmd1"', want_output=True - ) - assert "/prefix1/bin/cmd1" in output - assert_bash_exec(bash, "! complete -p cmd1", want_output=None) - output = assert_bash_exec( - bash, "_comp_load prefix1/sbin/cmd2", want_output=True - ) - assert output.strip() == "cmd2: sourced from prefix1" - output = assert_bash_exec( - bash, "_comp_load bin/cmd1", want_output=True - ) - assert output.strip() == "cmd1: sourced from prefix1" - output = assert_bash_exec( - bash, "_comp_load bin/cmd2", want_output=True - ) - assert output.strip() == "cmd2: sourced from prefix1" - - def test_cmd_path_2(self, bash): - with bash_env_saved(bash) as bash_env: - bash_env.write_variable("PATH", "$PWD/bin:$PATH", quote=False) - output = assert_bash_exec( - bash, "_comp_load cmd1", want_output=True - ) - assert output.strip() == "cmd1: sourced from prefix1" - output = assert_bash_exec( - bash, "_comp_load cmd2", want_output=True - ) - assert output.strip() == "cmd2: sourced from prefix1" - - def test_cmd_intree_precedence(self, bash): - """ - Test in-tree, i.e. completions/$cmd relative to the main script - has precedence over location derived from PATH. - """ - with bash_env_saved(bash) as bash_env: - bash_env.write_variable("PATH", "$PWD/prefix1/bin", quote=False) - # The in-tree `sh` completion should be loaded here, - # and cause no output, unlike our `$PWD/prefix1/bin/sh` canary. - assert_bash_exec(bash, "_comp_load sh", want_output=False) diff --git a/version.txt b/version.txt index d8b6989..fb2c076 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -2.12.0 +2.13.0 -- cgit v1.2.3