From 16164d52e1bf7ffd9d30549289ccd998117ecb7a Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Thu, 25 Mar 2021 03:39:53 +1100 Subject: [PATCH 01/52] Update tested with in Cabal file --- optparse-applicative.cabal | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 732acd8e..7ea5f91c 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -54,7 +54,9 @@ tested-with: GHC==8.2.2, GHC==8.4.4, GHC==8.6.5, - GHC==8.8.1 + GHC==8.8.4, + GHC==8.10.4, + GHC==9.0.1 source-repository head type: git From c010e2f78817e21da102619921c38af52d2b6667 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Thu, 25 Mar 2021 03:43:15 +1100 Subject: [PATCH 02/52] Add github actions CI --- .github/workflows/haskell-ci.yml | 192 +++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 .github/workflows/haskell-ci.yml diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml new file mode 100644 index 00000000..252d8373 --- /dev/null +++ b/.github/workflows/haskell-ci.yml @@ -0,0 +1,192 @@ +# This GitHub workflow config has been generated by a script via +# +# haskell-ci 'github' 'optparse-applicative.cabal' +# +# To regenerate the script (for example after adjusting tested-with) run +# +# haskell-ci regenerate +# +# For more information, see https://github.com/haskell-CI/haskell-ci +# +# version: 0.12.1 +# +# REGENDATA ("0.12.1",["github","optparse-applicative.cabal"]) +# +name: Haskell-CI +on: + - push + - pull_request +jobs: + linux: + name: Haskell-CI - Linux - ${{ matrix.compiler }} + runs-on: ubuntu-18.04 + container: + image: buildpack-deps:bionic + continue-on-error: ${{ matrix.allow-failure }} + strategy: + matrix: + include: + - compiler: ghc-9.0.1 + allow-failure: false + - compiler: ghc-8.10.4 + allow-failure: false + - compiler: ghc-8.8.4 + allow-failure: false + - compiler: ghc-8.6.5 + allow-failure: false + - compiler: ghc-8.4.4 + allow-failure: false + - compiler: ghc-8.2.2 + allow-failure: false + - compiler: ghc-8.0.2 + allow-failure: false + - compiler: ghc-7.10.3 + allow-failure: false + - compiler: ghc-7.8.4 + allow-failure: false + - compiler: ghc-7.6.3 + allow-failure: false + - compiler: ghc-7.4.2 + allow-failure: false + - compiler: ghc-7.2.2 + allow-failure: false + - compiler: ghc-7.0.4 + allow-failure: false + fail-fast: false + steps: + - name: apt + run: | + apt-get update + apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common + apt-add-repository -y 'ppa:hvr/ghc' + apt-get update + apt-get install -y $CC cabal-install-3.4 + env: + CC: ${{ matrix.compiler }} + - name: Set PATH and environment variables + run: | + echo "$HOME/.cabal/bin" >> $GITHUB_PATH + echo "LANG=C.UTF-8" >> $GITHUB_ENV + echo "CABAL_DIR=$HOME/.cabal" >> $GITHUB_ENV + echo "CABAL_CONFIG=$HOME/.cabal/config" >> $GITHUB_ENV + HCDIR=$(echo "/opt/$CC" | sed 's/-/\//') + HCNAME=ghc + HC=$HCDIR/bin/$HCNAME + echo "HC=$HC" >> $GITHUB_ENV + echo "HCPKG=$HCDIR/bin/$HCNAME-pkg" >> $GITHUB_ENV + echo "HADDOCK=$HCDIR/bin/haddock" >> $GITHUB_ENV + echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> $GITHUB_ENV + HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') + echo "HCNUMVER=$HCNUMVER" >> $GITHUB_ENV + echo "ARG_TESTS=--enable-tests" >> $GITHUB_ENV + echo "ARG_BENCH=--enable-benchmarks" >> $GITHUB_ENV + echo "HEADHACKAGE=false" >> $GITHUB_ENV + echo "ARG_COMPILER=--$HCNAME --with-compiler=$HC" >> $GITHUB_ENV + echo "GHCJSARITH=0" >> $GITHUB_ENV + env: + CC: ${{ matrix.compiler }} + - name: env + run: | + env + - name: write cabal config + run: | + mkdir -p $CABAL_DIR + cat >> $CABAL_CONFIG < cabal-plan.xz + echo 'de73600b1836d3f55e32d80385acc055fd97f60eaa0ab68a755302685f5d81bc cabal-plan.xz' | sha256sum -c - + xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan + rm -f cabal-plan.xz + chmod a+x $HOME/.cabal/bin/cabal-plan + cabal-plan --version + - name: checkout + uses: actions/checkout@v2 + with: + path: source + - name: initial cabal.project for sdist + run: | + touch cabal.project + echo "packages: $GITHUB_WORKSPACE/source/." >> cabal.project + cat cabal.project + - name: sdist + run: | + mkdir -p sdist + $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist + - name: unpack + run: | + mkdir -p unpacked + find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; + - name: generate cabal.project + run: | + PKGDIR_optparse_applicative="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/optparse-applicative-[0-9.]*')" + echo "PKGDIR_optparse_applicative=${PKGDIR_optparse_applicative}" >> $GITHUB_ENV + touch cabal.project + touch cabal.project.local + echo "packages: ${PKGDIR_optparse_applicative}" >> cabal.project + if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo "package optparse-applicative" >> cabal.project ; fi + if [ $((HCNUMVER >= 80200)) -ne 0 ] ; then echo " ghc-options: -Werror=missing-methods" >> cabal.project ; fi + cat >> cabal.project <> cabal.project.local + cat cabal.project + cat cabal.project.local + - name: dump install plan + run: | + $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all + cabal-plan + - name: cache + uses: actions/cache@v2 + with: + key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} + path: ~/.cabal/store + restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- + - name: install dependencies + run: | + $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all + $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all + - name: build w/o tests + run: | + $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all + - name: build + run: | + $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always + - name: tests + run: | + $CABAL v2-test $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --test-show-details=direct + - name: cabal check + run: | + cd ${PKGDIR_optparse_applicative} || false + ${CABAL} -vnormal check + - name: haddock + run: | + $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all + - name: unconstrained build + run: | + rm -f cabal.project.local + $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all From 6cbe0a4a1a318144d291cf9e240d6771a60cf217 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Thu, 25 Mar 2021 03:50:28 +1100 Subject: [PATCH 03/52] Remove travis CI file --- .travis.yml | 180 ---------------------------------------------------- 1 file changed, 180 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9260e707..00000000 --- a/.travis.yml +++ /dev/null @@ -1,180 +0,0 @@ -# This Travis job script has been generated by a script via -# -# haskell-ci '--ghc-head' '--hlint' '--hlint-yaml=.hlint.yaml' 'optparse-applicative.cabal' -# -# For more information, see https://github.com/haskell-CI/haskell-ci -# -# version: 0.3.20190429 -# -language: c -dist: xenial -git: - # whether to recursively clone submodules - submodules: false -cache: - directories: - - $HOME/.cabal/packages - - $HOME/.cabal/store -before_cache: - - rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log - # remove files that are regenerated by 'cabal update' - - rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.* - - rm -fv $CABALHOME/packages/hackage.haskell.org/*.json - - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache - - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar - - rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx - - rm -rfv $CABALHOME/packages/head.hackage -matrix: - include: - - compiler: ghc-8.10.1 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.10.1","cabal-install-3.0"]}} - env: GHCHEAD=true - - compiler: ghc-8.8.1 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.8.1","cabal-install-3.0"]}} - - compiler: ghc-8.6.5 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.6.5","cabal-install-2.4"]}} - - compiler: ghc-8.4.4 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.4.4","cabal-install-2.4"]}} - - compiler: ghc-8.2.2 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.2.2","cabal-install-2.4"]}} - - compiler: ghc-8.0.2 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-8.0.2","cabal-install-2.4"]}} - - compiler: ghc-7.10.3 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.10.3","cabal-install-2.4"]}} - - compiler: ghc-7.8.4 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.8.4","cabal-install-2.4"]}} - - compiler: ghc-7.6.3 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.6.3","cabal-install-2.4"]}} - - compiler: ghc-7.4.2 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.4.2","cabal-install-2.4"]}} - - compiler: ghc-7.2.2 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.2.2","cabal-install-2.4"]}} - - compiler: ghc-7.0.4 - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-7.0.4","cabal-install-2.4"]}} - - compiler: ghc-head - addons: {"apt":{"sources":["hvr-ghc"],"packages":["ghc-head","cabal-install-head"]}} - env: GHCHEAD=true - allow_failures: - - compiler: ghc-head -before_install: - - HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//') - - HCPKG="$HC-pkg" - - unset CC - - CABAL=/opt/ghc/bin/cabal - - CABALHOME=$HOME/.cabal - - export PATH="$CABALHOME/bin:$PATH" - - TOP=$(pwd) - - HCNUMVER=$(( $(${HC} --numeric-version|sed -E 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\1 * 10000 + \2 * 100 + \3/') )) - - echo $HCNUMVER - - CABAL="$CABAL -vnormal+nowrap+markoutput" - - set -o pipefail - - | - echo 'function blue(s) { printf "\033[0;34m" s "\033[0m " }' >> .colorful.awk - echo 'BEGIN { state = "output"; }' >> .colorful.awk - echo '/^-----BEGIN CABAL OUTPUT-----$/ { state = "cabal" }' >> .colorful.awk - echo '/^-----END CABAL OUTPUT-----$/ { state = "output" }' >> .colorful.awk - echo '!/^(-----BEGIN CABAL OUTPUT-----|-----END CABAL OUTPUT-----)/ {' >> .colorful.awk - echo ' if (state == "cabal") {' >> .colorful.awk - echo ' print blue($0)' >> .colorful.awk - echo ' } else {' >> .colorful.awk - echo ' print $0' >> .colorful.awk - echo ' }' >> .colorful.awk - echo '}' >> .colorful.awk - - cat .colorful.awk - - | - color_cabal_output () { - awk -f $TOP/.colorful.awk - } - - echo text | color_cabal_output -install: - - ${CABAL} --version - - echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]" - - TEST=--enable-tests - - BENCH=--enable-benchmarks - - GHCHEAD=${GHCHEAD-false} - - rm -f $CABALHOME/config - - | - echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config - echo "remote-build-reporting: anonymous" >> $CABALHOME/config - echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config - echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config - echo "world-file: $CABALHOME/world" >> $CABALHOME/config - echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config - echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config - echo "installdir: $CABALHOME/bin" >> $CABALHOME/config - echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config - echo "store-dir: $CABALHOME/store" >> $CABALHOME/config - echo "install-dirs user" >> $CABALHOME/config - echo " prefix: $CABALHOME" >> $CABALHOME/config - echo "repository hackage.haskell.org" >> $CABALHOME/config - echo " url: http://hackage.haskell.org/" >> $CABALHOME/config - - | - if $GHCHEAD; then - echo "allow-newer: $($HCPKG list --simple-output | sed -E 's/([a-zA-Z-]+)-[0-9.]+/*:\1/g')" >> $CABALHOME/config - - echo "repository head.hackage" >> $CABALHOME/config - echo " url: http://head.hackage.haskell.org/" >> $CABALHOME/config - echo " secure: True" >> $CABALHOME/config - echo " root-keys: 07c59cb65787dedfaef5bd5f987ceb5f7e5ebf88b904bbd4c5cbdeb2ff71b740" >> $CABALHOME/config - echo " 2e8555dde16ebd8df076f1a8ef13b8f14c66bad8eafefd7d9e37d0ed711821fb" >> $CABALHOME/config - echo " 8f79fd2389ab2967354407ec852cbe73f2e8635793ac446d09461ffb99527f6e" >> $CABALHOME/config - echo " key-threshold: 3" >> $CABALHOME/config - fi - - cat $CABALHOME/config - - rm -fv cabal.project cabal.project.local cabal.project.freeze - - travis_retry ${CABAL} v2-update -v - - if [ $HCNUMVER -eq 80605 ] ; then ${CABAL} v2-install -w ${HC} -j2 hlint --constraint='hlint ==2.1.*' | color_cabal_output ; fi - # Generate cabal.project - - rm -rf cabal.project cabal.project.local cabal.project.freeze - - touch cabal.project - - | - echo 'packages: "."' >> cabal.project - - | - echo "write-ghc-environment-files: always" >> cabal.project - - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(optparse-applicative)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" - - cat cabal.project || true - - cat cabal.project.local || true - - if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi - - ${CABAL} v2-freeze -w ${HC} ${TEST} ${BENCH} | color_cabal_output - - "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'" - - rm cabal.project.freeze - - ${CABAL} v2-build -w ${HC} ${TEST} ${BENCH} --dep -j2 all | color_cabal_output - - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks --dep -j2 all | color_cabal_output -script: - - DISTDIR=$(mktemp -d /tmp/dist-test.XXXX) - # Packaging... - - ${CABAL} v2-sdist all | color_cabal_output - # Unpacking... - - mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/ - - cd ${DISTDIR} || false - - find . -maxdepth 1 -name '*.tar.gz' -exec tar -xvf '{}' \; - # Generate cabal.project - - rm -rf cabal.project cabal.project.local cabal.project.freeze - - touch cabal.project - - | - echo 'packages: "optparse-applicative-*/*.cabal"' >> cabal.project - - | - echo "write-ghc-environment-files: always" >> cabal.project - - "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(optparse-applicative)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done" - - cat cabal.project || true - - cat cabal.project.local || true - # Building... - # this builds all libraries and executables (without tests/benchmarks) - - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks all | color_cabal_output - # Building with tests and benchmarks... - # build & run tests, build benchmarks - - ${CABAL} v2-build -w ${HC} ${TEST} ${BENCH} all | color_cabal_output - # Testing... - - ${CABAL} v2-test -w ${HC} ${TEST} ${BENCH} all | color_cabal_output - # HLint.. - - if [ $HCNUMVER -eq 80605 ] ; then (cd optparse-applicative-* && hlint -h ${TOP}/.hlint.yaml src) ; fi - # cabal check... - - (cd optparse-applicative-* && ${CABAL} -vnormal check) - # haddock... - - ${CABAL} v2-haddock -w ${HC} ${TEST} ${BENCH} all | color_cabal_output - # Building without installed constraints for packages in global-db... - - rm -f cabal.project.local - - ${CABAL} v2-build -w ${HC} --disable-tests --disable-benchmarks all | color_cabal_output - -# REGENDATA ["--ghc-head","--hlint","--hlint-yaml=.hlint.yaml","optparse-applicative.cabal"] -# EOF From b4127435af1caca01f099f3238249720b25feaa2 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Thu, 25 Mar 2021 04:00:20 +1100 Subject: [PATCH 04/52] Update badges and unreleased section of changelog --- CHANGELOG.md | 5 +++++ README.md | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9160579..1bb140e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ - Make tabulation width configurable in usage texts. +- Separate program name and description in ParserHelp type. + +- Add `helperWith` function, which can be easily used to + localize the help flag. + ## Version 0.16.1.0 (21 Nov 2020) - Guard `process` dependency behind an on by default flag. diff --git a/README.md b/README.md index 415676cb..fcbc22f1 100644 --- a/README.md +++ b/README.md @@ -1020,6 +1020,6 @@ simplified implementation. [monoid]: http://hackage.haskell.org/package/base/docs/Data-Monoid.html [semigroup]: http://hackage.haskell.org/package/base/docs/Data-Semigroup.html [parsec]: http://hackage.haskell.org/package/parsec - [status]: http://travis-ci.org/pcapriotti/optparse-applicative?branch=master - [status-png]: https://api.travis-ci.org/pcapriotti/optparse-applicative.svg?branch=master + [status]: https://github.com/pcapriotti/optparse-applicative/actions/workflows/haskell-ci.yml + [status-png]: https://github.com/pcapriotti/optparse-applicative/workflows/Haskell-CI/badge.svg [ansi-wl-pprint]: http://hackage.haskell.org/package/ansi-wl-pprint From 8edc41994984cbfdfc1ee960e4d4d112cfccbc11 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Tue, 30 Mar 2021 10:00:10 +0200 Subject: [PATCH 05/52] Fix typo in the documentation of style --- src/Options/Applicative/Builder.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index 3d919be2..917659a2 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -219,7 +219,7 @@ hidden = optionMod $ \p -> -- -- /NOTE/: This builder is more flexible than its name and example -- allude. One of the motivating examples for its addition was to --- used `const` to completely replace the usage text of an option. +-- use `const` to completely replace the usage text of an option. style :: ( Doc -> Doc ) -> Mod f a style x = optionMod $ \p -> p { propDescMod = Just x } From 7a686cb48ac9cd12cdcfe396332d0f5fc47d4a13 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Sat, 17 Apr 2021 12:24:18 +0200 Subject: [PATCH 06/52] Add note about disambiguation quirk (#419) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index fcbc22f1..e912ac95 100644 --- a/README.md +++ b/README.md @@ -702,6 +702,11 @@ main = customExecParser p opts ``` +**Note**. If an option name is a prefix of another option, then it +will never be matched when disambiguation is on. See +https://github.com/pcapriotti/optparse-applicative/issues/419 for more +details. + ### Customising the help screen optparse-applicative has a number of combinators to help customise From 791793f9cc262bc7d9dd481105d83f81a9c259bc Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Sat, 17 Apr 2021 12:27:43 +0200 Subject: [PATCH 07/52] Improve link formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e912ac95..99330938 100644 --- a/README.md +++ b/README.md @@ -704,8 +704,8 @@ main = customExecParser p opts **Note**. If an option name is a prefix of another option, then it will never be matched when disambiguation is on. See -https://github.com/pcapriotti/optparse-applicative/issues/419 for more -details. +[#419](https://github.com/pcapriotti/optparse-applicative/issues/419) +for more details. ### Customising the help screen From faeded9628e501611fa44a0a49be47a6fc133a24 Mon Sep 17 00:00:00 2001 From: ibzaman Date: Sun, 28 Mar 2021 23:13:28 -0700 Subject: [PATCH 08/52] fix noncompilable readme example fixes #286 --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 99330938..8750234b 100644 --- a/README.md +++ b/README.md @@ -527,22 +527,6 @@ functions, each with its own set of options, and possibly some global options that apply to all of them. Typical examples are version control systems like `git`, or build tools like `cabal`. -A command can be created using the `subparser` builder (or `hsubparser`, -which is identical but for an additional `--help` option on each -command), and commands can be added with the `command` modifier. -For example - -```haskell -subparser - ( command "add" (info addOptions ( progDesc "Add a file to the repository" )) - <> command "commit" (info commitOptions ( progDesc "Record changes to the repository" )) - ) -``` - -Each command takes a full `ParserInfo` structure, which will be -used to extract a description for this command when generating a -help text. - Note that all the parsers appearing in a command need to have the same type. For this reason, it is often best to use a sum type which has the same structure as the command itself. For example, @@ -559,6 +543,22 @@ data Command ... ``` +A command can then be created using the `subparser` builder (or +`hsubparser`, which is identical but for an additional `--help` option +on each command), and commands can be added with the `command` +modifier. For example, + +```haskell +subparser + ( command "add" (info (Add <$> addOptions) ( progDesc "Add a file to the repository" )) + <> command "commit" (info (Commit <$> commitOptions) ( progDesc "Record changes to the repository" )) + ) +``` + +Each command takes a full `ParserInfo` structure, which will be +used to extract a description for this command when generating a +help text. + Alternatively, you can directly return an `IO` action from a parser, and execute it using `join` from `Control.Monad`. From 094435b40ed2acb716421d3130b2483c4d38b3b4 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Sat, 17 Apr 2021 16:59:30 +0200 Subject: [PATCH 09/52] Simplify command example in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8750234b..facf30eb 100644 --- a/README.md +++ b/README.md @@ -550,8 +550,8 @@ modifier. For example, ```haskell subparser - ( command "add" (info (Add <$> addOptions) ( progDesc "Add a file to the repository" )) - <> command "commit" (info (Commit <$> commitOptions) ( progDesc "Record changes to the repository" )) + ( command "add" (info addCommand ( progDesc "Add a file to the repository" )) + <> command "commit" (info commitCommand ( progDesc "Record changes to the repository" )) ) ``` From 1276236f75f1f855bc0cd84322fd2066a8aa7ab1 Mon Sep 17 00:00:00 2001 From: Paolo Capriotti Date: Wed, 21 Apr 2021 22:35:34 +0200 Subject: [PATCH 10/52] Fix warnings on recent GHC --- src/Options/Applicative/Help/Core.hs | 5 +++++ src/Options/Applicative/Help/Pretty.hs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index 9560fcec..1901546d 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} module Options.Applicative.Help.Core ( cmdDesc, briefDesc, @@ -24,8 +25,12 @@ import Data.Function (on) import Data.List (sort, intersperse, groupBy) import Data.Foldable (any, foldl') import Data.Maybe (maybeToList, catMaybes, fromMaybe) +#if !MIN_VERSION_base(4,8,0) import Data.Monoid (mempty) +#endif +#if !MIN_VERSION_base(4,11,0) import Data.Semigroup (Semigroup (..)) +#endif import Prelude hiding (any) import Options.Applicative.Common diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index 9c8400b3..f23f02b3 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE CPP #-} module Options.Applicative.Help.Pretty ( module Text.PrettyPrint.ANSI.Leijen , (.$.) @@ -6,7 +7,9 @@ module Options.Applicative.Help.Pretty ) where import Control.Applicative +#if !MIN_VERSION_base(4,11,0) import Data.Semigroup ((<>)) +#endif import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>), columns) import Text.PrettyPrint.ANSI.Leijen.Internal (Doc (..), flatten) From 4ac16d00c44f5d2102f54ecd4e0c80407558cd92 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Tue, 27 Jul 2021 09:57:43 +1000 Subject: [PATCH 11/52] Update bounds on transformers and transformers-compat --- optparse-applicative.cabal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 7ea5f91c..0fb93394 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -97,8 +97,8 @@ library , Options.Applicative.Internal build-depends: base == 4.* - , transformers >= 0.2 && < 0.6 - , transformers-compat >= 0.3 && < 0.7 + , transformers >= 0.2 && < 0.7 + , transformers-compat >= 0.3 && < 0.8 , ansi-wl-pprint >= 0.6.8 && < 0.7 if flag(process) From 4a20540894ffe8922c40287bfc304ef89fe5c67f Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Wed, 4 Aug 2021 21:52:02 +1000 Subject: [PATCH 12/52] Add inline and backtrack information to subparser haddocks --- src/Options/Applicative/Builder.hs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index 917659a2..ea028446 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -273,6 +273,11 @@ completer f = fieldMod $ modCompleter (`mappend` f) -- | Builder for a command parser. The 'command' modifier can be used to -- specify individual commands. +-- +-- By default, sub-parsers allow backtracking to their parent's options when +-- they are completed. To allow full mixing of parent and sub-parser options, +-- turn on 'subparserInline'; otherwise, to disable backtracking completely, +-- use 'noBacktrack'. subparser :: Mod CommandFields a -> Parser a subparser m = mkParser d g rdr where From 9ab6f7dfee3d267cf5df45bd8307bcf13071a982 Mon Sep 17 00:00:00 2001 From: Andreas Abel Date: Mon, 16 Aug 2021 09:34:35 +0200 Subject: [PATCH 13/52] Haddock for fullDesc: point out that this is the default --- src/Options/Applicative/Builder.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index ea028446..e205b979 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -390,7 +390,7 @@ instance Monoid (InfoMod a) where instance Semigroup (InfoMod a) where m1 <> m2 = InfoMod $ applyInfoMod m2 . applyInfoMod m1 --- | Show a full description in the help text of this parser. +-- | Show a full description in the help text of this parser (default). fullDesc :: InfoMod a fullDesc = InfoMod $ \i -> i { infoFullDesc = True } From bcf750fa3bc1074a43471df511f5ab1b5ea62026 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Tue, 27 Jul 2021 09:57:43 +1000 Subject: [PATCH 14/52] Support starting the usage text on the next line if too long. --- src/Options/Applicative/Help/Core.hs | 11 ++++---- src/Options/Applicative/Help/Pretty.hs | 39 ++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index 1901546d..fb17b328 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -265,11 +265,12 @@ parserGlobals pprefs p = -- | Generate option summary. parserUsage :: ParserPrefs -> Parser a -> String -> Doc parserUsage pprefs p progn = - hsep - [ string "Usage:", - string progn, - align (extractChunk (briefDesc pprefs p)) - ] + group $ + hsep + [ string "Usage:", + string progn, + hangAtIfOver 9 35 (extractChunk (briefDesc pprefs p)) + ] -- | Peek at the structure of the rendered tree within. -- diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index f23f02b3..5954b4e8 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -4,6 +4,7 @@ module Options.Applicative.Help.Pretty , (.$.) , groupOrNestLine , altSep + , hangAtIfOver ) where import Control.Applicative @@ -24,12 +25,24 @@ import Prelude -- | Apply the function if we're not at the -- start of our nesting level. ifNotAtRoot :: (Doc -> Doc) -> Doc -> Doc -ifNotAtRoot f doc = +ifNotAtRoot = + ifElseAtRoot id + +-- | Apply the function if we're not at the +-- start of our nesting level. +ifAtRoot :: (Doc -> Doc) -> Doc -> Doc +ifAtRoot = + flip ifElseAtRoot id + +-- | Apply the function if we're not at the +-- start of our nesting level. +ifElseAtRoot :: (Doc -> Doc) -> (Doc -> Doc) -> Doc -> Doc +ifElseAtRoot f g doc = Nesting $ \i -> Column $ \j -> if i == j - then doc - else f doc + then f doc + else g doc -- | Render flattened text on this line, or start @@ -57,3 +70,23 @@ groupOrNestLine = altSep :: Doc -> Doc -> Doc altSep x y = group (x <+> char '|' <> line) y + + +-- | Printer hacks to get nice indentation for long commands +-- and subcommands. +-- +-- If we're starting this section over the desired width +--   (usually 1/3 of the ribbon), then we will make a line +-- break, indent all of the usage, and go. +-- +-- The ifAtRoot is an interesting clause. If this whole +-- operation is put under a `group` then the linebreak +-- will disappear; then item d will therefore not be at +-- the starting column, and it won't be indented more. +hangAtIfOver :: Int -> Int -> Doc -> Doc +hangAtIfOver i j d = + Column $ \k -> + if k <= j then + align d + else + linebreak <> ifAtRoot (indent i) d From aab8fb9592ead2a3732359c3853e1b17aaa73313 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Thu, 16 Sep 2021 12:50:09 +1000 Subject: [PATCH 15/52] Add test for long sub-command rendering --- optparse-applicative.cabal | 2 ++ tests/Examples/LongSub.hs | 34 ++++++++++++++++++++++++ tests/formatting-long-subcommand.err.txt | 9 +++++++ tests/test.hs | 13 +++++++++ 4 files changed, 58 insertions(+) create mode 100644 tests/Examples/LongSub.hs create mode 100644 tests/formatting-long-subcommand.err.txt diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 0fb93394..6047981d 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -36,6 +36,7 @@ extra-source-files: CHANGELOG.md tests/helponemptysub.err.txt tests/long_equals.err.txt tests/formatting.err.txt + tests/formatting-long-subcommand.err.txt tests/nested.err.txt tests/optional.err.txt tests/nested_optional.err.txt @@ -124,6 +125,7 @@ test-suite tests , Examples.Commands , Examples.Formatting , Examples.Hello + , Examples.LongSub build-depends: base , optparse-applicative diff --git a/tests/Examples/LongSub.hs b/tests/Examples/LongSub.hs new file mode 100644 index 00000000..22ced292 --- /dev/null +++ b/tests/Examples/LongSub.hs @@ -0,0 +1,34 @@ +{-# LANGUAGE CPP #-} +module Examples.LongSub where + +import Data.Monoid +import Options.Applicative + +#if __GLASGOW_HASKELL__ <= 702 +(<>) :: Monoid a => a -> a -> a +(<>) = mappend +#endif + +data Sample + = Hello [String] + | Goodbye + deriving (Eq, Show) + +hello :: Parser Sample +hello = + Hello + <$> many (argument str (metavar "TARGET...")) + <* switch (long "first-flag") + <* switch (long "second-flag") + <* switch (long "third-flag") + <* switch (long "fourth-flag") + +sample :: Parser Sample +sample = hsubparser + ( command "hello-very-long-sub" + (info hello + (progDesc "Print greeting")) + ) + +opts :: ParserInfo Sample +opts = info (sample <**> helper) idm diff --git a/tests/formatting-long-subcommand.err.txt b/tests/formatting-long-subcommand.err.txt new file mode 100644 index 00000000..a6179fe3 --- /dev/null +++ b/tests/formatting-long-subcommand.err.txt @@ -0,0 +1,9 @@ +Usage: formatting-long-subcommand hello-very-long-sub + [TARGET...] [--first-flag] + [--second-flag] [--third-flag] + [--fourth-flag] + + Print greeting + +Available options: + -h,--help Show this help text diff --git a/tests/test.hs b/tests/test.hs index 3c8bf6a4..b67295c6 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -9,6 +9,7 @@ import qualified Examples.Commands as Commands import qualified Examples.Cabal as Cabal import qualified Examples.Alternatives as Alternatives import qualified Examples.Formatting as Formatting +import qualified Examples.LongSub as LongSub import Control.Applicative import Control.Monad @@ -891,6 +892,18 @@ prop_help_unknown_context = once $ post = run i ["--help", "not-a-command"] in grabHelpMessage pre === grabHelpMessage post + +prop_long_command_line_flow :: Property +prop_long_command_line_flow = once $ + let p = LongSub.sample <**> helper + i = info p + ( progDesc (concat + [ "This is a very long program description. " + , "This text should be automatically wrapped " + , "to fit the size of the terminal" ]) ) + in checkHelpTextWith ExitSuccess (prefs (columns 50)) "formatting-long-subcommand" i ["hello-very-long-sub", "--help"] + + --- deriving instance Arbitrary a => Arbitrary (Chunk a) From 610fd9c136dc1f02c57a33587c7181971039c2ce Mon Sep 17 00:00:00 2001 From: Oleg Grenrus Date: Tue, 16 Nov 2021 17:32:33 +0200 Subject: [PATCH 16/52] Allow semigroups-0.20 --- optparse-applicative.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 6047981d..21375fb9 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -106,7 +106,7 @@ library build-depends: process >= 1.0 && < 1.7 if !impl(ghc >= 8) - build-depends: semigroups >= 0.10 && < 0.20 + build-depends: semigroups >= 0.10 && < 0.21 , fail == 4.9.* test-suite tests From 0e2c605cff367b9cc76ce740bbc80e3122f3dea6 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Mon, 31 Jan 2022 18:23:36 +1100 Subject: [PATCH 17/52] Prepare version 0.17 --- CHANGELOG.md | 6 +++++- optparse-applicative.cabal | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb140e4..81c4d2e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## Version 0.17.0.0 (1 Feb 2022) - Make tabulation width configurable in usage texts. @@ -7,6 +7,10 @@ - Add `helperWith` function, which can be easily used to localize the help flag. +- Improve usage texts when command names are long. + +- Improve Documentation. + ## Version 0.16.1.0 (21 Nov 2020) - Guard `process` dependency behind an on by default flag. diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 21375fb9..fb9fa569 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -1,5 +1,5 @@ name: optparse-applicative -version: 0.16.1.0 +version: 0.17.0.0 synopsis: Utilities and combinators for parsing command line options description: optparse-applicative is a haskell library for parsing options From 0dce55f6fd80e2bd4af27372a9eb8d06f33eec70 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Mon, 31 Jan 2022 23:32:40 +1100 Subject: [PATCH 18/52] Fixup documentation for show globals option --- src/Options/Applicative/Types.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index ee0636b6..0644676b 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -125,7 +125,7 @@ data ParserPrefs = ParserPrefs -- single space (default: False) , prefHelpShowGlobal :: Bool -- ^ when displaying subparsers' usage help, -- show parent options under a "global options" - -- section (default: True) + -- section (default: False) , prefTabulateFill ::Int -- ^ Indentation width for tables } deriving (Eq, Show) From 355d7bf5bb6c610ec7a8716676eb2eb513c99c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20L=C3=A4ll?= Date: Fri, 29 Apr 2022 01:54:32 +0300 Subject: [PATCH 19/52] Remove `import Data.Semigroup ((<>))` from readme (#452) `Data.Semigroup` is exported from Prelude since base-4.11.0.0 (2018). --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index facf30eb..ec820614 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,6 @@ Here's a simple example of a parser. ```haskell import Options.Applicative -import Data.Semigroup ((<>)) data Sample = Sample { hello :: String From 6f74f3be4e4a9571feb8aedeb5efed87f3352aaa Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Wed, 6 Jul 2022 15:14:20 +1000 Subject: [PATCH 20/52] Allow disambiguation to work with commands as well as options --- src/Options/Applicative/BashCompletion.hs | 26 ++++++++++----------- src/Options/Applicative/Builder.hs | 4 ++-- src/Options/Applicative/Builder/Internal.hs | 4 ++-- src/Options/Applicative/Common.hs | 17 +++++++++----- src/Options/Applicative/Extra.hs | 8 +++---- src/Options/Applicative/Help/Core.hs | 7 +++--- src/Options/Applicative/Internal.hs | 6 +++++ src/Options/Applicative/Types.hs | 4 ++-- 8 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index b010c7df..7a9a1109 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -107,11 +107,11 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -> return [] | otherwise -> run_completer (crCompleter rdr) - CmdReader _ ns p + CmdReader _ ns | argumentIsUnreachable reachability -> return [] | otherwise - -> return . add_cmd_help p $ filter_names ns + -> return . with_cmd_help $ filter (is_completion . fst) ns -- When doing enriched completions, add any help specified -- to the completion variables (tab separated). @@ -126,17 +126,18 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- When doing enriched completions, add the command description -- to the completion variables (tab separated). - add_cmd_help :: Functor f => (String -> Maybe (ParserInfo a)) -> f String -> f String - add_cmd_help p = case richness of - Standard -> - id - Enriched _ len -> - fmap $ \cmd -> - let h = p cmd >>= unChunk . infoProgDesc - in maybe cmd (\h' -> cmd ++ "\t" ++ render_line len h') h + with_cmd_help :: Functor f => f (String, ParserInfo a) -> f String + with_cmd_help = + case richness of + Standard -> + fmap fst + Enriched _ len -> + fmap $ \(cmd, cmdInfo) -> + let h = unChunk (infoProgDesc cmdInfo) + in maybe cmd (\h' -> cmd ++ "\t" ++ render_line len h') h show_names :: [OptName] -> [String] - show_names = filter_names . map showOption + show_names = filter is_completion . map showOption -- We only want to show a single line in the completion results description. -- If there was a line break, it would come across as a different completion @@ -147,9 +148,6 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre [x] -> x x : _ -> x ++ "..." - filter_names :: [String] -> [String] - filter_names = filter is_completion - run_completer :: Completer -> IO [String] run_completer c = runCompleter c (fromMaybe "" (listToMaybe ws'')) diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index e205b979..ec7809c0 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -282,8 +282,8 @@ subparser :: Mod CommandFields a -> Parser a subparser m = mkParser d g rdr where Mod _ d g = metavar "COMMAND" `mappend` m - (groupName, cmds, subs) = mkCommand m - rdr = CmdReader groupName cmds subs + (groupName, cmds) = mkCommand m + rdr = CmdReader groupName cmds -- | Builder for an argument parser. argument :: ReadM a -> Mod ArgumentFields a -> Parser a diff --git a/src/Options/Applicative/Builder/Internal.hs b/src/Options/Applicative/Builder/Internal.hs index e5bc4b63..39ab8a49 100644 --- a/src/Options/Applicative/Builder/Internal.hs +++ b/src/Options/Applicative/Builder/Internal.hs @@ -152,8 +152,8 @@ baseProps = OptProperties , propShowGlobal = True } -mkCommand :: Mod CommandFields a -> (Maybe String, [String], String -> Maybe (ParserInfo a)) -mkCommand m = (group, map fst cmds, (`lookup` cmds)) +mkCommand :: Mod CommandFields a -> (Maybe String, [(String, ParserInfo a)]) +mkCommand m = (group, cmds) where Mod f _ _ = m CommandFields cmds group = f (CommandFields [] Nothing) diff --git a/src/Options/Applicative/Common.hs b/src/Options/Applicative/Common.hs index 46d2b730..0c3d4f91 100644 --- a/src/Options/Applicative/Common.hs +++ b/src/Options/Applicative/Common.hs @@ -166,24 +166,29 @@ searchArg prefs arg = searchParser $ \opt -> do when (isArg (optMain opt)) cut case optMain opt of - CmdReader _ _ f -> - case (f arg, prefBacktrack prefs) of - (Just subp, NoBacktrack) -> lift $ do + CmdReader _ cs -> do + subp <- select (cmdMatches cs) + case prefBacktrack prefs of + NoBacktrack -> lift $ do args <- get <* put [] fmap pure . lift $ enterContext arg subp *> runParserInfo subp args <* exitContext - (Just subp, Backtrack) -> fmap pure . lift . StateT $ \args -> + Backtrack -> fmap pure . lift . StateT $ \args -> enterContext arg subp *> runParser (infoPolicy subp) CmdStart (infoParser subp) args <* exitContext - (Just subp, SubparserInline) -> lift $ do + SubparserInline -> lift $ do lift $ enterContext arg subp return $ infoParser subp - (Nothing, _) -> mzero ArgReader rdr -> fmap pure . lift . lift $ runReadM (crReader rdr) arg _ -> mzero + where + cmdMatches cs + | prefDisambiguate prefs = snd <$> filter (isPrefixOf arg . fst) cs + | otherwise = maybeToList (lookup arg cs) + stepParser :: MonadP m => ParserPrefs -> ArgPolicy -> String -> Parser a -> NondetT (StateT Args m) (Parser a) stepParser pprefs AllPositionals arg p = diff --git a/src/Options/Applicative/Extra.hs b/src/Options/Applicative/Extra.hs index e8e9a752..47facbbe 100644 --- a/src/Options/Applicative/Extra.hs +++ b/src/Options/Applicative/Extra.hs @@ -88,8 +88,8 @@ hsubparser :: Mod CommandFields a -> Parser a hsubparser m = mkParser d g rdr where Mod _ d g = metavar "COMMAND" `mappend` m - (groupName, cmds, subs) = mkCommand m - rdr = CmdReader groupName cmds (fmap add_helper . subs) + (groupName, cmds) = mkCommand m + rdr = CmdReader groupName ((fmap . fmap) add_helper cmds) add_helper pinfo = pinfo { infoParser = infoParser pinfo <**> helper } @@ -303,10 +303,10 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> OptReader ns _ _ -> fmap showOption ns FlagReader ns _ -> fmap showOption ns ArgReader _ -> [] - CmdReader _ ns _ | argumentIsUnreachable reachability + CmdReader _ ns | argumentIsUnreachable reachability -> [] | otherwise - -> ns + -> fst <$> ns _ -> mempty diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index fb17b328..1ee7a7e5 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -94,12 +94,11 @@ cmdDesc pprefs = mapParser desc where desc _ opt = case optMain opt of - CmdReader gn cmds p -> + CmdReader gn cmds -> (,) gn $ tabulate (prefTabulateFill pprefs) - [ (string cmd, align (extractChunk d)) - | cmd <- reverse cmds, - d <- maybeToList . fmap infoProgDesc $ p cmd + [ (string nm, align (extractChunk (infoProgDesc cmd))) + | (nm, cmd) <- reverse cmds ] _ -> mempty diff --git a/src/Options/Applicative/Internal.hs b/src/Options/Applicative/Internal.hs index d5b854e7..7495f6a5 100644 --- a/src/Options/Applicative/Internal.hs +++ b/src/Options/Applicative/Internal.hs @@ -18,6 +18,7 @@ module Options.Applicative.Internal , ListT , takeListT , runListT + , select , NondetT , cut @@ -263,3 +264,8 @@ disamb allow_amb xs = do return $ case xs' of [x] -> Just x _ -> Nothing + +select :: (Foldable f, Alternative m) => f a -> m a +select = foldr cons empty + where + cons x xs = pure x <|> xs diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index 0644676b..a556f2a8 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -242,14 +242,14 @@ data OptReader a -- ^ flag reader | ArgReader (CReader a) -- ^ argument reader - | CmdReader (Maybe String) [String] (String -> Maybe (ParserInfo a)) + | CmdReader (Maybe String) [(String, ParserInfo a)] -- ^ command reader instance Functor OptReader where fmap f (OptReader ns cr e) = OptReader ns (fmap f cr) e fmap f (FlagReader ns x) = FlagReader ns (f x) fmap f (ArgReader cr) = ArgReader (fmap f cr) - fmap f (CmdReader n cs g) = CmdReader n cs ((fmap . fmap) f . g) + fmap f (CmdReader n cs) = CmdReader n ((fmap . fmap . fmap) f cs) -- | A @Parser a@ is an option parser returning a value of type 'a'. data Parser a From 58be314d119339815618c54d7f8cd587a62e3107 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Wed, 6 Jul 2022 15:31:39 +1000 Subject: [PATCH 21/52] Add tests for command disambuguation --- tests/test.hs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/test.hs b/tests/test.hs index b67295c6..4ae75df3 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -318,6 +318,49 @@ prop_ambiguous = once $ result = execParserPure (prefs disambiguate) i ["--ba"] in assertError result (\_ -> property succeeded) + +prop_disambiguate_in_same_subparsers :: Property +prop_disambiguate_in_same_subparsers = once $ + let p0 = subparser (command "oranges" (info (pure "oranges") idm) <> command "apples" (info (pure "apples") idm) <> metavar "B") + i = info (p0 <**> helper) idm + result = execParserPure (prefs disambiguate) i ["orang"] + in assertResult result ((===) "oranges") + +prop_disambiguate_commands_in_separate_subparsers :: Property +prop_disambiguate_commands_in_separate_subparsers = once $ + let p2 = subparser (command "oranges" (info (pure "oranges") idm) <> metavar "B") + p1 = subparser (command "apples" (info (pure "apples") idm) <> metavar "C") + p0 = p1 <|> p2 + i = info (p0 <**> helper) idm + result = execParserPure (prefs disambiguate) i ["orang"] + in assertResult result ((===) "oranges") + +prop_fail_ambiguous_commands_in_same_subparser :: Property +prop_fail_ambiguous_commands_in_same_subparser = once $ + let p0 = subparser (command "oranges" (info (pure ()) idm) <> command "orangutans" (info (pure ()) idm) <> metavar "B") + i = info (p0 <**> helper) idm + result = execParserPure (prefs disambiguate) i ["orang"] + in assertError result (\_ -> property succeeded) + +prop_fail_ambiguous_commands_in_separate_subparser :: Property +prop_fail_ambiguous_commands_in_separate_subparser = once $ + let p2 = subparser (command "oranges" (info (pure ()) idm) <> metavar "B") + p1 = subparser (command "orangutans" (info (pure ()) idm) <> metavar "C") + p0 = p1 <|> p2 + i = info (p0 <**> helper) idm + result = execParserPure (prefs disambiguate) i ["orang"] + in assertError result (\_ -> property succeeded) + +prop_without_disambiguation_same_named_commands_should_parse_in_order :: Property +prop_without_disambiguation_same_named_commands_should_parse_in_order = once $ + let p3 = subparser (command "b" (info (pure ()) idm) <> metavar "B") + p2 = subparser (command "a" (info (pure ()) idm) <> metavar "B") + p1 = subparser (command "a" (info (pure ()) idm) <> metavar "C") + p0 = (,,) <$> p1 <*> p2 <*> p3 + i = info (p0 <**> helper) idm + result = execParserPure defaultPrefs i ["b", "a", "a"] + in assertResult result ((===) ((), (), ())) + prop_completion :: Property prop_completion = once . ioProperty $ let p = (,) @@ -903,7 +946,6 @@ prop_long_command_line_flow = once $ , "to fit the size of the terminal" ]) ) in checkHelpTextWith ExitSuccess (prefs (columns 50)) "formatting-long-subcommand" i ["hello-very-long-sub", "--help"] - --- deriving instance Arbitrary a => Arbitrary (Chunk a) From 49e8b1c56b531ece210a6ccdcd5348d4409126ba Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Wed, 6 Jul 2022 17:00:30 +1000 Subject: [PATCH 22/52] Specialise Foldable to List for older GHC --- src/Options/Applicative/Common.hs | 2 +- src/Options/Applicative/Internal.hs | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Options/Applicative/Common.hs b/src/Options/Applicative/Common.hs index 0c3d4f91..91e03cd7 100644 --- a/src/Options/Applicative/Common.hs +++ b/src/Options/Applicative/Common.hs @@ -167,7 +167,7 @@ searchArg prefs arg = when (isArg (optMain opt)) cut case optMain opt of CmdReader _ cs -> do - subp <- select (cmdMatches cs) + subp <- hoistList (cmdMatches cs) case prefBacktrack prefs of NoBacktrack -> lift $ do args <- get <* put [] diff --git a/src/Options/Applicative/Internal.hs b/src/Options/Applicative/Internal.hs index 7495f6a5..b4831447 100644 --- a/src/Options/Applicative/Internal.hs +++ b/src/Options/Applicative/Internal.hs @@ -18,7 +18,7 @@ module Options.Applicative.Internal , ListT , takeListT , runListT - , select + , hoistList , NondetT , cut @@ -173,9 +173,6 @@ bimapTStep :: (a -> b) -> (x -> y) -> TStep a x -> TStep b y bimapTStep _ _ TNil = TNil bimapTStep f g (TCons a x) = TCons (f a) (g x) -hoistList :: Monad m => [a] -> ListT m a -hoistList = foldr (\x xt -> ListT (return (TCons x xt))) mzero - takeListT :: Monad m => Int -> ListT m a -> ListT m a takeListT 0 = const mzero takeListT n = ListT . liftM (bimapTStep id (takeListT (n - 1))) . stepListT @@ -193,7 +190,7 @@ instance Monad m => Functor (ListT m) where . stepListT instance Monad m => Applicative (ListT m) where - pure = hoistList . pure + pure a = ListT (return (TCons a mzero)) (<*>) = ap instance Monad m => Monad (ListT m) where @@ -265,7 +262,7 @@ disamb allow_amb xs = do [x] -> Just x _ -> Nothing -select :: (Foldable f, Alternative m) => f a -> m a -select = foldr cons empty +hoistList :: Alternative m => [a] -> m a +hoistList = foldr cons empty where cons x xs = pure x <|> xs From 30a9b6157b277737f35d6f830bd7c9723983324e Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Tue, 16 Aug 2022 10:43:55 +0200 Subject: [PATCH 23/52] Update code snippet in parserFailure haddocks ShowHelpText takes an extra argument now so the snippet was failing compilation. --- src/Options/Applicative/Extra.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Options/Applicative/Extra.hs b/src/Options/Applicative/Extra.hs index 47facbbe..6284b209 100644 --- a/src/Options/Applicative/Extra.hs +++ b/src/Options/Applicative/Extra.hs @@ -153,7 +153,7 @@ execParserPure pprefs pinfo args = -- -- This function can be used, for example, to show the help text for a parser: -- --- @handleParseResult . Failure $ parserFailure pprefs pinfo ShowHelpText mempty@ +-- @handleParseResult . Failure $ parserFailure pprefs pinfo (ShowHelpText Nothing) mempty@ parserFailure :: ParserPrefs -> ParserInfo a -> ParseError -> [Context] -> ParserFailure ParserHelp From 6cd1dda291d5984b21e4b65412a21abb63db17ca Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 8 Sep 2022 10:38:05 +0200 Subject: [PATCH 24/52] add simpleVersioner --- src/Options/Applicative.hs | 1 + src/Options/Applicative/Extra.hs | 14 ++++++++++++++ tests/Examples/Cabal.hs | 6 +----- tests/cabal.err.txt | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs index 662134bb..de467e45 100644 --- a/src/Options/Applicative.hs +++ b/src/Options/Applicative.hs @@ -74,6 +74,7 @@ module Options.Applicative ( abortOption, infoOption, helper, + simpleVersioner, -- ** Modifiers -- diff --git a/src/Options/Applicative/Extra.hs b/src/Options/Applicative/Extra.hs index 47facbbe..337927e8 100644 --- a/src/Options/Applicative/Extra.hs +++ b/src/Options/Applicative/Extra.hs @@ -6,6 +6,7 @@ module Options.Applicative.Extra ( helper, helperWith, hsubparser, + simpleVersioner, execParser, customExecParser, execParserPure, @@ -93,6 +94,19 @@ hsubparser m = mkParser d g rdr add_helper pinfo = pinfo { infoParser = infoParser pinfo <**> helper } +-- | A hidden \"--version\" option that displays the version. +-- +-- > opts :: ParserInfo Sample +-- > opts = info (sample <**> simpleVersioner "v1.2.3") mempty +simpleVersioner :: String -- ^ Version string to be shown + -> Parser (a -> a) +simpleVersioner version = infoOption version $ + mconcat + [ long "version" + , help "Show version information" + , hidden + ] + -- | Run a program description. -- -- Parse command line arguments. Display help text and exit if any parse error diff --git a/tests/Examples/Cabal.hs b/tests/Examples/Cabal.hs index 2d5560cc..7100a545 100644 --- a/tests/Examples/Cabal.hs +++ b/tests/Examples/Cabal.hs @@ -39,10 +39,6 @@ data BuildOpts = BuildOpts { buildDir :: FilePath } deriving Show -version :: Parser (a -> a) -version = infoOption "0.0.0" - ( long "version" - <> help "Print version information" ) parser :: Parser Args parser = runA $ proc () -> do @@ -60,7 +56,7 @@ parser = runA $ proc () -> do <> command "build" (info buildParser (progDesc "Make this package ready for installation")) ) -< () - A version >>> A helper -< Args opts cmds + A (simpleVersioner "0.0.0") >>> A helper -< Args opts cmds commonOpts :: Parser CommonOpts commonOpts = CommonOpts diff --git a/tests/cabal.err.txt b/tests/cabal.err.txt index b419bb5f..c0f843be 100644 --- a/tests/cabal.err.txt +++ b/tests/cabal.err.txt @@ -9,4 +9,4 @@ Available options: Global options: -v,--verbose LEVEL Set verbosity to LEVEL - --version Print version information + --version Show version information From 5abe208467f317aa337f285f1ef946d1ef022948 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sun, 11 Sep 2022 20:38:59 +1000 Subject: [PATCH 25/52] Export requote --- src/Options/Applicative/Builder/Completer.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Options/Applicative/Builder/Completer.hs b/src/Options/Applicative/Builder/Completer.hs index f4d58098..5da556e7 100644 --- a/src/Options/Applicative/Builder/Completer.hs +++ b/src/Options/Applicative/Builder/Completer.hs @@ -6,6 +6,8 @@ module Options.Applicative.Builder.Completer , listIOCompleter , listCompleter , bashCompleter + + , requote ) where import Control.Applicative From 028bf60e43e056385b36ea4e925dd2f671fb7797 Mon Sep 17 00:00:00 2001 From: kukimik Date: Wed, 2 Nov 2022 23:44:29 +0100 Subject: [PATCH 26/52] Export `helpIndent` from `Options.Applicative`. --- CHANGELOG.md | 4 ++++ src/Options/Applicative.hs | 1 + 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c4d2e2..1baa7eb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + +- Export `helpIndent` from `Options.Applicative`. + ## Version 0.17.0.0 (1 Feb 2022) - Make tabulation width configurable in usage texts. diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs index de467e45..fa042f31 100644 --- a/src/Options/Applicative.hs +++ b/src/Options/Applicative.hs @@ -198,6 +198,7 @@ module Options.Applicative ( columns, helpLongEquals, helpShowGlobals, + helpIndent, defaultPrefs, -- * Completions From 5fda110544c6106b02c5eac2ac1651beeed702a5 Mon Sep 17 00:00:00 2001 From: Andreas Abel Date: Tue, 28 Mar 2023 22:29:53 +0200 Subject: [PATCH 27/52] Bump CI to ubuntu-20.04 and GHCs 9.6.1 9.4.4 9.2.7 9.0.2 8.10.7 ubuntu-18.04 has reached EOL --- .github/workflows/haskell-ci.yml | 155 ++++++++++++++++++++++++------- optparse-applicative.cabal | 29 +++--- 2 files changed, 139 insertions(+), 45 deletions(-) diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index 252d8373..5d2a1300 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -8,9 +8,9 @@ # # For more information, see https://github.com/haskell-CI/haskell-ci # -# version: 0.12.1 +# version: 0.15.20230321 # -# REGENDATA ("0.12.1",["github","optparse-applicative.cabal"]) +# REGENDATA ("0.15.20230321",["github","optparse-applicative.cabal"]) # name: Haskell-CI on: @@ -19,72 +19,152 @@ on: jobs: linux: name: Haskell-CI - Linux - ${{ matrix.compiler }} - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 + timeout-minutes: + 60 container: image: buildpack-deps:bionic continue-on-error: ${{ matrix.allow-failure }} strategy: matrix: include: - - compiler: ghc-9.0.1 + - compiler: ghc-9.6.1 + compilerKind: ghc + compilerVersion: 9.6.1 + setup-method: ghcup allow-failure: false - - compiler: ghc-8.10.4 + - compiler: ghc-9.4.4 + compilerKind: ghc + compilerVersion: 9.4.4 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.2.7 + compilerKind: ghc + compilerVersion: 9.2.7 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.0.2 + compilerKind: ghc + compilerVersion: 9.0.2 + setup-method: ghcup + allow-failure: false + - compiler: ghc-8.10.7 + compilerKind: ghc + compilerVersion: 8.10.7 + setup-method: ghcup allow-failure: false - compiler: ghc-8.8.4 + compilerKind: ghc + compilerVersion: 8.8.4 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-8.6.5 + compilerKind: ghc + compilerVersion: 8.6.5 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-8.4.4 + compilerKind: ghc + compilerVersion: 8.4.4 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-8.2.2 + compilerKind: ghc + compilerVersion: 8.2.2 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-8.0.2 + compilerKind: ghc + compilerVersion: 8.0.2 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-7.10.3 + compilerKind: ghc + compilerVersion: 7.10.3 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-7.8.4 + compilerKind: ghc + compilerVersion: 7.8.4 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-7.6.3 + compilerKind: ghc + compilerVersion: 7.6.3 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-7.4.2 + compilerKind: ghc + compilerVersion: 7.4.2 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-7.2.2 + compilerKind: ghc + compilerVersion: 7.2.2 + setup-method: hvr-ppa allow-failure: false - compiler: ghc-7.0.4 + compilerKind: ghc + compilerVersion: 7.0.4 + setup-method: hvr-ppa allow-failure: false fail-fast: false steps: - name: apt run: | apt-get update - apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common - apt-add-repository -y 'ppa:hvr/ghc' - apt-get update - apt-get install -y $CC cabal-install-3.4 + apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 + if [ "${{ matrix.setup-method }}" = ghcup ]; then + mkdir -p "$HOME/.ghcup/bin" + curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup" + chmod a+x "$HOME/.ghcup/bin/ghcup" + "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) + "$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + else + apt-add-repository -y 'ppa:hvr/ghc' + apt-get update + apt-get install -y "$HCNAME" + mkdir -p "$HOME/.ghcup/bin" + curl -sL https://downloads.haskell.org/ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 > "$HOME/.ghcup/bin/ghcup" + chmod a+x "$HOME/.ghcup/bin/ghcup" + "$HOME/.ghcup/bin/ghcup" install cabal 3.10.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + fi env: - CC: ${{ matrix.compiler }} + HCKIND: ${{ matrix.compilerKind }} + HCNAME: ${{ matrix.compiler }} + HCVER: ${{ matrix.compilerVersion }} - name: Set PATH and environment variables run: | echo "$HOME/.cabal/bin" >> $GITHUB_PATH - echo "LANG=C.UTF-8" >> $GITHUB_ENV - echo "CABAL_DIR=$HOME/.cabal" >> $GITHUB_ENV - echo "CABAL_CONFIG=$HOME/.cabal/config" >> $GITHUB_ENV - HCDIR=$(echo "/opt/$CC" | sed 's/-/\//') - HCNAME=ghc - HC=$HCDIR/bin/$HCNAME - echo "HC=$HC" >> $GITHUB_ENV - echo "HCPKG=$HCDIR/bin/$HCNAME-pkg" >> $GITHUB_ENV - echo "HADDOCK=$HCDIR/bin/haddock" >> $GITHUB_ENV - echo "CABAL=/opt/cabal/3.4/bin/cabal -vnormal+nowrap" >> $GITHUB_ENV + echo "LANG=C.UTF-8" >> "$GITHUB_ENV" + echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" + echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" + HCDIR=/opt/$HCKIND/$HCVER + if [ "${{ matrix.setup-method }}" = ghcup ]; then + HC=$HOME/.ghcup/bin/$HCKIND-$HCVER + echo "HC=$HC" >> "$GITHUB_ENV" + echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV" + echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV" + echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" + else + HC=$HCDIR/bin/$HCKIND + echo "HC=$HC" >> "$GITHUB_ENV" + echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV" + echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV" + echo "CABAL=$HOME/.ghcup/bin/cabal-3.10.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" + fi + HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') - echo "HCNUMVER=$HCNUMVER" >> $GITHUB_ENV - echo "ARG_TESTS=--enable-tests" >> $GITHUB_ENV - echo "ARG_BENCH=--enable-benchmarks" >> $GITHUB_ENV - echo "HEADHACKAGE=false" >> $GITHUB_ENV - echo "ARG_COMPILER=--$HCNAME --with-compiler=$HC" >> $GITHUB_ENV - echo "GHCJSARITH=0" >> $GITHUB_ENV + echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" + echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" + echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" + echo "HEADHACKAGE=false" >> "$GITHUB_ENV" + echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" + echo "GHCJSARITH=0" >> "$GITHUB_ENV" env: - CC: ${{ matrix.compiler }} + HCKIND: ${{ matrix.compilerKind }} + HCNAME: ${{ matrix.compiler }} + HCVER: ${{ matrix.compilerVersion }} - name: env run: | env @@ -107,6 +187,10 @@ jobs: repository hackage.haskell.org url: http://hackage.haskell.org/ EOF + cat >> $CABAL_CONFIG <> $GITHUB_ENV + echo "PKGDIR_optparse_applicative=${PKGDIR_optparse_applicative}" >> "$GITHUB_ENV" + rm -f cabal.project cabal.project.local touch cabal.project touch cabal.project.local echo "packages: ${PKGDIR_optparse_applicative}" >> cabal.project @@ -160,8 +245,8 @@ jobs: run: | $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all cabal-plan - - name: cache - uses: actions/cache@v2 + - name: restore cache + uses: actions/cache/restore@v3 with: key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} path: ~/.cabal/store @@ -185,8 +270,14 @@ jobs: ${CABAL} -vnormal check - name: haddock run: | - $CABAL v2-haddock $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all + $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all - name: unconstrained build run: | rm -f cabal.project.local $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all + - name: save cache + uses: actions/cache/save@v3 + if: always() + with: + key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} + path: ~/.cabal/store diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index fb9fa569..6ecbd305 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -45,19 +45,22 @@ extra-source-files: CHANGELOG.md homepage: https://github.com/pcapriotti/optparse-applicative bug-reports: https://github.com/pcapriotti/optparse-applicative/issues tested-with: - GHC==7.0.4, - GHC==7.2.2, - GHC==7.4.2, - GHC==7.6.3, - GHC==7.8.4, - GHC==7.10.3, - GHC==8.0.2, - GHC==8.2.2, - GHC==8.4.4, - GHC==8.6.5, - GHC==8.8.4, - GHC==8.10.4, - GHC==9.0.1 + GHC==9.6.1 + GHC==9.4.4 + GHC==9.2.7 + GHC==9.0.2 + GHC==8.10.7 + GHC==8.8.4 + GHC==8.6.5 + GHC==8.4.4 + GHC==8.2.2 + GHC==8.0.2 + GHC==7.10.3 + GHC==7.8.4 + GHC==7.6.3 + GHC==7.4.2 + GHC==7.2.2 + GHC==7.0.4 source-repository head type: git From 8f1dbeb9c945ee57dd6d376294a0d0caef7111f4 Mon Sep 17 00:00:00 2001 From: Paul J Date: Sat, 8 Apr 2023 14:24:04 +0200 Subject: [PATCH 28/52] Put back example for `optional`. Give example of usage with `optional`, as introduced by e7cc1568c5a112904d06837a086201de0431f6c8 but then removed by 8de333d2aeb6cf32261ff261db48d613b01c33c2. Its removal has caused a slight inconvenience I think for those not as well-versed in `Control.Applicative` combinators, e.g. https://stackoverflow.com/questions/32422339/how-to-parse-an-optional-flag-as-a-maybe-value. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ec820614..b29f063c 100644 --- a/README.md +++ b/README.md @@ -302,7 +302,14 @@ Having `Applicative` and `Alternative` instances, optparse-applicative parsers are also able to be composed with standard combinators. For example: `optional :: Alternative f => f a -> f (Maybe a)` will mean the user is not required to provide input for the affected -`Parser`. +`Parser`. The following option will return `Nothing` instead of failing +when it's not supplied: + +```haskell +optional $ strOption + ( long "output" + <> metavar "DIRECTORY" ) +``` ### Running parsers From e27156d8ca4f0f5abc353145f3e9d7b5022678ae Mon Sep 17 00:00:00 2001 From: Griffin Date: Tue, 18 Apr 2023 13:55:53 +0800 Subject: [PATCH 29/52] docs: remove broken link --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index ec820614..faf4420b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # optparse-applicative [![Continuous Integration status][status-png]][status] -[![Hackage matrix][hackage-matrix-png]][hackage-matrix] [![Hackage page (downloads and API reference)][hackage-png]][hackage] [![Hackage-Deps][hackage-deps-png]][hackage-deps] @@ -1017,8 +1016,6 @@ simplified implementation. [blog]: http://paolocapriotti.com/blog/2012/04/27/applicative-option-parser/ [hackage]: http://hackage.haskell.org/package/optparse-applicative [hackage-png]: http://img.shields.io/hackage/v/optparse-applicative.svg - [hackage-matrix]: https://matrix.hackage.haskell.org/package/optparse-applicative - [hackage-matrix-png]: https://matrix.hackage.haskell.org/api/v2/packages/optparse-applicative/badge [hackage-deps]: http://packdeps.haskellers.com/reverse/optparse-applicative [hackage-deps-png]: https://img.shields.io/hackage-deps/v/optparse-applicative.svg [monoid]: http://hackage.haskell.org/package/base/docs/Data-Monoid.html From effbdcc7bd99dff5f31cb81388eee02696d93965 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sat, 22 Apr 2023 08:28:00 +1000 Subject: [PATCH 30/52] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b29f063c..f016f1c3 100644 --- a/README.md +++ b/README.md @@ -302,8 +302,8 @@ Having `Applicative` and `Alternative` instances, optparse-applicative parsers are also able to be composed with standard combinators. For example: `optional :: Alternative f => f a -> f (Maybe a)` will mean the user is not required to provide input for the affected -`Parser`. The following option will return `Nothing` instead of failing -when it's not supplied: +`Parser`. For example, the following parser will return `Nothing` +instead of failing if the user doesn't supply an `output` option: ```haskell optional $ strOption From 889cdb4fd5f737a6c4c0f926784dc6303063e02b Mon Sep 17 00:00:00 2001 From: modotte Date: Thu, 18 May 2023 22:43:47 +0800 Subject: [PATCH 31/52] Make shell completion script text functions public --- src/Options/Applicative/BashCompletion.hs | 47 ++++++++++++++--------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 7a9a1109..83ea79c0 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -4,7 +4,10 @@ -- -- for more information on bash completion. module Options.Applicative.BashCompletion - ( bashCompletionParser + ( bashCompletionParser, + bashCompletionScript, + fishCompletionScript, + zshCompletionScript, ) where import Control.Applicative @@ -34,11 +37,15 @@ data Richness bashCompletionParser :: ParserInfo a -> ParserPrefs -> Parser CompletionResult bashCompletionParser pinfo pprefs = complParser where - failure opts = CompletionResult - { execCompletion = \progn -> unlines <$> opts progn } + returnCompletions opts = + CompletionResult $ + \progn -> unlines <$> opts progn + + scriptRequest = + CompletionResult . fmap pure complParser = asum - [ failure <$> + [ returnCompletions <$> ( bashCompletionQuery pinfo pprefs -- To get rich completions, one just needs the first -- command. To customise the lengths, use either of @@ -53,15 +60,13 @@ bashCompletionParser pinfo pprefs = complParser <*> (many . strOption) (long "bash-completion-word" `mappend` internal) <*> option auto (long "bash-completion-index" `mappend` internal) ) - , failure <$> - (bashCompletionScript <$> - strOption (long "bash-completion-script" `mappend` internal)) - , failure <$> - (fishCompletionScript <$> - strOption (long "fish-completion-script" `mappend` internal)) - , failure <$> - (zshCompletionScript <$> - strOption (long "zsh-completion-script" `mappend` internal)) + + , scriptRequest . bashCompletionScript <$> + strOption (long "bash-completion-script" `mappend` internal) + , scriptRequest . fishCompletionScript <$> + strOption (long "fish-completion-script" `mappend` internal) + , scriptRequest . zshCompletionScript <$> + strOption (long "zsh-completion-script" `mappend` internal) ] bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Richness -> [String] -> Int -> String -> IO [String] @@ -159,8 +164,9 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre w:_ -> isPrefixOf w _ -> const True -bashCompletionScript :: String -> String -> IO [String] -bashCompletionScript prog progn = return +-- | Generated bash shell completion script +bashCompletionScript :: String -> String -> String +bashCompletionScript prog progn = unlines [ "_" ++ progn ++ "()" , "{" , " local CMDLINE" @@ -194,8 +200,10 @@ words. Tab characters separate items from descriptions. -} -fishCompletionScript :: String -> String -> IO [String] -fishCompletionScript prog progn = return + +-- | Generated fish shell completion script +fishCompletionScript :: String -> String -> String +fishCompletionScript prog progn = unlines [ " function _" ++ progn , " set -l cl (commandline --tokenize --current-process)" , " # Hack around fish issue #3934" @@ -217,8 +225,9 @@ fishCompletionScript prog progn = return , "complete --no-files --command " ++ progn ++ " --arguments '(_" ++ progn ++ ")'" ] -zshCompletionScript :: String -> String -> IO [String] -zshCompletionScript prog progn = return +-- | Generated zsh shell completion script +zshCompletionScript :: String -> String -> String +zshCompletionScript prog progn = unlines [ "#compdef " ++ progn , "" , "local request" From 08fc3c8210292cb14efdbf0d627a8d7335efca55 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sun, 21 May 2023 08:57:33 +1000 Subject: [PATCH 32/52] Update changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1baa7eb7..ff8aa581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ - Export `helpIndent` from `Options.Applicative`. +- Export completion script generators from `Options.Applicative.BashCompletion`. + +- Add `simpleVersioner` utility for adding a '--version' option to a parser. + +- Allow commands to be disambiguated in a similar manner to flags when the + `disambiguate` modifier is used. + + This is a potentially breaking change as the internal `CmdReader` constructor + has been adapted so it is able to be inspected to a greater degree to support + finding submatches. + +- Improve documentation. + ## Version 0.17.0.0 (1 Feb 2022) - Make tabulation width configurable in usage texts. From c49dd48d0b9a835131b5100f4a6152710c644cf6 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Fri, 19 May 2023 21:37:00 +1000 Subject: [PATCH 33/52] Bump pretty printer and stop relying on its internal modules. This means that the prettyprinter library can now be used instead of the ansi-wl-pprint library depending on the version bounds. This was a bit tricky to get right, as there were a few tricks we pulled using the internal modules. Figured it out though. I've turned off deprection warnings on the module, which is a bit rough, but otherwise it spews many, many warnings. --- optparse-applicative.cabal | 2 +- src/Options/Applicative/Help/Pretty.hs | 12 +++++------- tests/test.hs | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 6ecbd305..211c0c8e 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -103,7 +103,7 @@ library build-depends: base == 4.* , transformers >= 0.2 && < 0.7 , transformers-compat >= 0.3 && < 0.8 - , ansi-wl-pprint >= 0.6.8 && < 0.7 + , ansi-wl-pprint >= 0.6.8 && < 1.1 if flag(process) build-depends: process >= 1.0 && < 1.7 diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index 5954b4e8..787faf78 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# OPTIONS -Wno-warnings-deprecations #-} module Options.Applicative.Help.Pretty ( module Text.PrettyPrint.ANSI.Leijen , (.$.) @@ -13,7 +14,6 @@ import Data.Semigroup ((<>)) #endif import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>), columns) -import Text.PrettyPrint.ANSI.Leijen.Internal (Doc (..), flatten) import qualified Text.PrettyPrint.ANSI.Leijen as PP import Prelude @@ -38,8 +38,8 @@ ifAtRoot = -- start of our nesting level. ifElseAtRoot :: (Doc -> Doc) -> (Doc -> Doc) -> Doc -> Doc ifElseAtRoot f g doc = - Nesting $ \i -> - Column $ \j -> + nesting $ \i -> + column $ \j -> if i == j then f doc else g doc @@ -52,9 +52,7 @@ ifElseAtRoot f g doc = -- group. groupOrNestLine :: Doc -> Doc groupOrNestLine = - Union - <$> flatten - <*> ifNotAtRoot (line <>) . nest 2 + group . ifNotAtRoot (linebreak <>) . nest 2 -- | Separate items in an alternative with a pipe. @@ -85,7 +83,7 @@ altSep x y = -- the starting column, and it won't be indented more. hangAtIfOver :: Int -> Int -> Doc -> Doc hangAtIfOver i j d = - Column $ \k -> + column $ \k -> if k <= j then align d else diff --git a/tests/test.hs b/tests/test.hs index 4ae75df3..ecbe1e28 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -949,12 +949,11 @@ prop_long_command_line_flow = once $ --- deriving instance Arbitrary a => Arbitrary (Chunk a) -deriving instance Eq SimpleDoc -deriving instance Show SimpleDoc + equalDocs :: Float -> Int -> Doc -> Doc -> Property -equalDocs f w d1 d2 = Doc.renderPretty f w d1 - === Doc.renderPretty f w d2 +equalDocs f w d1 d2 = Doc.displayS (Doc.renderPretty f w d1) "" + === Doc.displayS (Doc.renderPretty f w d2) "" prop_listToChunk_1 :: [String] -> Property prop_listToChunk_1 xs = isEmpty (listToChunk xs) === null xs From b4b5dac2081180df296b795fe0b02716485e02f8 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Fri, 19 May 2023 22:19:05 +1000 Subject: [PATCH 34/52] More warnings --- src/Options/Applicative/BashCompletion.hs | 4 +++- src/Options/Applicative/Help/Chunk.hs | 1 + src/Options/Applicative/Help/Core.hs | 3 ++- src/Options/Applicative/Help/Pretty.hs | 20 +++++++++++++++++--- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 83ea79c0..4a041c2f 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -1,10 +1,12 @@ +{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} -- | You don't need to import this module to enable bash completion. -- -- See -- -- for more information on bash completion. module Options.Applicative.BashCompletion - ( bashCompletionParser, + ( bashCompletionParser, + bashCompletionScript, fishCompletionScript, zshCompletionScript, diff --git a/src/Options/Applicative/Help/Chunk.hs b/src/Options/Applicative/Help/Chunk.hs index 6fd39a91..73d0bfa7 100644 --- a/src/Options/Applicative/Help/Chunk.hs +++ b/src/Options/Applicative/Help/Chunk.hs @@ -1,3 +1,4 @@ +{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Options.Applicative.Help.Chunk ( Chunk(..) , chunked diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index 1ee7a7e5..54c37032 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Options.Applicative.Help.Core ( cmdDesc, briefDesc, @@ -24,7 +25,7 @@ import Control.Monad (guard) import Data.Function (on) import Data.List (sort, intersperse, groupBy) import Data.Foldable (any, foldl') -import Data.Maybe (maybeToList, catMaybes, fromMaybe) +import Data.Maybe (catMaybes, fromMaybe) #if !MIN_VERSION_base(4,8,0) import Data.Monoid (mempty) #endif diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index 787faf78..b20e0dcc 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -1,23 +1,37 @@ {-# LANGUAGE CPP #-} -{-# OPTIONS -Wno-warnings-deprecations #-} +{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Options.Applicative.Help.Pretty ( module Text.PrettyPrint.ANSI.Leijen + , Doc + , indent + , renderPretty + , displayS , (.$.) , groupOrNestLine , altSep , hangAtIfOver ) where -import Control.Applicative #if !MIN_VERSION_base(4,11,0) import Data.Semigroup ((<>)) #endif -import Text.PrettyPrint.ANSI.Leijen hiding ((<$>), (<>), columns) +import Text.PrettyPrint.ANSI.Leijen hiding (Doc, (<$>), (<>), columns, indent, renderPretty, displayS) import qualified Text.PrettyPrint.ANSI.Leijen as PP import Prelude +type Doc = PP.Doc + +indent :: Int -> PP.Doc -> PP.Doc +indent = PP.indent + +renderPretty :: Float -> Int -> PP.Doc -> SimpleDoc +renderPretty = PP.renderPretty + +displayS :: SimpleDoc -> ShowS +displayS = PP.displayS + (.$.) :: Doc -> Doc -> Doc (.$.) = (PP.<$>) From 79f3a6310ea523ec3b06160770d92db44992dd64 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sun, 21 May 2023 09:18:47 +1000 Subject: [PATCH 35/52] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff8aa581..56375351 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +- Widen bounds for `ansi-wl-pprint`. This supports the use of `prettyprinter` + in a non-breaking way, as the `ansi-wl-pprint > 1.0` support the newer + library. + - Export `helpIndent` from `Options.Applicative`. - Export completion script generators from `Options.Applicative.BashCompletion`. From 856046e18012dc0409f162f648914fe5cabe73d8 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sat, 20 May 2023 19:01:57 +1000 Subject: [PATCH 36/52] Move to pretty printer --- .github/workflows/haskell-ci.yml | 10 ---- optparse-applicative.cabal | 5 +- src/Options/Applicative/BashCompletion.hs | 3 +- src/Options/Applicative/Help/Chunk.hs | 3 +- src/Options/Applicative/Help/Core.hs | 17 ++++--- src/Options/Applicative/Help/Pretty.hs | 57 ++++++++++++++--------- src/Options/Applicative/Help/Types.hs | 3 +- tests/test.hs | 12 ++--- 8 files changed, 56 insertions(+), 54 deletions(-) diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml index 5d2a1300..6ca82301 100644 --- a/.github/workflows/haskell-ci.yml +++ b/.github/workflows/haskell-ci.yml @@ -98,16 +98,6 @@ jobs: compilerVersion: 7.4.2 setup-method: hvr-ppa allow-failure: false - - compiler: ghc-7.2.2 - compilerKind: ghc - compilerVersion: 7.2.2 - setup-method: hvr-ppa - allow-failure: false - - compiler: ghc-7.0.4 - compilerKind: ghc - compilerVersion: 7.0.4 - setup-method: hvr-ppa - allow-failure: false fail-fast: false steps: - name: apt diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 211c0c8e..641743f3 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -100,10 +100,11 @@ library , Options.Applicative.Types , Options.Applicative.Internal - build-depends: base == 4.* + build-depends: base >= 4.5 && < 5 , transformers >= 0.2 && < 0.7 , transformers-compat >= 0.3 && < 0.8 - , ansi-wl-pprint >= 0.6.8 && < 1.1 + , prettyprinter >= 1.7 && < 1.8 + , prettyprinter-ansi-terminal >= 1.1 && < 1.2 if flag(process) build-depends: process >= 1.0 && < 1.7 diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 4a041c2f..e4b6356c 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -1,4 +1,3 @@ -{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} -- | You don't need to import this module to enable bash completion. -- -- See @@ -150,7 +149,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- If there was a line break, it would come across as a different completion -- possibility. render_line :: Int -> Doc -> String - render_line len doc = case lines (displayS (renderPretty 1 len doc) "") of + render_line len doc = case lines (prettyString 1 len doc) of [] -> "" [x] -> x x : _ -> x ++ "..." diff --git a/src/Options/Applicative/Help/Chunk.hs b/src/Options/Applicative/Help/Chunk.hs index 73d0bfa7..881a3819 100644 --- a/src/Options/Applicative/Help/Chunk.hs +++ b/src/Options/Applicative/Help/Chunk.hs @@ -1,4 +1,3 @@ -{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Options.Applicative.Help.Chunk ( Chunk(..) , chunked @@ -116,7 +115,7 @@ isEmpty = isNothing . unChunk -- > extractChunk . stringChunk = string stringChunk :: String -> Chunk Doc stringChunk "" = mempty -stringChunk s = pure (string s) +stringChunk s = pure (pretty s) -- | Convert a paragraph into a 'Chunk'. The resulting chunk is composed by the -- words of the original paragraph separated by softlines, so it will be diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index 54c37032..ce89070f 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -1,5 +1,4 @@ {-# LANGUAGE CPP #-} -{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Options.Applicative.Help.Core ( cmdDesc, briefDesc, @@ -58,7 +57,7 @@ optDesc pprefs style _reachability opt = meta = stringChunk $ optMetaVar opt descs = - map (string . showOption) names + map (pretty . showOption) names descriptions = listToChunk (intersperse (descSep style) descs) desc @@ -98,7 +97,7 @@ cmdDesc pprefs = mapParser desc CmdReader gn cmds -> (,) gn $ tabulate (prefTabulateFill pprefs) - [ (string nm, align (extractChunk (infoProgDesc cmd))) + [ (pretty nm, align (extractChunk (infoProgDesc cmd))) | (nm, cmd) <- reverse cmds ] _ -> mempty @@ -127,7 +126,7 @@ briefDesc' showOptional pprefs = | otherwise = filterOptional style = OptDescStyle - { descSep = string "|", + { descSep = pretty '|', descHidden = False, descGlobal = False } @@ -204,9 +203,9 @@ optionsDesc global pprefs = tabulate (prefTabulateFill pprefs) . catMaybes . map n = fst $ optDesc pprefs style info opt h = optHelp opt hdef = Chunk . fmap show_def . optShowDefault $ opt - show_def s = parens (string "default:" <+> string s) + show_def s = parens (pretty "default:" <+> pretty s) style = OptDescStyle - { descSep = string ",", + { descSep = pretty ',', descHidden = True, descGlobal = global } @@ -251,7 +250,7 @@ parserHelp pprefs p = group_title _ = mempty with_title :: String -> Chunk Doc -> Chunk Doc - with_title title = fmap (string title .$.) + with_title title = fmap (pretty title .$.) parserGlobals :: ParserPrefs -> Parser a -> ParserHelp @@ -267,8 +266,8 @@ parserUsage :: ParserPrefs -> Parser a -> String -> Doc parserUsage pprefs p progn = group $ hsep - [ string "Usage:", - string progn, + [ pretty "Usage:", + pretty progn, hangAtIfOver 9 35 (extractChunk (briefDesc pprefs p)) ] diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index b20e0dcc..c5ab867a 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -1,40 +1,41 @@ {-# LANGUAGE CPP #-} -{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-} module Options.Applicative.Help.Pretty - ( module Text.PrettyPrint.ANSI.Leijen + ( module Prettyprinter + , module Prettyprinter.Render.Terminal , Doc - , indent - , renderPretty - , displayS + , SimpleDoc + , (.$.) + , () + , groupOrNestLine , altSep , hangAtIfOver + + , prettyString ) where #if !MIN_VERSION_base(4,11,0) -import Data.Semigroup ((<>)) +import Data.Semigroup ((<>), mempty) #endif -import Text.PrettyPrint.ANSI.Leijen hiding (Doc, (<$>), (<>), columns, indent, renderPretty, displayS) -import qualified Text.PrettyPrint.ANSI.Leijen as PP +import Prettyprinter hiding (Doc) +import qualified Prettyprinter as PP +import qualified Prettyprinter.Render.String as PP +import Prettyprinter.Render.Terminal import Prelude -type Doc = PP.Doc +type Doc = PP.Doc Prettyprinter.Render.Terminal.AnsiStyle +type SimpleDoc = SimpleDocStream AnsiStyle -indent :: Int -> PP.Doc -> PP.Doc -indent = PP.indent - -renderPretty :: Float -> Int -> PP.Doc -> SimpleDoc -renderPretty = PP.renderPretty - -displayS :: SimpleDoc -> ShowS -displayS = PP.displayS +linebreak :: Doc +linebreak = flatAlt line mempty (.$.) :: Doc -> Doc -> Doc -(.$.) = (PP.<$>) - +x .$. y = x <> line <> y +() :: Doc -> Doc -> Doc +x y = x <> softline <> y -- | Apply the function if we're not at the -- start of our nesting level. @@ -58,7 +59,6 @@ ifElseAtRoot f g doc = then f doc else g doc - -- | Render flattened text on this line, or start -- a new line before rendering any text. -- @@ -81,7 +81,7 @@ groupOrNestLine = -- next line. altSep :: Doc -> Doc -> Doc altSep x y = - group (x <+> char '|' <> line) y + group (x <+> pretty '|' <> line) <> group linebreak <> y -- | Printer hacks to get nice indentation for long commands @@ -102,3 +102,18 @@ hangAtIfOver i j d = align d else linebreak <> ifAtRoot (indent i) d + + +renderPretty :: Double -> Int -> Doc -> SimpleDocStream AnsiStyle +renderPretty ribbonFraction lineWidth + = layoutSmart LayoutOptions + { layoutPageWidth = AvailablePerLine lineWidth ribbonFraction } + +prettyString :: Double -> Int -> Doc -> String +prettyString ribbonFraction lineWidth + = streamToString + . renderPretty ribbonFraction lineWidth + +streamToString :: SimpleDocStream AnsiStyle -> String +streamToString stream = + PP.renderShowS stream "" diff --git a/src/Options/Applicative/Help/Types.hs b/src/Options/Applicative/Help/Types.hs index 0e2d05c0..e9743ca2 100644 --- a/src/Options/Applicative/Help/Types.hs +++ b/src/Options/Applicative/Help/Types.hs @@ -42,6 +42,5 @@ helpText (ParserHelp e s h u d b g f) = -- | Convert a help text to 'String'. renderHelp :: Int -> ParserHelp -> String renderHelp cols - = (`displayS` "") - . renderPretty 1.0 cols + = prettyString 1.0 cols . helpText diff --git a/tests/test.hs b/tests/test.hs index ecbe1e28..4c888dca 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -28,7 +28,7 @@ import qualified Options.Applicative.NonEmpty import qualified Options.Applicative.Help as H -import Options.Applicative.Help.Pretty (Doc, SimpleDoc(..)) +import Options.Applicative.Help.Pretty (Doc) import qualified Options.Applicative.Help.Pretty as Doc import Options.Applicative.Help.Chunk import Options.Applicative.Help.Levenshtein @@ -951,9 +951,9 @@ prop_long_command_line_flow = once $ deriving instance Arbitrary a => Arbitrary (Chunk a) -equalDocs :: Float -> Int -> Doc -> Doc -> Property -equalDocs f w d1 d2 = Doc.displayS (Doc.renderPretty f w d1) "" - === Doc.displayS (Doc.renderPretty f w d2) "" +equalDocs :: Double -> Int -> Doc -> Doc -> Property +equalDocs f w d1 d2 = Doc.prettyString f w d1 + === Doc.prettyString f w d2 prop_listToChunk_1 :: [String] -> Property prop_listToChunk_1 xs = isEmpty (listToChunk xs) === null xs @@ -967,10 +967,10 @@ prop_extractChunk_1 x = extractChunk (pure x) === x prop_extractChunk_2 :: Chunk String -> Property prop_extractChunk_2 x = extractChunk (fmap pure x) === x -prop_stringChunk_1 :: Positive Float -> Positive Int -> String -> Property +prop_stringChunk_1 :: Positive Double -> Positive Int -> String -> Property prop_stringChunk_1 (Positive f) (Positive w) s = equalDocs f w (extractChunk (stringChunk s)) - (Doc.string s) + (Doc.pretty s) prop_stringChunk_2 :: String -> Property prop_stringChunk_2 s = isEmpty (stringChunk s) === null s From a67b20e70540cd1e0126730c274b55a0f23b8764 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sun, 21 May 2023 08:59:02 +1000 Subject: [PATCH 37/52] Bump version and add changelog. This also adds in the changelog for 0.17.1 which is not strictly a parent of this commit, but it's close enough and will be release in time order. --- CHANGELOG.md | 30 ++++++++++++++++++++++-------- optparse-applicative.cabal | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56375351..04131555 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,23 @@ -## Unreleased +## Version 0.18.0.0 (22 May 2023) + +- Move to 'prettyprinter` library for pretty printing. + + This is a potentially breaking change when one uses the '*Doc' family of functions + (like `headerDoc`) from `Options.Applicative`. However, as versions of + 'ansi-wl-pprint > 1.0' export a compatible `Doc` type, this can be mitigated by + using a recent version. + + One can also either import directly from `Options.Applicative.Help` or from the + `Prettyprinter` module of 'prettyprinter'. + +- Allow commands to be disambiguated in a similar manner to flags when the + `disambiguate` modifier is used. + + This is a potentially breaking change as the internal `CmdReader` constructor + has been adapted so it is able to be inspected to a greater degree to support + finding prefix matches. + +## Version 0.17.1.0 (22 May 2023) - Widen bounds for `ansi-wl-pprint`. This supports the use of `prettyprinter` in a non-breaking way, as the `ansi-wl-pprint > 1.0` support the newer @@ -10,15 +29,10 @@ - Add `simpleVersioner` utility for adding a '--version' option to a parser. -- Allow commands to be disambiguated in a similar manner to flags when the - `disambiguate` modifier is used. - - This is a potentially breaking change as the internal `CmdReader` constructor - has been adapted so it is able to be inspected to a greater degree to support - finding submatches. - - Improve documentation. +- Drop support for GHC 7.0 and 7.2. + ## Version 0.17.0.0 (1 Feb 2022) - Make tabulation width configurable in usage texts. diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 641743f3..be172b16 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -1,5 +1,5 @@ name: optparse-applicative -version: 0.17.0.0 +version: 0.18.0.0 synopsis: Utilities and combinators for parsing command line options description: optparse-applicative is a haskell library for parsing options From 4a5041e3cc5775c1f876a4c09f04e319c99004ea Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Fri, 26 May 2023 11:39:33 +1000 Subject: [PATCH 38/52] Use layoutPretty instead of layoutSmart when rendering help texts. The latter can occasionally result in a hang given complex inputs. --- CHANGELOG.md | 11 +++++++++++ src/Options/Applicative/Help/Pretty.hs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04131555..5ef13ec8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## Version 0.18.1.0 (26 May 2023) + +- Change layout algorithm used to a simpler, faster one. + + The layoutSmart algorithm appears to be extremely slow with some command line + sets, to the point where the program appears to hang. + + Fixes issues: + * \# 476 - Stack executable 'hangs' with 0.17.1 and 0.18.0. + + ## Version 0.18.0.0 (22 May 2023) - Move to 'prettyprinter` library for pretty printing. diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index c5ab867a..997ffd58 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -106,7 +106,7 @@ hangAtIfOver i j d = renderPretty :: Double -> Int -> Doc -> SimpleDocStream AnsiStyle renderPretty ribbonFraction lineWidth - = layoutSmart LayoutOptions + = layoutPretty LayoutOptions { layoutPageWidth = AvailablePerLine lineWidth ribbonFraction } prettyString :: Double -> Int -> Doc -> String From 66284950a600a1053978bf0b554eccf9ac53a044 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Fri, 26 May 2023 15:28:01 +1000 Subject: [PATCH 39/52] Use Ansi aware rendering functions for printing to the terminal. This unfortunately brings in a direct `text` dependency, which will make supporting text free use cases a bit harder. --- CHANGELOG.md | 1 + optparse-applicative.cabal | 1 + src/Options/Applicative/Help/Pretty.hs | 12 ++++++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef13ec8..e84e46a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Fixes issues: * \# 476 - Stack executable 'hangs' with 0.17.1 and 0.18.0. +- Render help text with `AnsiStyle` aware rendering functions. ## Version 0.18.0.0 (22 May 2023) diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index be172b16..59f64351 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -101,6 +101,7 @@ library , Options.Applicative.Internal build-depends: base >= 4.5 && < 5 + , text >= 1.2 , transformers >= 0.2 && < 0.7 , transformers-compat >= 0.3 && < 0.8 , prettyprinter >= 1.7 && < 1.8 diff --git a/src/Options/Applicative/Help/Pretty.hs b/src/Options/Applicative/Help/Pretty.hs index 997ffd58..43d111a8 100644 --- a/src/Options/Applicative/Help/Pretty.hs +++ b/src/Options/Applicative/Help/Pretty.hs @@ -18,15 +18,15 @@ module Options.Applicative.Help.Pretty #if !MIN_VERSION_base(4,11,0) import Data.Semigroup ((<>), mempty) #endif +import qualified Data.Text.Lazy as Lazy import Prettyprinter hiding (Doc) import qualified Prettyprinter as PP -import qualified Prettyprinter.Render.String as PP import Prettyprinter.Render.Terminal import Prelude -type Doc = PP.Doc Prettyprinter.Render.Terminal.AnsiStyle +type Doc = PP.Doc AnsiStyle type SimpleDoc = SimpleDocStream AnsiStyle linebreak :: Doc @@ -115,5 +115,9 @@ prettyString ribbonFraction lineWidth . renderPretty ribbonFraction lineWidth streamToString :: SimpleDocStream AnsiStyle -> String -streamToString stream = - PP.renderShowS stream "" +streamToString sdoc = + let + rendered = + Prettyprinter.Render.Terminal.renderLazy sdoc + in + Lazy.unpack rendered From d4f5d0d58b077a1643a5c8af18ed8c3bf8e3de34 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Fri, 26 May 2023 15:48:25 +1000 Subject: [PATCH 40/52] Rework documentation for latest version --- src/Options/Applicative/Builder.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index ec7809c0..bc12b5f2 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -189,7 +189,7 @@ showDefault = showDefaultWith show help :: String -> Mod f a help s = optionMod $ \p -> p { propHelp = paragraph s } --- | Specify the help text for an option as a 'Text.PrettyPrint.ANSI.Leijen.Doc' +-- | Specify the help text for an option as a 'Prettyprinter.Doc AnsiStyle' -- value. helpDoc :: Maybe Doc -> Mod f a helpDoc doc = optionMod $ \p -> p { propHelp = Chunk doc } @@ -215,7 +215,7 @@ hidden = optionMod $ \p -> -- | Apply a function to the option description in the usage text. -- -- > import Options.Applicative.Help --- > flag' () (short 't' <> style bold) +-- > flag' () (short 't' <> style (annotate bold)) -- -- /NOTE/: This builder is more flexible than its name and example -- allude. One of the motivating examples for its addition was to @@ -402,7 +402,7 @@ briefDesc = InfoMod $ \i -> i { infoFullDesc = False } header :: String -> InfoMod a header s = InfoMod $ \i -> i { infoHeader = paragraph s } --- | Specify a header for this parser as a 'Text.PrettyPrint.ANSI.Leijen.Doc' +-- | Specify a header for this parser as a 'Prettyprinter.Doc AnsiStyle' -- value. headerDoc :: Maybe Doc -> InfoMod a headerDoc doc = InfoMod $ \i -> i { infoHeader = Chunk doc } @@ -411,7 +411,7 @@ headerDoc doc = InfoMod $ \i -> i { infoHeader = Chunk doc } footer :: String -> InfoMod a footer s = InfoMod $ \i -> i { infoFooter = paragraph s } --- | Specify a footer for this parser as a 'Text.PrettyPrint.ANSI.Leijen.Doc' +-- | Specify a footer for this parser as a 'Prettyprinter.Doc AnsiStyle' -- value. footerDoc :: Maybe Doc -> InfoMod a footerDoc doc = InfoMod $ \i -> i { infoFooter = Chunk doc } @@ -420,7 +420,7 @@ footerDoc doc = InfoMod $ \i -> i { infoFooter = Chunk doc } progDesc :: String -> InfoMod a progDesc s = InfoMod $ \i -> i { infoProgDesc = paragraph s } --- | Specify a short program description as a 'Text.PrettyPrint.ANSI.Leijen.Doc' +-- | Specify a short program description as a 'Prettyprinter.Doc AnsiStyle' -- value. progDescDoc :: Maybe Doc -> InfoMod a progDescDoc doc = InfoMod $ \i -> i { infoProgDesc = Chunk doc } From 0713e59ed028afc3345115a2f438aa14404bae41 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Mon, 29 May 2023 09:15:44 +1000 Subject: [PATCH 41/52] Bump version in cabal file --- CHANGELOG.md | 4 ++-- optparse-applicative.cabal | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84e46a6..d397e8c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ -## Version 0.18.1.0 (26 May 2023) +## Version 0.18.1.0 (29 May 2023) -- Change layout algorithm used to a simpler, faster one. +- Change pretty printer layout algorithm used. The layoutSmart algorithm appears to be extremely slow with some command line sets, to the point where the program appears to hang. diff --git a/optparse-applicative.cabal b/optparse-applicative.cabal index 59f64351..d9a20768 100644 --- a/optparse-applicative.cabal +++ b/optparse-applicative.cabal @@ -1,5 +1,5 @@ name: optparse-applicative -version: 0.18.0.0 +version: 0.18.1.0 synopsis: Utilities and combinators for parsing command line options description: optparse-applicative is a haskell library for parsing options From 146a3c46a532a75c84d4c882c9749f176b579bcb Mon Sep 17 00:00:00 2001 From: Kevin Quick Date: Fri, 30 Jun 2023 15:55:52 -0700 Subject: [PATCH 42/52] Update README for switch to prettyprinter from ansi-wl-pprint. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 34abc1e7..d42a682a 100644 --- a/README.md +++ b/README.md @@ -721,8 +721,8 @@ The `progDesc`, `header`, and `footer` functions can be used to specify a brief description or tagline for the program, and detailed information surrounding the generated option and command descriptions. -Internally we actually use the [ansi-wl-pprint][ansi-wl-pprint] -library, and one can use the `headerDoc` combinator and friends if +Internally we actually use the [prettyprinter][prettyprinter] +library, and one can supply either text or prettyprinter `Doc` elements if additional customisation is required. To display the usage text, the user may type `--help` if the `helper` @@ -1030,4 +1030,4 @@ simplified implementation. [parsec]: http://hackage.haskell.org/package/parsec [status]: https://github.com/pcapriotti/optparse-applicative/actions/workflows/haskell-ci.yml [status-png]: https://github.com/pcapriotti/optparse-applicative/workflows/Haskell-CI/badge.svg - [ansi-wl-pprint]: http://hackage.haskell.org/package/ansi-wl-pprint + [prettyprinter]: http://hackage.haskell.org/package/prettyprinter From c6cc6122b239d8df6e1864e12cba857f16ebf2a6 Mon Sep 17 00:00:00 2001 From: Huw Campbell Date: Sun, 6 Aug 2023 09:01:03 +1000 Subject: [PATCH 43/52] Update README.md The ellipsis for some and many arguments is inserted by the help text generator; so shouldn't be included in the metavar in the readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d42a682a..8c61e555 100644 --- a/README.md +++ b/README.md @@ -509,7 +509,7 @@ number of arguments, combine the `argument` builder with either the `many` or `some` combinator: ```haskell -some (argument str (metavar "FILES...")) +some (argument str (metavar "FILES")) ``` Note that arguments starting with `-` are considered options by From 659c01810c869e11a5fd876db434d3f482e8f33a Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 23 Feb 2021 18:50:07 +0100 Subject: [PATCH 44/52] Control addition of space after completion in bash and zsh (cherry picked from commit 7c3b764eae24ce8872db5e96f2ae04e4b4b5d0fd) --- CHANGELOG.md | 7 ++ src/Options/Applicative.hs | 2 + src/Options/Applicative/BashCompletion.hs | 109 ++++++++++++++----- src/Options/Applicative/Builder/Completer.hs | 4 +- src/Options/Applicative/Types.hs | 35 +++++- tests/test.hs | 19 ++-- 6 files changed, 140 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d397e8c2..dadbd9f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## Next + +- Add `mkCompleterWithOptions`, allowing completers to + request that no space is added after the completion. + This is useful in situations where not all completions + can be computed efficiently, or when they are too many. + ## Version 0.18.1.0 (29 May 2023) - Change pretty printer layout algorithm used. diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs index fa042f31..4d84e033 100644 --- a/src/Options/Applicative.hs +++ b/src/Options/Applicative.hs @@ -214,6 +214,8 @@ module Options.Applicative ( -- convenience, to use 'bashCompleter' and 'listCompleter' as a 'Mod'. Completer, mkCompleter, + CompletionItem(..), + mkCompleterWithOptions, listIOCompleter, listCompleter, diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index e4b6356c..2f53f9b6 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -73,9 +73,9 @@ bashCompletionParser pinfo pprefs = complParser bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Richness -> [String] -> Int -> String -> IO [String] bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl pprefs of Just (Left (SomeParser p, a)) - -> list_options a p + -> render_items <$> list_options a p Just (Right c) - -> run_completer c + -> render_items <$> run_completer c Nothing -> return [] where @@ -100,12 +100,12 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre opt_completions argPolicy reachability opt = case optMain opt of OptReader ns _ _ | argPolicy /= AllPositionals - -> return . add_opt_help opt $ show_names ns + -> return . fmap defaultCompletionItem . add_opt_help opt $ show_names ns | otherwise -> return [] FlagReader ns _ | argPolicy /= AllPositionals - -> return . add_opt_help opt $ show_names ns + -> return . fmap defaultCompletionItem . add_opt_help opt $ show_names ns | otherwise -> return [] ArgReader rdr @@ -117,7 +117,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre | argumentIsUnreachable reachability -> return [] | otherwise - -> return . with_cmd_help $ filter (is_completion . fst) ns + -> return . fmap defaultCompletionItem . with_cmd_help $ filter (is_completion . fst) ns -- When doing enriched completions, add any help specified -- to the completion variables (tab separated). @@ -154,7 +154,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre [x] -> x x : _ -> x ++ "..." - run_completer :: Completer -> IO [String] + run_completer :: Completer -> IO [CompletionItem] run_completer c = runCompleter c (fromMaybe "" (listToMaybe ws'')) (ws', ws'') = splitAt i ws @@ -165,12 +165,22 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre w:_ -> isPrefixOf w _ -> const True + render_items :: [CompletionItem] -> [String] + render_items = concatMap render_item + + render_item :: CompletionItem -> [String] + render_item CompletionItem { ciOptions = opts, ciValue = val } = + [ "%addspace" | cioAddSpace opts ] + ++ ["%value", val] + -- | Generated bash shell completion script bashCompletionScript :: String -> String -> String bashCompletionScript prog progn = unlines + -- compopt: see complete -o at https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html [ "_" ++ progn ++ "()" , "{" , " local CMDLINE" + , " local value_mode=false" , " local IFS=$'\\n'" , " CMDLINE=(--bash-completion-index $COMP_CWORD)" , "" @@ -178,7 +188,23 @@ bashCompletionScript prog progn = unlines , " CMDLINE=(${CMDLINE[@]} --bash-completion-word $arg)" , " done" , "" - , " COMPREPLY=( $(" ++ prog ++ " \"${CMDLINE[@]}\") )" + , " compopt -o nospace" + , " COMPREPLY=()" + , " for ln in $(" ++ prog ++ " \"${CMDLINE[@]}\"); do" + , " if $value_mode; then" + , " COMPREPLY+=($ln)" + , " value_mode=false" + , " else" + , " case $ln in" + , " %value)" + , " value_mode=true" + , " ;;" + , " %addspace)" + , " compopt +o nospace" + , " ;;" + , " esac" + , " fi" + , " done" , "}" , "" , "complete -o filenames -F _" ++ progn ++ " " ++ progn ] @@ -214,11 +240,23 @@ fishCompletionScript prog progn = unlines , " for arg in $cl" , " set tmpline $tmpline --bash-completion-word $arg" , " end" - , " for opt in (" ++ prog ++ " $tmpline)" - , " if test -d $opt" - , " echo -E \"$opt/\"" + , " set -l value_mode false" + , " for ln in (" ++ prog ++ " $tmpline)" + , " if $value_mode" + , " if test -d $ln" + , " echo -E \"$ln/\"" + , " else" + , " echo -E \"$ln\"" + , " end" + , " set value_mode false" , " else" - , " echo -E \"$opt\"" + , " switch $ln" + , " case '%value'" + , " set value_mode true" + -- Ignore %addspace, because fish does not let us remove the end + -- space. Dynamic control has not been implemented as of 2020, see + -- https://github.com/fish-shell/fish-shell/issues/6928#issuecomment-618012509 + , " end" , " end" , " end" , "end" @@ -229,11 +267,15 @@ fishCompletionScript prog progn = unlines -- | Generated zsh shell completion script zshCompletionScript :: String -> String -> String zshCompletionScript prog progn = unlines + -- compadd: http://zsh.sourceforge.net/Doc/Release/Completion-Widgets.html#Completion-Builtin-Commands [ "#compdef " ++ progn , "" , "local request" , "local completions" , "local word" + , "local value_mode=false" + , "local addspace=false" + , "local files=false" , "local index=$((CURRENT - 1))" , "" , "request=(--bash-completion-enriched --bash-completion-index $index)" @@ -241,24 +283,41 @@ zshCompletionScript prog progn = unlines , " request=(${request[@]} --bash-completion-word $arg)" , "done" , "" - , "IFS=$'\\n' completions=($( " ++ prog ++ " \"${request[@]}\" ))" + , "IFS=$'\\n' completionLines=($( " ++ prog ++ " \"${request[@]}\" ))" + , "" + , "for word in $completionLines; do" + , " if $value_mode; then" + , " local -a parts args" , "" - , "for word in $completions; do" - , " local -a parts" + , " # Split the line at a tab if there is one." + , " IFS=$'\\t' parts=($( echo $word ))" , "" - , " # Split the line at a tab if there is one." - , " IFS=$'\\t' parts=($( echo $word ))" + , " if $addspace; then" + , " args+=( -S' ' )" + , " fi" , "" - , " if [[ -n $parts[2] ]]; then" - , " if [[ $word[1] == \"-\" ]]; then" - , " local desc=(\"$parts[1] ($parts[2])\")" - , " compadd -d desc -- $parts[1]" - , " else" - , " local desc=($(print -f \"%-019s -- %s\" $parts[1] $parts[2]))" - , " compadd -l -d desc -- $parts[1]" - , " fi" + , " if [[ -n $parts[2] ]]; then" + , " if [[ $word[1] == \"-\" ]]; then" + , " local desc=(\"$parts[1] ($parts[2])\")" + , " compadd $args -d desc -- $parts[1]" + , " else" + , " local desc=($(print -f \"%-019s -- %s\" $parts[1] $parts[2]))" + , " compadd $args -l -d desc -- $parts[1]" + , " fi" + , " else" + , " compadd $args -f -- $word" + , " fi" + , " value_mode=false" + , " addspace=false" , " else" - , " compadd -f -- $word" + , " case $word in" + , " %value)" + , " value_mode=true" + , " ;;" + , " %addspace)" + , " addspace=true" + , " ;;" + , " esac" , " fi" , "done" ] diff --git a/src/Options/Applicative/Builder/Completer.hs b/src/Options/Applicative/Builder/Completer.hs index 5da556e7..4161cd1b 100644 --- a/src/Options/Applicative/Builder/Completer.hs +++ b/src/Options/Applicative/Builder/Completer.hs @@ -22,7 +22,7 @@ import Options.Applicative.Types -- | Create a 'Completer' from an IO action listIOCompleter :: IO [String] -> Completer -listIOCompleter ss = Completer $ \s -> +listIOCompleter ss = mkCompleter $ \s -> filter (isPrefixOf s) <$> ss -- | Create a 'Completer' from a constant @@ -38,7 +38,7 @@ listCompleter = listIOCompleter . pure -- for a complete list. bashCompleter :: String -> Completer #ifdef MIN_VERSION_process -bashCompleter action = Completer $ \word -> do +bashCompleter action = mkCompleter $ \word -> do let cmd = unwords ["compgen", "-A", action, "--", requote word] result <- tryIO $ readProcess "bash" ["-c", cmd] "" return . lines . either (const []) id $ result diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index a556f2a8..e1647d0b 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -22,6 +22,10 @@ module Options.Applicative.Types ( ParserM(..), Completer(..), mkCompleter, + mkCompleterWithOptions, + CompletionItem(..), + defaultCompletionItem, + CompletionItemOptions(..), CompletionResult(..), ParserFailure(..), ParserResult(..), @@ -306,13 +310,40 @@ instance Alternative Parser where many = fromM . manyM some = fromM . someM +data CompletionItem = CompletionItem { + ciOptions :: CompletionItemOptions, + ciValue :: String +} +defaultCompletionItem :: String -> CompletionItem +defaultCompletionItem = CompletionItem mempty + +data CompletionItemOptions = CompletionItemOptions { + -- | Whether to add a space after the completion. Defaults to 'True'. + -- + -- Set this value to 'False' if the completion is only a prefix of the final + -- valid values. + cioAddSpace :: Bool +} +instance Semigroup CompletionItemOptions where + a <> b = + CompletionItemOptions { + cioAddSpace = cioAddSpace a && cioAddSpace b + } +instance Monoid CompletionItemOptions where + mempty = CompletionItemOptions True + mappend = (<>) + -- | A shell complete function. newtype Completer = Completer - { runCompleter :: String -> IO [String] } + { runCompleter :: String -> IO [CompletionItem] } -- | Smart constructor for a 'Completer' mkCompleter :: (String -> IO [String]) -> Completer -mkCompleter = Completer +mkCompleter f = Completer (fmap (map (CompletionItem mempty)) . f) + +-- | Smart constructor for a 'Completer' +mkCompleterWithOptions :: (String -> IO [CompletionItem]) -> Completer +mkCompleterWithOptions = Completer instance Semigroup Completer where (Completer c1) <> (Completer c2) = diff --git a/tests/test.hs b/tests/test.hs index 4c888dca..128c4c36 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -318,6 +318,11 @@ prop_ambiguous = once $ result = execParserPure (prefs disambiguate) i ["--ba"] in assertError result (\_ -> property succeeded) +completionValues :: [String] -> [String] +completionValues ("%value" : v : more) = v : completionValues more +completionValues (('%':_) : more) = completionValues more +completionValues (a:_) = error ("Unexpected non-% line in completions: " <> a) +completionValues [] = [] prop_disambiguate_in_same_subparsers :: Property prop_disambiguate_in_same_subparsers = once $ @@ -371,7 +376,7 @@ prop_completion = once . ioProperty $ in case result of CompletionInvoked (CompletionResult err) -> do completions <- lines <$> err "test" - return $ ["--foo", "--bar"] === completions + return $ ["--foo", "--bar"] === completionValues completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -386,7 +391,7 @@ prop_completion_opt_after_double_dash = once . ioProperty $ , "--bash-completion-word", "--"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- completionValues . lines <$> err "test" return $ ["bar"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -401,7 +406,7 @@ prop_completion_only_reachable = once . ioProperty $ result = run i ["--bash-completion-index", "0"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- completionValues . lines <$> err "test" return $ ["reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -418,7 +423,7 @@ prop_completion_only_reachable_deep = once . ioProperty $ , "--bash-completion-word", "seen" ] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- completionValues . lines <$> err "test" return $ ["now-reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -433,7 +438,7 @@ prop_completion_multi = once . ioProperty $ , "--bash-completion-word", "nope" ] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- completionValues . lines <$> err "test" return $ ["reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -447,7 +452,7 @@ prop_completion_rich = once . ioProperty $ result = run i ["--bash-completion-enriched", "--bash-completion-index", "0"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- completionValues . lines <$> err "test" return $ ["--foo\tFo?", "--bar\tBa?"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -464,7 +469,7 @@ prop_completion_rich_lengths = once . ioProperty $ , "--bash-completion-command-desc-length=30"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- lines <$> err "test" + completions <- completionValues . lines <$> err "test" return $ ["--foo\tFoo...", "--bar\tBar..."] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed From 133c5632fd36db82a0e23fc88c873f5f242b116c Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 23 Feb 2021 19:14:52 +0100 Subject: [PATCH 45/52] Control special file/directory behavior in completions (cherry picked from commit ba0d981f4344134ab1363f92896099fc0e392152) --- src/Options/Applicative/BashCompletion.hs | 10 +++++++++- src/Options/Applicative/Types.hs | 12 +++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 2f53f9b6..49ad3c64 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -171,6 +171,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre render_item :: CompletionItem -> [String] render_item CompletionItem { ciOptions = opts, ciValue = val } = [ "%addspace" | cioAddSpace opts ] + ++ [ "%files" | cioFiles opts ] ++ ["%value", val] -- | Generated bash shell completion script @@ -188,7 +189,7 @@ bashCompletionScript prog progn = unlines , " CMDLINE=(${CMDLINE[@]} --bash-completion-word $arg)" , " done" , "" - , " compopt -o nospace" + , " compopt -o nospace +o filenames" , " COMPREPLY=()" , " for ln in $(" ++ prog ++ " \"${CMDLINE[@]}\"); do" , " if $value_mode; then" @@ -202,6 +203,9 @@ bashCompletionScript prog progn = unlines , " %addspace)" , " compopt +o nospace" , " ;;" + , " %files)" + , " compopt -o filenames" + , " ;;" , " esac" , " fi" , " done" @@ -309,6 +313,7 @@ zshCompletionScript prog progn = unlines , " fi" , " value_mode=false" , " addspace=false" + , " files=false" , " else" , " case $word in" , " %value)" @@ -317,6 +322,9 @@ zshCompletionScript prog progn = unlines , " %addspace)" , " addspace=true" , " ;;" + , " %files)" + , " files=true" + , " ;;" , " esac" , " fi" , "done" diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index e1647d0b..1541989e 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -322,15 +322,21 @@ data CompletionItemOptions = CompletionItemOptions { -- -- Set this value to 'False' if the completion is only a prefix of the final -- valid values. - cioAddSpace :: Bool + cioAddSpace :: Bool, + + -- | Whether to treat the completions as file names (if they exists) and + -- add a trailing slash to completions that are directories. + -- Defaults to 'True' + cioFiles :: Bool } instance Semigroup CompletionItemOptions where a <> b = CompletionItemOptions { - cioAddSpace = cioAddSpace a && cioAddSpace b + cioAddSpace = cioAddSpace a && cioAddSpace b, + cioFiles = cioFiles a && cioFiles b } instance Monoid CompletionItemOptions where - mempty = CompletionItemOptions True + mempty = CompletionItemOptions True True mappend = (<>) -- | A shell complete function. From fa008aa2d50f405b7e9e21628429f38ab4d04430 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Mar 2021 16:58:58 +0100 Subject: [PATCH 46/52] Add completion protocol version for backcompat (cherry picked from commit af10154bd6802ba024c9f3bacdcd32204530cca1) --- src/Options/Applicative/BashCompletion.hs | 48 +++++++++++++++++------ tests/test.hs | 20 ++++------ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 49ad3c64..cff22498 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -24,6 +24,16 @@ import Options.Applicative.Types import Options.Applicative.Help.Pretty import Options.Applicative.Help.Chunk +-- | Which features are supported by the calling shell +-- completion integration script +data Features = Features + { richness :: Richness + , protocolVersion :: Int + } + +currentProtocolVerson :: Int +currentProtocolVerson = 1 + -- | Provide basic or rich command completions data Richness = Standard @@ -42,6 +52,19 @@ bashCompletionParser pinfo pprefs = complParser CompletionResult $ \progn -> unlines <$> opts progn + featuresParser :: Parser Features + featuresParser = Features <$> richnessParser <*> protocolVersionParser + + protocolVersionParser :: Parser Int + protocolVersionParser = option auto (long "optparse-completion-version" `mappend` value 0) + + richnessParser :: Parser Richness + richnessParser = + flag' Enriched (long "bash-completion-enriched" `mappend` internal) + <*> option auto (long "bash-completion-option-desc-length" `mappend` internal `mappend` value 40) + <*> option auto (long "bash-completion-command-desc-length" `mappend` internal `mappend` value 40) + <|> pure Standard + scriptRequest = CompletionResult . fmap pure @@ -53,14 +76,11 @@ bashCompletionParser pinfo pprefs = complParser -- the `desc-length` options. -- zsh commands can go on a single line, so they might -- want to be longer. - <$> ( flag' Enriched (long "bash-completion-enriched" `mappend` internal) - <*> option auto (long "bash-completion-option-desc-length" `mappend` internal `mappend` value 40) - <*> option auto (long "bash-completion-command-desc-length" `mappend` internal `mappend` value 40) - <|> pure Standard - ) + <$> featuresParser <*> (many . strOption) (long "bash-completion-word" `mappend` internal) - <*> option auto (long "bash-completion-index" `mappend` internal) ) + <*> option auto (long "bash-completion-index" `mappend` internal) + ) , scriptRequest . bashCompletionScript <$> strOption (long "bash-completion-script" `mappend` internal) @@ -70,8 +90,8 @@ bashCompletionParser pinfo pprefs = complParser strOption (long "zsh-completion-script" `mappend` internal) ] -bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Richness -> [String] -> Int -> String -> IO [String] -bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl pprefs of +bashCompletionQuery :: ParserInfo a -> ParserPrefs -> Features -> [String] -> Int -> String -> IO [String] +bashCompletionQuery pinfo pprefs features ws i _ = case runCompletion compl pprefs of Just (Left (SomeParser p, a)) -> render_items <$> list_options a p Just (Right c) @@ -122,7 +142,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- When doing enriched completions, add any help specified -- to the completion variables (tab separated). add_opt_help :: Functor f => Option a -> f String -> f String - add_opt_help opt = case richness of + add_opt_help opt = case richness features of Standard -> id Enriched len _ -> @@ -134,7 +154,7 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- to the completion variables (tab separated). with_cmd_help :: Functor f => f (String, ParserInfo a) -> f String with_cmd_help = - case richness of + case richness features of Standard -> fmap fst Enriched _ len -> @@ -169,6 +189,8 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre render_items = concatMap render_item render_item :: CompletionItem -> [String] + render_item CompletionItem { ciValue = val } + | protocolVersion features < 1 = [val] render_item CompletionItem { ciOptions = opts, ciValue = val } = [ "%addspace" | cioAddSpace opts ] ++ [ "%files" | cioFiles opts ] @@ -183,7 +205,7 @@ bashCompletionScript prog progn = unlines , " local CMDLINE" , " local value_mode=false" , " local IFS=$'\\n'" - , " CMDLINE=(--bash-completion-index $COMP_CWORD)" + , " CMDLINE=(--bash-completion-index $COMP_CWORD --optparse-completion-version " ++ show currentProtocolVerson ++ ")" , "" , " for arg in ${COMP_WORDS[@]}; do" , " CMDLINE=(${CMDLINE[@]} --bash-completion-word $arg)" @@ -240,7 +262,7 @@ fishCompletionScript prog progn = unlines , " # Hack around fish issue #3934" , " set -l cn (commandline --tokenize --cut-at-cursor --current-process)" , " set -l cn (count $cn)" - , " set -l tmpline --bash-completion-enriched --bash-completion-index $cn" + , " set -l tmpline --bash-completion-enriched --bash-completion-index $cn --optparse-completion-version " ++ show currentProtocolVerson , " for arg in $cl" , " set tmpline $tmpline --bash-completion-word $arg" , " end" @@ -282,7 +304,7 @@ zshCompletionScript prog progn = unlines , "local files=false" , "local index=$((CURRENT - 1))" , "" - , "request=(--bash-completion-enriched --bash-completion-index $index)" + , "request=(--bash-completion-enriched --bash-completion-index $index --optparse-completion-version " ++ show currentProtocolVerson ++ ")" , "for arg in ${words[@]}; do" , " request=(${request[@]} --bash-completion-word $arg)" , "done" diff --git a/tests/test.hs b/tests/test.hs index 128c4c36..57f4ead4 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -318,12 +318,6 @@ prop_ambiguous = once $ result = execParserPure (prefs disambiguate) i ["--ba"] in assertError result (\_ -> property succeeded) -completionValues :: [String] -> [String] -completionValues ("%value" : v : more) = v : completionValues more -completionValues (('%':_) : more) = completionValues more -completionValues (a:_) = error ("Unexpected non-% line in completions: " <> a) -completionValues [] = [] - prop_disambiguate_in_same_subparsers :: Property prop_disambiguate_in_same_subparsers = once $ let p0 = subparser (command "oranges" (info (pure "oranges") idm) <> command "apples" (info (pure "apples") idm) <> metavar "B") @@ -376,7 +370,7 @@ prop_completion = once . ioProperty $ in case result of CompletionInvoked (CompletionResult err) -> do completions <- lines <$> err "test" - return $ ["--foo", "--bar"] === completionValues completions + return $ ["--foo", "--bar"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -391,7 +385,7 @@ prop_completion_opt_after_double_dash = once . ioProperty $ , "--bash-completion-word", "--"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- completionValues . lines <$> err "test" + completions <- lines <$> err "test" return $ ["bar"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -406,7 +400,7 @@ prop_completion_only_reachable = once . ioProperty $ result = run i ["--bash-completion-index", "0"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- completionValues . lines <$> err "test" + completions <- lines <$> err "test" return $ ["reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -423,7 +417,7 @@ prop_completion_only_reachable_deep = once . ioProperty $ , "--bash-completion-word", "seen" ] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- completionValues . lines <$> err "test" + completions <- lines <$> err "test" return $ ["now-reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -438,7 +432,7 @@ prop_completion_multi = once . ioProperty $ , "--bash-completion-word", "nope" ] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- completionValues . lines <$> err "test" + completions <- lines <$> err "test" return $ ["reachable"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -452,7 +446,7 @@ prop_completion_rich = once . ioProperty $ result = run i ["--bash-completion-enriched", "--bash-completion-index", "0"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- completionValues . lines <$> err "test" + completions <- lines <$> err "test" return $ ["--foo\tFo?", "--bar\tBa?"] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed @@ -469,7 +463,7 @@ prop_completion_rich_lengths = once . ioProperty $ , "--bash-completion-command-desc-length=30"] in case result of CompletionInvoked (CompletionResult err) -> do - completions <- completionValues . lines <$> err "test" + completions <- lines <$> err "test" return $ ["--foo\tFoo...", "--bar\tBar..."] === completions Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed From 77d724897b6349328274aa28e26e04c1fa83db48 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Mar 2021 17:25:13 +0100 Subject: [PATCH 47/52] Test v1 completion protocol (cherry picked from commit 9934680993727f41dc2e6867d92f0d6074d53725) --- src/Options/Applicative.hs | 1 + src/Options/Applicative/BashCompletion.hs | 6 +++++ tests/test.hs | 30 +++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs index 4d84e033..ea4f4b6e 100644 --- a/src/Options/Applicative.hs +++ b/src/Options/Applicative.hs @@ -215,6 +215,7 @@ module Options.Applicative ( Completer, mkCompleter, CompletionItem(..), + CompletionItemOptions(..), mkCompleterWithOptions, listIOCompleter, diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index cff22498..20874994 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -31,6 +31,12 @@ data Features = Features , protocolVersion :: Int } +-- | Version of the output format that the shell integration script +-- expects optparse-applicative to write to stdout. +-- +-- Version increases should be rare, because most changes +-- can be handled by adding a new % keyword. Unknown keywords +-- are ignored by the shell integration scripts. currentProtocolVerson :: Int currentProtocolVerson = 1 diff --git a/tests/test.hs b/tests/test.hs index 57f4ead4..2507276a 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -468,6 +468,36 @@ prop_completion_rich_lengths = once . ioProperty $ Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed +prop_completion_v1_default :: Property +prop_completion_v1_default = once . ioProperty $ + let p :: Parser String + p = strArgument (completer (mkCompleterWithOptions (pure (pure [CompletionItem mempty "reachable"])))) + i = info p idm + result = run i [ "--optparse-completion-version", "1" + , "--bash-completion-index=0" + ] + in case result of + CompletionInvoked (CompletionResult err) -> do + completions <- lines <$> err "test" + return $ ["%addspace", "%files", "%value", "reachable"] === completions + Failure _ -> return $ counterexample "unexpected failure" failed + Success val -> return $ counterexample ("unexpected result " ++ show val) failed + +prop_completion_v1_minimal :: Property +prop_completion_v1_minimal = once . ioProperty $ + let p :: Parser String + p = strArgument (completer (mkCompleterWithOptions (pure (pure [CompletionItem (mempty { cioAddSpace = False, cioFiles = False }) "reachable"])))) + i = info p idm + result = run i [ "--optparse-completion-version", "1" + , "--bash-completion-index=0" + ] + in case result of + CompletionInvoked (CompletionResult err) -> do + completions <- lines <$> err "test" + return $ ["%value", "reachable"] === completions + Failure _ -> return $ counterexample "unexpected failure" failed + Success val -> return $ counterexample ("unexpected result " ++ show val) failed + prop_bind_usage :: Property prop_bind_usage = once $ let p :: Parser [String] From 15ce2aae46196c5e787dff7002a314debc817c57 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 1 Mar 2021 17:55:35 +0100 Subject: [PATCH 48/52] CompletionItemOptions: make mempty minimal, add legacyCompletionItemOptions (cherry picked from commit 6df28c2547ccfa012dfa8bb5afbebf9e3c72536b) --- src/Options/Applicative/BashCompletion.hs | 6 +-- src/Options/Applicative/Types.hs | 48 +++++++++++++++++------ tests/test.hs | 8 ++-- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 20874994..02eb7af4 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -126,12 +126,12 @@ bashCompletionQuery pinfo pprefs features ws i _ = case runCompletion compl ppre opt_completions argPolicy reachability opt = case optMain opt of OptReader ns _ _ | argPolicy /= AllPositionals - -> return . fmap defaultCompletionItem . add_opt_help opt $ show_names ns + -> return . fmap legacyCompletionItem . add_opt_help opt $ show_names ns | otherwise -> return [] FlagReader ns _ | argPolicy /= AllPositionals - -> return . fmap defaultCompletionItem . add_opt_help opt $ show_names ns + -> return . fmap legacyCompletionItem . add_opt_help opt $ show_names ns | otherwise -> return [] ArgReader rdr @@ -143,7 +143,7 @@ bashCompletionQuery pinfo pprefs features ws i _ = case runCompletion compl ppre | argumentIsUnreachable reachability -> return [] | otherwise - -> return . fmap defaultCompletionItem . with_cmd_help $ filter (is_completion . fst) ns + -> return . fmap legacyCompletionItem . with_cmd_help $ filter (is_completion . fst) ns -- When doing enriched completions, add any help specified -- to the completion variables (tab separated). diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index 1541989e..2ec355f7 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -21,11 +21,12 @@ module Options.Applicative.Types ( Parser(..), ParserM(..), Completer(..), - mkCompleter, mkCompleterWithOptions, + mkCompleter, CompletionItem(..), - defaultCompletionItem, + legacyCompletionItem, CompletionItemOptions(..), + legacyCompletionItemOptions, CompletionResult(..), ParserFailure(..), ParserResult(..), @@ -314,43 +315,64 @@ data CompletionItem = CompletionItem { ciOptions :: CompletionItemOptions, ciValue :: String } -defaultCompletionItem :: String -> CompletionItem -defaultCompletionItem = CompletionItem mempty +-- | A set of defaults that includes the bells and whistles that +-- were previously added by the shell. +-- +-- For the minimal shell behavior, use @'CompletionItem' mempty@ +-- +-- This adds spaces to unambiguous completions (@'cioAddSpace' = True@) and +-- treats the completions as files (@'cioFiles' = True@). +legacyCompletionItem :: String -> CompletionItem +legacyCompletionItem = CompletionItem CompletionItemOptions { cioAddSpace = True, cioFiles = True } data CompletionItemOptions = CompletionItemOptions { - -- | Whether to add a space after the completion. Defaults to 'True'. + -- | Whether to add a space after the completion. -- -- Set this value to 'False' if the completion is only a prefix of the final -- valid values. + -- + -- 'mempty': 'False'. + -- + -- 'legacyCompletionItemOptions': 'True'. + -- cioAddSpace :: Bool, -- | Whether to treat the completions as file names (if they exists) and -- add a trailing slash to completions that are directories. - -- Defaults to 'True' + -- + -- 'mempty': 'False'. + -- + -- 'legacyCompletionItemOptions': 'True'. + -- cioFiles :: Bool } +-- | Combines field-wise. Uses '||' for fields that have 'False' for 'mempty'. instance Semigroup CompletionItemOptions where a <> b = CompletionItemOptions { - cioAddSpace = cioAddSpace a && cioAddSpace b, - cioFiles = cioFiles a && cioFiles b + cioAddSpace = cioAddSpace a || cioAddSpace b, + cioFiles = cioFiles a || cioFiles b } +-- | 'mempty' is minimal. See per-field docs. instance Monoid CompletionItemOptions where - mempty = CompletionItemOptions True True + mempty = CompletionItemOptions False False mappend = (<>) +legacyCompletionItemOptions :: CompletionItemOptions +legacyCompletionItemOptions = CompletionItemOptions { cioAddSpace = True, cioFiles = True } + -- | A shell complete function. newtype Completer = Completer { runCompleter :: String -> IO [CompletionItem] } --- | Smart constructor for a 'Completer' -mkCompleter :: (String -> IO [String]) -> Completer -mkCompleter f = Completer (fmap (map (CompletionItem mempty)) . f) - -- | Smart constructor for a 'Completer' mkCompleterWithOptions :: (String -> IO [CompletionItem]) -> Completer mkCompleterWithOptions = Completer +-- | Smart constructor for a 'Completer' via 'legacyCompletionItem'. +mkCompleter :: (String -> IO [String]) -> Completer +mkCompleter f = Completer (fmap (map legacyCompletionItem) . f) + instance Semigroup Completer where (Completer c1) <> (Completer c2) = Completer $ \s -> (++) <$> c1 s <*> c2 s diff --git a/tests/test.hs b/tests/test.hs index 2507276a..628837aa 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -468,10 +468,10 @@ prop_completion_rich_lengths = once . ioProperty $ Failure _ -> return $ counterexample "unexpected failure" failed Success val -> return $ counterexample ("unexpected result " ++ show val) failed -prop_completion_v1_default :: Property -prop_completion_v1_default = once . ioProperty $ +prop_completion_v1_legacy :: Property +prop_completion_v1_legacy = once . ioProperty $ let p :: Parser String - p = strArgument (completer (mkCompleterWithOptions (pure (pure [CompletionItem mempty "reachable"])))) + p = strArgument (completer (mkCompleterWithOptions (pure (pure [legacyCompletionItem "reachable"])))) i = info p idm result = run i [ "--optparse-completion-version", "1" , "--bash-completion-index=0" @@ -486,7 +486,7 @@ prop_completion_v1_default = once . ioProperty $ prop_completion_v1_minimal :: Property prop_completion_v1_minimal = once . ioProperty $ let p :: Parser String - p = strArgument (completer (mkCompleterWithOptions (pure (pure [CompletionItem (mempty { cioAddSpace = False, cioFiles = False }) "reachable"])))) + p = strArgument (completer (mkCompleterWithOptions (pure (pure [CompletionItem mempty "reachable"])))) i = info p idm result = run i [ "--optparse-completion-version", "1" , "--bash-completion-index=0" From 2de7c8db01692e88f938ca2f031c4334f60e1590 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 5 Mar 2021 13:58:55 +0100 Subject: [PATCH 49/52] Add biOption for two-argument options --- src/Options/Applicative.hs | 6 ++ src/Options/Applicative/BashCompletion.hs | 9 ++ src/Options/Applicative/Builder.hs | 41 +++++++- src/Options/Applicative/Builder/Internal.hs | 15 +++ src/Options/Applicative/Common.hs | 29 +++++- src/Options/Applicative/Extra.hs | 10 ++ src/Options/Applicative/Help/Core.hs | 3 + src/Options/Applicative/Types.hs | 35 ++++--- tests/test.hs | 104 ++++++++++++++++++++ 9 files changed, 234 insertions(+), 18 deletions(-) diff --git a/src/Options/Applicative.hs b/src/Options/Applicative.hs index fa042f31..15b91770 100644 --- a/src/Options/Applicative.hs +++ b/src/Options/Applicative.hs @@ -65,6 +65,8 @@ module Options.Applicative ( strOption, option, + biOption, + strArgument, argument, @@ -94,6 +96,7 @@ module Options.Applicative ( showDefaultWith, showDefault, metavar, + metavar2, noArgError, hidden, internal, @@ -103,6 +106,7 @@ module Options.Applicative ( completeWith, action, completer, + completer2, idm, mappend, @@ -113,8 +117,10 @@ module Options.Applicative ( HasName, HasCompleter, + HasCompleter2, HasValue, HasMetavar, + HasMetavar2, -- ** Readers -- -- | A reader is used by the 'option' and 'argument' builders to parse diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index e4b6356c..96926c61 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE RankNTypes #-} -- | You don't need to import this module to enable bash completion. -- -- See @@ -97,12 +99,19 @@ bashCompletionQuery pinfo pprefs richness ws i _ = case runCompletion compl ppre -- -- For options and flags, ensure that the user -- hasn't disabled them with `--`. + opt_completions :: forall a. ArgPolicy -> ArgumentReachability -> Option a -> IO [String] opt_completions argPolicy reachability opt = case optMain opt of OptReader ns _ _ | argPolicy /= AllPositionals -> return . add_opt_help opt $ show_names ns | otherwise -> return [] + BiOptReader ns _ _ _ + | argPolicy /= AllPositionals + -> return . add_opt_help opt $ show_names ns + | otherwise + -> return [] + MapReader _f optr -> opt_completions argPolicy reachability (opt { optMain = optr }) FlagReader ns _ | argPolicy /= AllPositionals -> return . add_opt_help opt $ show_names ns diff --git a/src/Options/Applicative/Builder.hs b/src/Options/Applicative/Builder.hs index bc12b5f2..42e40962 100644 --- a/src/Options/Applicative/Builder.hs +++ b/src/Options/Applicative/Builder.hs @@ -28,6 +28,8 @@ module Options.Applicative.Builder ( strOption, option, + biOption, + -- * Modifiers short, long, @@ -37,6 +39,7 @@ module Options.Applicative.Builder ( showDefaultWith, showDefault, metavar, + metavar2, noArgError, ParseError(..), hidden, @@ -47,6 +50,7 @@ module Options.Applicative.Builder ( completeWith, action, completer, + completer2, idm, mappend, @@ -102,8 +106,10 @@ module Options.Applicative.Builder ( HasName, HasCompleter, + HasCompleter2, HasValue, - HasMetavar + HasMetavar, + HasMetavar2 ) where import Control.Applicative @@ -205,6 +211,13 @@ noArgError e = fieldMod $ \p -> p { optNoArgError = const e } metavar :: HasMetavar f => String -> Mod f a metavar var = optionMod $ \p -> p { propMetaVar = var } +-- | Specify a metavariable for the second argument of a 'biOption'. +-- +-- Metavariables have no effect on the actual parser, and only serve to specify +-- the symbolic name for an argument to be displayed in the help text. +metavar2 :: HasMetavar2 f => String -> Mod f a +metavar2 var = optionMod $ \p -> p { propMetaVar2 = var } + -- | Hide this option from the brief description. -- -- Use 'internal' to hide the option from the help text too. @@ -269,6 +282,14 @@ action = completer . bashCompleter completer :: HasCompleter f => Completer -> Mod f a completer f = fieldMod $ modCompleter (`mappend` f) +-- | Add a completer to the second argument of a 'biOption'. +-- +-- A completer is a function String -> IO String which, given a partial +-- argument, returns all possible completions for that argument. +completer2 :: HasCompleter2 f => Completer -> Mod f a +completer2 f = fieldMod $ modCompleter2 (`mappend` f) + + -- parsers -- -- | Builder for a command parser. The 'command' modifier can be used to @@ -375,10 +396,26 @@ option :: ReadM a -> Mod OptionFields a -> Parser a option r m = mkParser d g rdr where Mod f d g = metavar "ARG" `mappend` m - fields = f (OptionFields [] mempty ExpectsArgError) + fields = f (OptionFields [] mempty mempty ExpectsArgError) crdr = CReader (optCompleter fields) r rdr = OptReader (optNames fields) crdr (optNoArgError fields) +-- | Builder for a two-argument option using the given two readers. +-- +-- It should always have either a @long@ or +-- @short@ name specified in the modifiers (or both). +-- +-- > nameParser = option str ( long "name" <> short 'n' ) +-- +biOption :: ReadM a -> ReadM b -> Mod OptionFields (a, b) -> Parser (a, b) +biOption r r2 m = mkParser d g rdr + where + Mod f d g = metavar "ARG" `mappend` metavar2 "ARG" `mappend` m + fields = f (OptionFields [] mempty mempty ExpectsArgError2) + crdr = CReader (optCompleter fields) r + crdr2 = CReader (optCompleter2 fields) r2 + rdr = BiOptReader (optNames fields) crdr crdr2 (optNoArgError fields) + -- | Modifier for 'ParserInfo'. newtype InfoMod a = InfoMod { applyInfoMod :: ParserInfo a -> ParserInfo a } diff --git a/src/Options/Applicative/Builder/Internal.hs b/src/Options/Applicative/Builder/Internal.hs index 39ab8a49..129e90a8 100644 --- a/src/Options/Applicative/Builder/Internal.hs +++ b/src/Options/Applicative/Builder/Internal.hs @@ -3,8 +3,10 @@ module Options.Applicative.Builder.Internal ( Mod(..), HasName(..), HasCompleter(..), + HasCompleter2(..), HasValue(..), HasMetavar(..), + HasMetavar2(..), OptionFields(..), FlagFields(..), CommandFields(..), @@ -35,6 +37,7 @@ import Options.Applicative.Types data OptionFields a = OptionFields { optNames :: [OptName] , optCompleter :: Completer + , optCompleter2 :: Completer , optNoArgError :: String -> ParseError } data FlagFields a = FlagFields @@ -66,6 +69,12 @@ instance HasCompleter OptionFields where instance HasCompleter ArgumentFields where modCompleter f p = p { argCompleter = f (argCompleter p) } +class HasCompleter2 f where + modCompleter2 :: (Completer -> Completer) -> f a -> f a + +instance HasCompleter2 OptionFields where + modCompleter2 f p = p { optCompleter2 = f (optCompleter2 p) } + class HasValue f where -- this is just so that it is not necessary to specify the kind of f hasValueDummy :: f a -> () @@ -83,6 +92,11 @@ instance HasMetavar ArgumentFields where instance HasMetavar CommandFields where hasMetavarDummy _ = () +class HasMetavar2 f where + hasMetavar2Dummy :: f a -> () +instance HasMetavar2 OptionFields where + hasMetavar2Dummy _ = () + -- mod -- data DefaultProp a = DefaultProp @@ -145,6 +159,7 @@ instance Semigroup (Mod f a) where baseProps :: OptProperties baseProps = OptProperties { propMetaVar = "" + , propMetaVar2 = "" , propVisibility = Visible , propHelp = mempty , propShowDefault = Nothing diff --git a/src/Options/Applicative/Common.hs b/src/Options/Applicative/Common.hs index 91e03cd7..58852966 100644 --- a/src/Options/Applicative/Common.hs +++ b/src/Options/Applicative/Common.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE GADTs #-} {-# LANGUAGE Rank2Types #-} module Options.Applicative.Common ( -- * Option parsers @@ -68,6 +69,8 @@ showOption (OptShort n) = '-' : [n] optionNames :: OptReader a -> [OptName] optionNames (OptReader names _ _) = names +optionNames (BiOptReader names _ _ _) = names +optionNames (MapReader _f r) = optionNames r optionNames (FlagReader names _) = names optionNames _ = [] @@ -92,6 +95,23 @@ optMatches disambiguate opt (OptWord arg1 val) = case opt of put args' lift $ runReadM (withReadM (errorFor arg1) (crReader rdr)) arg' + BiOptReader names rdr rdr2 no_arg_err -> do + guard $ has_name arg1 names + Just $ do + args <- get + let mb_args = uncons $ maybeToList val ++ args + let missing_arg = missingArgP (no_arg_err $ showOption arg1) (crCompleter rdr) + (arg', args') <- maybe (lift missing_arg) return mb_args + let missing_arg2 = missingArgP (no_arg_err $ showOption arg1) (crCompleter rdr2) + (arg'', args'') <- maybe (lift missing_arg2) return (uncons args') + put args'' + lift $ do + a <- runReadM (withReadM (errorFor arg1) (crReader rdr)) arg' + b <- runReadM (withReadM (errorFor arg1) (crReader rdr2)) arg'' + pure (a, b) + + MapReader f r -> fmap f <$> optMatches disambiguate r (OptWord arg1 val) + FlagReader names x -> do guard $ has_name arg1 names -- #242 Flags/switches succeed incorrectly when given an argument. @@ -167,6 +187,10 @@ searchArg prefs arg = when (isArg (optMain opt)) cut case optMain opt of CmdReader _ cs -> do + let + cmdMatches _ + | prefDisambiguate prefs = snd <$> filter (isPrefixOf arg . fst) cs + | otherwise = maybeToList (lookup arg cs) subp <- hoistList (cmdMatches cs) case prefBacktrack prefs of NoBacktrack -> lift $ do @@ -184,11 +208,6 @@ searchArg prefs arg = fmap pure . lift . lift $ runReadM (crReader rdr) arg _ -> mzero - where - cmdMatches cs - | prefDisambiguate prefs = snd <$> filter (isPrefixOf arg . fst) cs - | otherwise = maybeToList (lookup arg cs) - stepParser :: MonadP m => ParserPrefs -> ArgPolicy -> String -> Parser a -> NondetT (StateT Args m) (Parser a) stepParser pprefs AllPositionals arg p = diff --git a/src/Options/Applicative/Extra.hs b/src/Options/Applicative/Extra.hs index 97ed572d..8b3f2721 100644 --- a/src/Options/Applicative/Extra.hs +++ b/src/Options/Applicative/Extra.hs @@ -1,4 +1,5 @@ {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE GADTs #-} module Options.Applicative.Extra ( -- * Extra parser utilities -- @@ -198,6 +199,7 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> UnknownError -> ExitFailure (infoFailureCode pinfo) MissingError {} -> ExitFailure (infoFailureCode pinfo) ExpectsArgError {} -> ExitFailure (infoFailureCode pinfo) + ExpectsArgError2 {}-> ExitFailure (infoFailureCode pinfo) UnexpectedError {} -> ExitFailure (infoFailureCode pinfo) ShowHelpText {} -> ExitSuccess InfoMsg {} -> ExitSuccess @@ -224,6 +226,7 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> else mempty + usage_help :: String -> [String] -> ParserInfo a -> ParserHelp usage_help progn names i = case msg of InfoMsg _ -> mempty @@ -253,6 +256,9 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> ExpectsArgError x -> stringChunk $ "The option `" ++ x ++ "` expects an argument." + ExpectsArgError2 x + -> stringChunk $ "The option `" ++ x ++ "` expects two arguments." + UnexpectedError arg _ -> stringChunk msg' where @@ -313,8 +319,12 @@ parserFailure pprefs pinfo msg ctx0 = ParserFailure $ \progn -> -- things the user could type. If it's a command -- reader also ensure that it can be immediately -- reachable from where the error was given. + opt_completions :: ArgumentReachability -> Option a -> [String] opt_completions reachability opt = case optMain opt of OptReader ns _ _ -> fmap showOption ns + BiOptReader ns _ _ _ -> + fmap showOption ns + MapReader _f r -> opt_completions reachability (opt { optMain = r }) FlagReader ns _ -> fmap showOption ns ArgReader _ -> [] CmdReader _ ns | argumentIsUnreachable reachability diff --git a/src/Options/Applicative/Help/Core.hs b/src/Options/Applicative/Help/Core.hs index ce89070f..76695b39 100644 --- a/src/Options/Applicative/Help/Core.hs +++ b/src/Options/Applicative/Help/Core.hs @@ -56,11 +56,14 @@ optDesc pprefs style _reachability opt = sort . optionNames . optMain $ opt meta = stringChunk $ optMetaVar opt + meta2 = + stringChunk $ optMetaVar2 opt descs = map (pretty . showOption) names descriptions = listToChunk (intersperse (descSep style) descs) desc + | not (isEmpty meta) && not (isEmpty meta2) = descriptions <<+>> meta <<+>> meta2 | prefHelpLongEquals pprefs && not (isEmpty meta) && any isLongName (safelast names) = descriptions <> stringChunk "=" <> meta | otherwise = diff --git a/src/Options/Applicative/Types.hs b/src/Options/Applicative/Types.hs index a556f2a8..a01a8b2a 100644 --- a/src/Options/Applicative/Types.hs +++ b/src/Options/Applicative/Types.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE CPP, Rank2Types, ExistentialQuantification #-} +{-# LANGUAGE CPP, Rank2Types, ExistentialQuantification, GADTs #-} module Options.Applicative.Types ( ParseError(..), ParserInfo(..), @@ -44,6 +44,7 @@ module Options.Applicative.Types ( filterOptional, optVisibility, optMetaVar, + optMetaVar2, optHelp, optShowDefault, optDescMod @@ -72,6 +73,7 @@ data ParseError | UnknownError | MissingError IsCmdStart SomeParser | ExpectsArgError String + | ExpectsArgError2 String | UnexpectedError String SomeParser data IsCmdStart = CmdStart | CmdCont @@ -152,17 +154,19 @@ data OptProperties = OptProperties { propVisibility :: OptVisibility -- ^ whether this flag is shown in the brief description , propHelp :: Chunk Doc -- ^ help text for this option , propMetaVar :: String -- ^ metavariable for this option + , propMetaVar2 :: String -- ^ second metavariable for this 'biOption' , propShowDefault :: Maybe String -- ^ what to show in the help text as the default , propShowGlobal :: Bool -- ^ whether the option is presented in global options text , propDescMod :: Maybe ( Doc -> Doc ) -- ^ a function to run over the brief description } instance Show OptProperties where - showsPrec p (OptProperties pV pH pMV pSD pSG _) + showsPrec p (OptProperties pV pH pMV pMV2 pSD pSG _) = showParen (p >= 11) $ showString "OptProperties { propVisibility = " . shows pV . showString ", propHelp = " . shows pH . showString ", propMetaVar = " . shows pMV + . showString ", propMetaVar2 = " . shows pMV2 . showString ", propShowDefault = " . shows pSD . showString ", propShowGlobal = " . shows pSG . showString ", propDescMod = _ }" @@ -235,18 +239,24 @@ instance Functor CReader where fmap f (CReader c r) = CReader c (fmap f r) -- | An 'OptReader' defines whether an option matches an command line argument. -data OptReader a - = OptReader [OptName] (CReader a) (String -> ParseError) - -- ^ option reader - | FlagReader [OptName] !a - -- ^ flag reader - | ArgReader (CReader a) - -- ^ argument reader - | CmdReader (Maybe String) [(String, ParserInfo a)] - -- ^ command reader +data OptReader a where + -- | option reader + OptReader :: [OptName] -> CReader a -> (String -> ParseError) -> OptReader a + -- | two-arg option reader + BiOptReader :: [OptName] -> CReader a -> CReader b -> (String -> ParseError) -> OptReader (a, b) + -- | fmap option reader + MapReader :: (a -> b) -> OptReader a -> OptReader b + -- | flag reader + FlagReader :: [OptName] -> !a -> OptReader a + -- | argument reader + ArgReader :: CReader a -> OptReader a + -- | command reader + CmdReader :: (Maybe String) -> [(String, ParserInfo a)] -> OptReader a instance Functor OptReader where fmap f (OptReader ns cr e) = OptReader ns (fmap f cr) e + fmap f r@BiOptReader {} = MapReader f r + fmap f (MapReader g r) = MapReader (f . g) r fmap f (FlagReader ns x) = FlagReader ns (f x) fmap f (ArgReader cr) = ArgReader (fmap f cr) fmap f (CmdReader n cs) = CmdReader n ((fmap . fmap . fmap) f cs) @@ -437,6 +447,9 @@ optHelp = propHelp . optProps optMetaVar :: Option a -> String optMetaVar = propMetaVar . optProps +optMetaVar2 :: Option a -> String +optMetaVar2 = propMetaVar2 . optProps + optShowDefault :: Option a -> Maybe String optShowDefault = propShowDefault . optProps diff --git a/tests/test.hs b/tests/test.hs index 4c888dca..6216741b 100644 --- a/tests/test.hs +++ b/tests/test.hs @@ -935,6 +935,110 @@ prop_help_unknown_context = once $ post = run i ["--help", "not-a-command"] in grabHelpMessage pre === grabHelpMessage post +prop_biOption_example :: Property +prop_biOption_example = once $ + let p = biOption str str ( short 'p' <> metavar "KEY" <> metavar2 "VALUE" ) + i = info (p <**> helper) idm + result = run i ["-p", "foo", "bar"] + in assertResult result (("foo", "bar") ===) + +prop_biOption_example_many :: Property +prop_biOption_example_many = once $ + let p = many (biOption str auto ( long "option" )) + i = info (p <**> helper) idm + result = run i ["--option", "one", "1", "--option", "two", "2"] + in assertResult result ([("one", 1), ("two", 2 :: Int)] ===) + +prop_biOption_fail_zero :: Property +prop_biOption_fail_zero = once $ + let p :: Parser (String, String) + p = biOption str str (long "option") + i = info (p <**> helper) briefDesc + result = run i ["--help"] + in assertError result $ \failure -> + let text = head . lines . fst $ renderFailure failure "test" + in "Usage: test --option ARG ARG" === text + +prop_kvOption_fail_zero :: Property +prop_kvOption_fail_zero = once $ + let p :: Parser (String, String) + p = biOption str str (long "option" <> metavar "KEY" <> metavar2 "VALUE") + i = info (p <**> helper) briefDesc + result = run i ["--help"] + in assertError result $ \failure -> + let text = head . lines . fst $ renderFailure failure "test" + in "Usage: test --option KEY VALUE" === text + +prop_many_kvOption_fail_zero :: Property +prop_many_kvOption_fail_zero = once $ + let p :: Parser [(String, String)] + p = many $ biOption str str (long "option" <> metavar "KEY" <> metavar2 "VALUE") + i = info (p <**> helper) briefDesc + result = run i ["--help"] + in assertError result $ \failure -> + let text = head . lines . fst $ renderFailure failure "test" + in "Usage: test [--option KEY VALUE]" === text + +prop_strOption_fail_zero :: Property +prop_strOption_fail_zero = once $ + let p :: Parser String + p = strOption (long "option") + i = info (p <**> helper) briefDesc + result = run i ["--help"] + in assertError result $ \failure -> + let text = head . lines . fst $ renderFailure failure "test" + in "Usage: test --option ARG" === text + +prop_completion_biOption_option :: Property +prop_completion_biOption_option = once . ioProperty $ + let p :: Parser (String,String) + p = biOption str str (long "option" <> completeWith ["key"] <> completer2 (listCompleter ["value"])) + i = info p idm + result = run i + [ "--bash-completion-index", "1" + ] + in case result of + CompletionInvoked (CompletionResult err) -> do + completions <- lines <$> err "test" + return $ ["--option"] === completions + Failure _ -> return $ counterexample "unexpected failure" failed + Success val -> return $ counterexample ("unexpected result " ++ show val) failed + +prop_completion_biOption_first_value :: Property +prop_completion_biOption_first_value = once . ioProperty $ + let p :: Parser (String,String) + p = biOption str str (long "option" <> completeWith ["key"] <> completer2 (listCompleter ["value"])) <* many (strArgument mempty :: Parser String) + i = info p idm + result = run i + [ "--bash-completion-word", "test" + , "--bash-completion-word", "--option" + , "--bash-completion-index", "2" + ] + in case result of + CompletionInvoked (CompletionResult err) -> do + completions <- lines <$> err "test" + return $ ["key"] === completions + Failure _ -> return $ counterexample "unexpected failure" failed + Success val -> return $ counterexample ("unexpected result " ++ show val) failed + +prop_completion_biOption_second_value :: Property +prop_completion_biOption_second_value = once . ioProperty $ + let p :: Parser (String,String) + p = biOption str str (short 'o' <> completeWith ["key"] <> completer2 (listCompleter ["value"])) + i = info p idm + result = run i + [ "--bash-completion-word", "test" + , "--bash-completion-word", "-o" + , "--bash-completion-word", "key" + , "--bash-completion-index", "3" + ] + in case result of + CompletionInvoked (CompletionResult err) -> do + completions <- lines <$> err "test" + return $ ["value"] === completions + Failure _ -> return $ counterexample "unexpected failure" failed + Success val -> return $ counterexample ("unexpected result " ++ show val) failed + prop_long_command_line_flow :: Property prop_long_command_line_flow = once $ From e2fd501f25921e8ec16fb6e1b4be7d8308b17882 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 14 Oct 2020 19:11:53 +0200 Subject: [PATCH 50/52] Use bash from $SHELL when it makes sense --- src/Options/Applicative/Builder/Completer.hs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Options/Applicative/Builder/Completer.hs b/src/Options/Applicative/Builder/Completer.hs index 5da556e7..14135b28 100644 --- a/src/Options/Applicative/Builder/Completer.hs +++ b/src/Options/Applicative/Builder/Completer.hs @@ -15,7 +15,9 @@ import Prelude import Control.Exception (IOException, try) import Data.List (isPrefixOf) #ifdef MIN_VERSION_process +import Data.List (isSuffixOf) import System.Process (readProcess) +import System.Environment (lookupEnv) #endif import Options.Applicative.Types @@ -40,8 +42,23 @@ bashCompleter :: String -> Completer #ifdef MIN_VERSION_process bashCompleter action = Completer $ \word -> do let cmd = unwords ["compgen", "-A", action, "--", requote word] - result <- tryIO $ readProcess "bash" ["-c", cmd] "" + bash <- getBash + result <- tryIO $ readProcess bash ["-c", cmd] "" return . lines . either (const []) id $ result + +-- | Determines the bash executable. Ideally we'd invoke the same bash that +-- is currently active. If $SHELL does not seem to be set to a bash executable +-- we don't assume $SHELL is bash and we take bash from the $PATH. +-- This fixes file completion in cases where a virtual environment with a +-- non-interactive bash is loaded with direnv, nix-shell or similar. +getBash :: IO String +getBash = do + shellEnv <- lookupEnv "SHELL" + pure (case shellEnv of + Just exe | "/bash" `isSuffixOf` exe -> exe + _ -> "bash" + ) + #else bashCompleter = const $ Completer $ const $ return [] #endif From e2a4e43584f6a5f79ae940e5265b06e0c7cddc80 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 5 Mar 2021 14:23:36 +0100 Subject: [PATCH 51/52] Rename the fork (cherry picked from commit f9d1242f9889d2e09ff852db9dc2d231d9a3e8d8) --- ...bal => hercules-ci-optparse-applicative.cabal | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename optparse-applicative.cabal => hercules-ci-optparse-applicative.cabal (91%) diff --git a/optparse-applicative.cabal b/hercules-ci-optparse-applicative.cabal similarity index 91% rename from optparse-applicative.cabal rename to hercules-ci-optparse-applicative.cabal index d9a20768..7f155455 100644 --- a/optparse-applicative.cabal +++ b/hercules-ci-optparse-applicative.cabal @@ -1,6 +1,6 @@ -name: optparse-applicative +name: hercules-ci-optparse-applicative version: 0.18.1.0 -synopsis: Utilities and combinators for parsing command line options +synopsis: Utilities and combinators for parsing command line options (fork) description: optparse-applicative is a haskell library for parsing options on the command line, and providing a powerful applicative @@ -13,11 +13,11 @@ description: . See the included README for detailed instructions and examples, which is also available on github - . + . license: BSD3 license-file: LICENSE author: Paolo Capriotti, Huw Campbell -maintainer: huw.campbell@gmail.com +maintainer: info@hercules-ci.com copyright: (c) 2012-2017 Paolo Capriotti category: System, CLI, Options, Parsing build-type: Simple @@ -42,8 +42,8 @@ extra-source-files: CHANGELOG.md tests/nested_optional.err.txt tests/subparsers.err.txt -homepage: https://github.com/pcapriotti/optparse-applicative -bug-reports: https://github.com/pcapriotti/optparse-applicative/issues +homepage: https://github.com/hercules-ci/optparse-applicative +bug-reports: https://github.com/hercules-ci/optparse-applicative/issues tested-with: GHC==9.6.1 GHC==9.4.4 @@ -64,7 +64,7 @@ tested-with: source-repository head type: git - location: https://github.com/pcapriotti/optparse-applicative.git + location: https://github.com/hercules-ci/optparse-applicative.git flag process description: @@ -133,7 +133,7 @@ test-suite tests , Examples.LongSub build-depends: base - , optparse-applicative + , hercules-ci-optparse-applicative , QuickCheck >= 2.8 && < 2.15 if !impl(ghc >= 8) From 4b376562b51ee58b27171c3316d2d5ddac108deb Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Fri, 5 Mar 2021 14:23:54 +0100 Subject: [PATCH 52/52] Fix merge (cherry picked from commit 582aaf615d0a66d0b9c5b567effe209fa4fac1bb) --- src/Options/Applicative/BashCompletion.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Options/Applicative/BashCompletion.hs b/src/Options/Applicative/BashCompletion.hs index 80fcd4f7..eca2553f 100644 --- a/src/Options/Applicative/BashCompletion.hs +++ b/src/Options/Applicative/BashCompletion.hs @@ -125,7 +125,7 @@ bashCompletionQuery pinfo pprefs features ws i _ = case runCompletion compl ppre -- -- For options and flags, ensure that the user -- hasn't disabled them with `--`. - opt_completions :: forall a. ArgPolicy -> ArgumentReachability -> Option a -> IO [String] + opt_completions :: forall a. ArgPolicy -> ArgumentReachability -> Option a -> IO [CompletionItem] opt_completions argPolicy reachability opt = case optMain opt of OptReader ns _ _ | argPolicy /= AllPositionals @@ -134,7 +134,7 @@ bashCompletionQuery pinfo pprefs features ws i _ = case runCompletion compl ppre -> return [] BiOptReader ns _ _ _ | argPolicy /= AllPositionals - -> return . add_opt_help opt $ show_names ns + -> return . fmap legacyCompletionItem . add_opt_help opt $ show_names ns | otherwise -> return [] MapReader _f optr -> opt_completions argPolicy reachability (opt { optMain = optr })