Merge master @1.22.1 with v2

commit c71fbcefd21552b70cd625b2c54466006e258ad7
Merge: 61f3ae3 ef47250
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Sep 12 05:35:50 2019 +0530

    Merge pull request #887 from urfave/asahasrabuddhe-patch-1

    Release 1.22.1

commit ef47250cda5ff52a313118c01ad6b0c5b4877a70
Merge: 71eaf37 61f3ae3
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Sep 12 05:19:58 2019 +0530

    Merge branch 'master' into asahasrabuddhe-patch-1

commit 61f3ae353bf455e3522aff0d5a28be9278bba7f2
Merge: 388c2dd fa858dc
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Sep 12 05:19:33 2019 +0530

    Merge pull request #890 from urfave/issue-878

    Fix #878

commit fa858dcc260fb07c25aab13650d9fa0e64f851c7
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Sep 11 15:10:14 2019 +0530

    Ensure flag is not blank

commit f8bb66ae7d679973cf9b3f6f8c3dc6933404a31a
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Sep 11 14:42:38 2019 +0530

    Fix Typo

commit 056aef13fe0b0e51403036ca5527854f50a1f3cd
Merge: c6ee3b4 82a84fc
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Sep 11 14:37:06 2019 +0530

    Merge branch 'issue-878' of https://github.com/urfave/cli into issue-878

commit c6ee3b4904ed76d34f277c315c2097ae7b22d38f
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Sep 11 14:34:41 2019 +0530

    Use iterative logic to determine missing flag

commit 82a84fc187c23434a5c2e1398a7fcfbc9c51df94
Merge: 1547ac2 388c2dd
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 14:17:11 2019 +0530

    Merge branch 'master' into issue-878

commit 1547ac2f6a3d3d39fe4d49570c0d1c2401a8f20e
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Sep 11 14:15:20 2019 +0530

    Modify variable names

commit 388c2dd0f4ffaa8541e371d49c8413870a04d9fe
Merge: e19126a 6d888d6
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 14:13:40 2019 +0530

    Merge pull request #891 from saschagrunert/fish-hidden

    Don't generate fish completion for hidden commands

commit 71eaf37e337d5daea12c6a137113c71056151530
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 14:09:50 2019 +0530

    Update CHANGELOG.md

commit 6d888d693d81e13806356854c57574334d9ef3b9
Merge: bac5bde e19126a
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 14:04:44 2019 +0530

    Merge branch 'master' into fish-hidden

commit e19126a8198a7c076339e69ed4d372567750dd24
Merge: b207e20 35eb598
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 14:01:44 2019 +0530

    Merge pull request #883 from urfave/remove-flag-generation

    Remove flag generation

commit bac5bde38c7725990645cf9b2bf2c824594f3963
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Wed Sep 11 09:06:02 2019 +0200

    Don't generate fish completion for hidden commands

    Added the missing test case as well.

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 36cdaa9964df03e2b8f8d2147a99497536851ad9
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 10:34:00 2019 +0530

    Update CHANGELOG.md

commit cbb9e015b89225aa090c41085bdb0933f6290d96
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 09:21:45 2019 +0530

    Improve Code and Add Test Case

commit 7d6a604106e44732edc0a76f4a4800c8c27ddfbe
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 08:59:51 2019 +0530

    Fix #878

commit be37c2cbda3ba6a37fa8f7a0df960de844afc843
Merge: 0aee120 b207e20
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 08:27:45 2019 +0530

    Merge branch 'master' into asahasrabuddhe-patch-1

commit 35eb598d43c3ab639e3c0ccc72e37f294e5b5828
Merge: 8575558 b207e20
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 05:39:35 2019 +0530

    Merge branch 'master' into remove-flag-generation

commit 0aee120c32003fff6f320c2a00a41d03285fdde0
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 05:37:41 2019 +0530

    Update CHANGELOG.md

commit 5c019b10ca37c1a0b74b637d3b0aa25dfeb0110f
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Sep 11 05:33:46 2019 +0530

    Update CHANGELOG.md

commit b207e20873f5805e7c9cc544b3418a0a3ec63e09
Merge: 249cb33 487be14
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Tue Sep 10 21:28:35 2019 +0100

    Merge pull request #889 from crosbymichael/hidden-man

    Don't output hidden commands for  man pages

commit 487be14dceb185a3321cacd9bf302f5e811f5ee8
Author: Michael Crosby <crosbymichael@gmail.com>
Date:   Tue Sep 10 13:49:11 2019 -0400

    Don't output hidden commands for  man pages

    Signed-off-by: Michael Crosby <crosbymichael@gmail.com>

commit 85755588ac06f74702bf7d62802dab0655881182
Merge: 024692c 249cb33
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Tue Sep 10 15:04:12 2019 +0530

    Merge branch 'master' into remove-flag-generation

commit 249cb3339254361b1a884733a98d4202e2838b9b
Merge: bfe2e92 abfb13b
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Tue Sep 10 08:28:09 2019 +0100

    Merge pull request #885 from urfave/go-modules-support

    Go modules support

commit abfb13b8542fbe3d542d46543ab0d3be6aacb4e5
Merge: 534d60b bfe2e92
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Tue Sep 10 06:56:41 2019 +0530

    Merge branch 'master' into go-modules-support

commit 054fbefec36cad7425dc6f4cfb6d2963c2710751
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Tue Sep 10 06:42:34 2019 +0530

    Update CHANGELOG.md

commit 534d60bb9bb0476141540ec77c5a3d51e176d162
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Tue Sep 10 06:40:45 2019 +0530

    Bump minimum supported version of Go to 1.11

commit 024692c172f7000fe2431c3280a1e4b724b15945
Merge: 4a9e440 bfe2e92
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Tue Sep 10 06:33:21 2019 +0530

    Merge branch 'master' into remove-flag-generation

commit bfe2e925cfb6d44b40ad3a779165ea7e8aff9212
Merge: 3eca109 238c80f
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Tue Sep 10 06:24:10 2019 +0530

    Merge pull request #882 from urfave/lynncyrin-patch-1

    Release 1.22.0

commit 426e21c150d9a33e4d8c13c2a13c5234e85f3a0e
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 13:15:47 2019 +0530

    Update .travis.yml

    Set GOPROXY in Travis environment

commit 39bd6176649871817d1966b6b91f042be4b62fe9
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 08:37:16 2019 +0530

    Cleanup after before_script to avoid git diff errors
    remove windows build

commit edbf66c25cf83541faee77d0064fdb5ac35a51b1
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 08:23:04 2019 +0530

    Update gfmrun import command to suite Go Modules pattern
    Fix test command typo in travis script

commit afd0ecbbf2fbda2f9459046228ccc9e8d2693258
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 08:19:06 2019 +0530

    Add support for Go 1.13
    Drop support for Go 1.11
    Use md2man v2 to avoid dependency issues when building with Go Modules
    Enabled
    Update TravisCI build environment images (trusty was deprecated)
    Add optional Windows build

commit 4a9e440503f4113a351322da93f8630df14dfcaa
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 08:12:09 2019 +0530

    Fix AppVeyor build

commit 5c81af9f10b974cecbec6e20e4976574e86fc78b
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 07:47:18 2019 +0530

    Remove generate script from travis flow
    Remove unused dependencies from travis script

commit b6c5d17a835d17e8dd8f2e34c02e96af7f43c9e4
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Mon Sep 9 07:44:07 2019 +0530

    Remove Flag Generation
    Remove Legacy Python Scripts

commit 238c80f9b5e54a834f25423fd0887f68b9737cbb
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Sep 7 20:44:48 2019 -0700

    Update CHANGELOG.md

commit 980742b7cc56e8f7b14b4668fb8ddcf89025c9ea
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Sep 7 20:43:56 2019 -0700

    typo

commit 890d49ca7a15a3fb7f44d33e5cdb499f0e5c2331
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Sep 7 20:41:44 2019 -0700

    Release 1.22.0

    - adds the changelog for 1.22.0
    - updates the changelog for 1.21.0. some PRs were missed, as was mentioned here https://github.com/urfave/cli/pull/829#issuecomment-517968795
    - closes https://github.com/urfave/cli/issues/867

commit 3eca1090a37a65fea5b84bdbd2c61f8104211b74
Merge: 38a6c56 4bbff84
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Fri Aug 30 15:53:55 2019 +0100

    Merge pull request #879 from saschagrunert/escape-single-quotes

    Escape single quotes in fish shell completion

commit 4bbff841690954ed86c147147151c0144dcf1765
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Thu Aug 29 14:45:32 2019 +0200

    Escape single quotes in fish shell completion

    Single quotes can break the generated fish shell completion and should
    be escaped correctly.

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 38a6c560b3b8ac6d47e031a44b92e0531824c7e5
Merge: fa6797b 687f721
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Thu Aug 29 20:52:07 2019 +0100

    Merge pull request #857 from saschagrunert/takes-file-fish

    Add `TakesFile` to fish shell completion

commit 687f721eaa40859950820b37b9ad1fcd85b7da9f
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Mon Aug 26 10:07:50 2019 +0200

    Update function alignment

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 0c01922a12c501867cad200bc4b36a25f9a073e0
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Mon Aug 26 08:46:55 2019 +0200

    Add type switch

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 38d0ac629677a7fbf08a52e17fec73894bb31263
Author: Sascha Grunert <mail@saschagrunert.de>
Date:   Sun Aug 25 17:50:18 2019 +0200

    Removed GetTakesFile and stick to type assertions

    Signed-off-by: Sascha Grunert <mail@saschagrunert.de>

commit a1cf7f44b6cf65c0c10c282c71524cc37442b798
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Mon Aug 12 09:42:12 2019 +0200

    Add `TakesFile` to fish shell completion

    The new `TakesFile` flag will be now consumed by the fish shell
    completion generator.

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit fa6797beefc5727035323623aed515e4dfd3ccdf
Merge: 2344c98 82eb0d7
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 24 18:58:52 2019 -0700

    Merge pull request #876 from urfave/lynncyrin-patch-1

    Bump go version to 1.10 in readme

commit 82eb0d70cbcf89de5e71965fc9ededbb41cdbd96
Merge: edd8cb2 2344c98
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sun Aug 25 01:04:33 2019 +0100

    Merge branch 'master' into lynncyrin-patch-1

commit 2344c98f678ac236c6bf952fe724bc5b0a6bd69b
Merge: 55de011 68ee2bc
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sun Aug 25 01:04:17 2019 +0100

    Merge pull request #860 from saschagrunert/takes-file-not-all

    Update `TakesFile` flag to apply only to supported flags

commit edd8cb2068b6501d6b631299038cb42194926a8e
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 24 14:44:56 2019 -0700

    Bump go version to 1.10 in readme

    Closes https://github.com/urfave/cli/issues/875

commit 68ee2bc4af27ae14cedbfb881384b0900a0ed3a9
Merge: 959d9ec 55de011
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 24 14:34:15 2019 -0700

    Merge branch 'master' into takes-file-not-all

commit 55de011cf89b3d78842e3b3e2cf92f9d157fa399
Merge: 392c1de d3edef8
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sat Aug 24 11:55:28 2019 +0100

    Merge pull request #873 from urfave/show-test-failures

    build: show failures when running tests

commit d3edef887a2fc39830216cd41b16955ef60d0d3c
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sat Aug 24 11:34:03 2019 +0100

    Update build.go

commit c2d1a132082e3b02a219e61eeef49da364d2c315
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Aug 24 03:05:45 2019 -0700

    Revert "check length"

    This reverts commit 1095838cca9d596e55cff88bcb35b67cf83bf4e4.

commit 959d9ec36b7848004fd2e85f07b810266d65c8d2
Merge: 3681b05 392c1de
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Sat Aug 24 11:23:51 2019 +0200

    Merge branch 'master' into takes-file-not-all

commit 7d62a9d0547cbab68e78c2c92a79db97ee61f115
Merge: 1095838 392c1de
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 24 00:50:42 2019 -0700

    Merge branch 'master' into show-test-failures

commit 1095838cca9d596e55cff88bcb35b67cf83bf4e4
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Aug 24 00:49:29 2019 -0700

    check length

commit 29ad6ee6ad7e02dbec3334e8843bb6711c011b55
Author: [[ BOT ]] Lynn Cyrin <lynncyrin@gmail.com>
Date:   Fri Aug 23 20:09:08 2019 -0700

    DRY

commit 392c1de1a2b3f8bc2ca95c2389dd05469d347b14
Merge: 23c8303 487c723
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Fri Aug 23 22:51:39 2019 +0100

    Merge pull request #874 from saschagrunert/go-mod-cleanup

    Cleanup go modules

commit 487c7236736db7d5d2e46633d09453d0e149a0bd
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Fri Aug 23 10:28:32 2019 +0200

    Cleanup go modules

    These two dependencies are not really needed, which can be
    reproduced via:

    ```
    > export GO111MODULE=on && go mod tidy
    ```

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 8469a9de07c45435b61cbfd4aed7167fb9e59cca
Author: [[ BOT ]] Lynn Cyrin <lynncyrin@gmail.com>
Date:   Thu Aug 22 21:42:07 2019 -0700

    show test failures

commit 23c83030263f7adfc0e3c34b567ee508e8d536cf
Merge: ecd576e 6a25af9
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 17 11:24:05 2019 -0700

    Merge pull request #862 from russoj88/UpdateREADME_gopkg.in_v1

    Rewrite the "pinning to v1" section.

commit 6a25af96413deaeb4d6c451d6288079db0840a82
Merge: 3bc62c4 ecd576e
Author: russoj88 <russoj88@users.noreply.github.com>
Date:   Sat Aug 17 10:01:35 2019 -0700

    Merge branch 'master' into UpdateREADME_gopkg.in_v1

commit ecd576e779bce41496738b34e9ee6272c63801d0
Merge: 6cc7e98 e11183f
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sat Aug 17 16:51:43 2019 +0100

    Merge pull request #868 from urfave/lynncyrin-patch-1

    Modernize readme

commit e11183fe50e3b3d75e481b1262e3222c565ba8bf
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 17 02:44:49 2019 -0700

    Modernize readme

    I assume that people no longer care about what the package was named many years ago

commit 3bc62c4fde03e107cad02f8828780470258b8fc0
Author: russoj88 <russoj88@gmail.com>
Date:   Thu Aug 15 12:30:29 2019 -0700

    Mimic v2 example code from above.

commit 62b8a7cc2cb05b1a454908087b35e5780a1d12ad
Author: russoj88 <russoj88@gmail.com>
Date:   Wed Aug 14 11:20:09 2019 -0700

    Add "Using v1 releases" to table of contents.

commit cc091db561b137c49cbf370766a94b47cfdae182
Author: russoj88 <russoj88@users.noreply.github.com>
Date:   Wed Aug 14 11:21:40 2019 -0700

    Update README.md

    Only instruct on right way to use library.

    Co-Authored-By: Lynn Cyrin (they/them) <lynncyrin@gmail.com>

commit f529dad70caa6e307f95eecb2db6f16efc0f964d
Author: russoj88 <russoj88@users.noreply.github.com>
Date:   Wed Aug 14 11:20:58 2019 -0700

    Update README.md

    Include suggestion to put example in.

    Co-Authored-By: Lynn Cyrin (they/them) <lynncyrin@gmail.com>

commit f2c26bab772e6b69a9fca945534728678578eb2b
Author: russoj88 <russoj88@gmail.com>
Date:   Tue Aug 13 21:10:38 2019 -0700

    Rewrite the "pinning to v1" section.

commit 3681b057c5df7f380e75974674a8282cf5632dc3
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Tue Aug 13 09:43:57 2019 +0200

    Update `TakesFile` flag to apply only to supported flags

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 6cc7e987c4fa553caa5014c7dbc1e7acaea9f0f1
Merge: 7e49cc2 08c24e2
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Mon Aug 12 21:30:37 2019 +0100

    Merge pull request #856 from FaranIdo/master

    Add Subcommand fallback call to ExitErrHandler, fixing #816

commit 08c24e22ed2c4bebb348a738caf92c40bb63133c
Author: FaranIdo <idoosbron@gmail.com>
Date:   Mon Aug 12 00:29:46 2019 +0300

    add missing ExitErrHandler in command + matching test, fixing #816

commit 7e49cc210a231eec218c2fba82df106af06d05b5
Merge: 8b18c71 4e42a2f
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 10 09:01:16 2019 +0000

    Merge pull request #848 from saschagrunert/fish-shell

    Add fish shell completion support

commit 4e42a2f02ceb3cbfe2f8c4e5c6e419a712c335c9
Merge: 56d12d0 8b18c71
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 10 08:47:31 2019 +0000

    Merge branch 'master' into fish-shell

commit 8b18c71e1a4eabe8d7ba20d81d7fbd882709833d
Merge: 7058c58 c6c2008
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 10 08:47:23 2019 +0000

    Merge pull request #851 from saschagrunert/takes-file

    Add `TakesFile` indicator to flag

commit 56d12d0c2f27a159e95165cf3cec2396df6f68af
Merge: 7506b11 7058c58
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Fri Aug 9 17:21:24 2019 +0530

    Merge branch 'master' into fish-shell

commit c6c200864d770982106717a20ad99603396fb042
Merge: e9e9e0a 7058c58
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Fri Aug 9 13:48:36 2019 +0200

    Merge branch 'master' into takes-file

commit 7058c58eb6af9ee166dafdf82012e1241890223d
Merge: 2e0e39a de0fa70
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Fri Aug 9 17:16:13 2019 +0530

    Merge pull request #847 from saschagrunert/remove-date-var

    Remove unused `Date` variable from `cliTemplate`

commit de0fa704331adf0183d6f1b6d94a2390a48a810c
Merge: 0d79d1d 2e0e39a
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Fri Aug 9 12:38:50 2019 +0100

    Merge branch 'master' into remove-date-var

commit e9e9e0ac5dfce215fb2207b3bbde2534a67907f6
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Fri Aug 9 09:05:55 2019 +0200

    Add `TakesFile` indicator to flag

    This new member of `Flag` indicates if the flag expects a file as input.
    This is especially useful for documentation and shell completion purposes.

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 7506b11da746beef287831f805f5b0e49264b400
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Thu Aug 8 15:50:36 2019 +0200

    Add fish shell completion support

    This commit adds a new method `ToFishCompletion` to the `*App` which can
    be used to generate a fish completion string for the application.

    Relates to: #351

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit 2e0e39a03b46023f83ec2e70f1948836e0581543
Merge: 946f918 aed704a
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Fri Aug 9 10:34:28 2019 +0530

    Merge pull request #845 from urfave/lint-fixes

    linter fixes

commit 0d79d1d9d99db9e380e37034f677523b0ca435b5
Author: Sascha Grunert <sgrunert@suse.com>
Date:   Thu Aug 8 14:04:21 2019 +0200

    Remove unused `Date` variable from `cliTemplate`

    Signed-off-by: Sascha Grunert <sgrunert@suse.com>

commit aed704a9d036852c332867dffd97c60c51e8a38d
Merge: 0990ca2 946f918
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Aug 8 14:44:02 2019 +0530

    Merge branch 'master' into lint-fixes

commit 946f918365f62f6fe8d7fb7d4ea54dd441eccfb6
Merge: 2c477e7 286133f
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Thu Aug 8 09:13:09 2019 +0100

    Merge pull request #735 from rliebz/combined

    Add app-wide support for combining short flags

commit 0990ca2391ac8a72bc59d393e64ca520d9c53772
Merge: fdba7e0 2c477e7
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Aug 8 13:36:30 2019 +0530

    Merge branch 'master' into lint-fixes

commit 286133fee5ef662bcfc9fdb7e410ce83528ab1f8
Merge: 815c29f 2c477e7
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Aug 8 13:33:32 2019 +0530

    Merge branch 'master' into combined

commit 2c477e720e69b9ce81e9d6cf68c81a0334446016
Merge: e0057bb 99fad61
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Thu Aug 8 08:41:19 2019 +0100

    Merge pull request #830 from saschagrunert/docs-gen

    Add markdown and man page docs generation methods

commit 99fad61ded52131321a0d7e5d330554512254ebe
Merge: 40d4a25 e0057bb
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Thu Aug 8 07:06:08 2019 +0100

    Merge branch 'master' into docs-gen

commit e0057bb59731900e8b702b0b5282378577cb99e5
Merge: 521735b fd39578
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Thu Aug 8 07:04:25 2019 +0100

    Merge pull request #846 from urfave/asahasrabuddhe-patch-1

    Update README.md

commit 815c29ffc73623b32e587298eeda0a0dd2ff5737
Merge: a77c440 521735b
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Thu Aug 8 07:03:49 2019 +0100

    Merge branch 'master' into combined

commit fd395786a2c77da3f111c6208ba50b3041fe6ee2
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Aug 7 21:00:07 2019 +0530

    Update README.md

    remove quotes around coverage badge

commit fdba7e0f8c921d4ce169cb416b2eae58026c83e9
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Aug 7 20:14:50 2019 +0530

    linter fixes
    code cleanup
    changing some test code to ensure uniformity

commit 40d4a25a01296d2f51bf7bbf0b6e8eb99ba4a84c
Author: Sascha Grunert <mail@saschagrunert.de>
Date:   Sat Aug 3 12:41:50 2019 +0200

    Add markdown and man page docs generation methods

    This adds two new methods to the `App` struct:

    - `ToMarkdown`: creates a markdown documentation string
    - `ToMan`: creates a man page string

    Signed-off-by: Sascha Grunert <mail@saschagrunert.de>

commit 521735b7608a25d771a39d42e2267e061e7e84b8
Merge: 97179ca 22e1fc8
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Wed Aug 7 12:10:34 2019 +0100

    Merge pull request #844 from urfave/asahasrabuddhe-patch-1

    Update README.md

commit 22e1fc84192059f056a7b53aa5ef2ee7113d9a83
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Aug 7 14:02:52 2019 +0530

    Update README.md

    add codecov.io badge

commit 97179ca390abf228a187e6ebbedca69636d60f0d
Merge: b6f7dd9 3a41d6d
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Aug 7 13:58:51 2019 +0530

    Merge pull request #843 from lafriks/patch-1

    Support GoLang 1.10 to 1.12

commit 3a41d6d7851b15f132131444865e82b17baf0be0
Author: Lauris BH <lauris@nix.lv>
Date:   Wed Aug 7 11:21:31 2019 +0300

    Lower support to GoLang compiler version 1.10

commit 1f4473209dcad42ed88ba68f1be7d4e906ae91be
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Aug 7 13:50:04 2019 +0530

    Update .travis.yml

    support go versions in line with go's release policy

commit e3fa7e8566f9374ac6c1e08ace0e0555f9666e10
Author: Lauris BH <lauris@nix.lv>
Date:   Wed Aug 7 11:06:15 2019 +0300

    Support also GoLang 1.11 compiler

commit b6f7dd93594d17c08d349ba5f974e501b8c12b7b
Merge: 93392d1 e2de8c7
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Aug 7 13:28:34 2019 +0530

    Merge pull request #836 from urfave/flag-type-generation-golang

    Flag Generation in the CLI

commit e2de8c74587d464770155415cda09c3569224692
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Aug 7 12:38:17 2019 +0530

    update readme with correct error message, add 1.12 and 1.11 to travis

commit a77c440b8476d59b59fcf2606a92fd3bb82603c3
Merge: 8d31c5e 93392d1
Author: Robert Liebowitz <rliebz@gmail.com>
Date:   Tue Aug 6 22:33:49 2019 -0400

    Merge branch 'master' into combined

commit 24de27b05e91ef797b9ba97e3c146842fb8e29d8
Merge: c19938f 93392d1
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Wed Aug 7 01:54:44 2019 +0530

    Merge branch 'master' into flag-type-generation-golang

commit c19938fbbfb19120beeca5d0af02291a99e61f27
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Aug 7 01:51:02 2019 +0530

    update ci commands

commit 6ee5b89e03a0fc47c6351c902ef714f1475e8fde
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Aug 7 01:50:50 2019 +0530

    move build.go to root

commit e8bbb4c3b5472320f0af5fb00961c76f1061edd4
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Wed Aug 7 01:46:22 2019 +0530

    remove unnecessary sprintf

commit 5070d00811cd5f2f21b0a6e021581e8efb2479e9
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Tue Aug 6 12:33:33 2019 +0530

    move packages slice to global scope

commit 93392d12e8cd60a1c7c62dc4bf4bab7a8e001eec
Merge: 26945c5 1db0496
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Tue Aug 6 07:59:53 2019 +0100

    Merge pull request #808 from yogeshlonkar/master

    Add support for flags bash completion

commit adfe6a09c121a6b96357ea161f2d679d509c013f
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Tue Aug 6 12:14:18 2019 +0530

    indenting fix

commit 1db049685ac49e11b2e27285e1287793cfe0ea84
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Mon Aug 5 20:22:52 2019 +0200

    Fix unused regex

commit 2be2bc755e4634d34136769a426a7ca52e698cc0
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Mon Aug 5 20:18:08 2019 +0200

    Add additional test for log flag completion and comments

commit c3f51bed6fffdf84227c5b59bd3f2e90683314df
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Mon Aug 5 17:07:46 2019 +0200

    Fix SC2199: Arrays implicitly concatenate in

commit c5612e8cd21e0cd99f73d23103df99a9af70f853
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Mon Aug 5 16:58:04 2019 +0200

    Fix review comments

commit 8d31c5e167103ef4235cc5553b0fb45a2f6e8f74
Author: Robert Liebowitz <rliebz@gmail.com>
Date:   Mon Aug 5 07:05:07 2019 -0400

    Update README.md

    Co-Authored-By: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>

commit 03153b9cf8988d787fe79d02bd4138283ea507bd
Author: Robert Liebowitz <rliebz@gmail.com>
Date:   Mon Aug 5 06:16:30 2019 -0400

    Allow combining short flags globally

commit d6523cf8692d40c0ff9f6d81f7ac25341c58da7a
Merge: e949dc2 26945c5
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Mon Aug 5 11:00:26 2019 +0200

    Merge branch 'master' into master

commit 26945c58edddd8cb0830baf73ebc7bee44b5f455
Merge: d09efb5 c25e4ca
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sun Aug 4 12:36:23 2019 -0700

    Merge pull request #823 from xordspar0/master

    Make the exit code example more clear

commit c25e4cab32bab49d1d3c4847a0a6419e2cb3dd15
Merge: b1a7c50 d09efb5
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sun Aug 4 12:30:28 2019 -0700

    Merge branch 'master' into master

commit ac5c97b41844032ae47d5f94d7f73533af629f11
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 20:45:28 2019 +0530

    add latest assets file

commit 489d92d2e2477b240015e83af07658e826eaa7cb
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 20:44:15 2019 +0530

    add missing os package

commit a7f0d86509d0845980b77999dc5882b32f244818
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 20:33:21 2019 +0530

    add zero mod fs back and commit file with latest ts

commit 798e1f3d3aba8d04c9118962870b41ad099b7bea
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 20:29:53 2019 +0530

    fix spacing issue

commit 7a6f3d4394003447c99b223ce1c1e19e23b20124
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 20:23:32 2019 +0530

    fix tests

commit 58ae5eb590667b0115d3f82e03593d87e2924b1c
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 20:06:44 2019 +0530

    move entire build system to go

commit 8547458f1d93654348b3bbeccb6d04424d5eab3e
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 19:01:47 2019 +0530

    remove zero mod fs

commit d09efb5fbd744f23d561c02b99a16a61e679bba6
Merge: 7745000 1327f58
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sun Aug 4 18:00:11 2019 +0530

    Merge pull request #837 from urfave/codeowners

    Use codeowners instead of maintainers

commit 1327f583142a70a5d2fb9ef8422862b84a1a8780
Merge: 9938dec 7745000
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sun Aug 4 17:52:42 2019 +0530

    Merge branch 'master' into codeowners

commit e949dc2cc05a553645eea447ded78eddfec0ad37
Merge: 11c9e59 7745000
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Sun Aug 4 10:38:56 2019 +0200

    Merge branch 'master' into master

commit 4b0a4104630bddd01a7a6c5aa8a70dc94642ab52
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 12:26:48 2019 +0530

    fix travis build

commit fb4cea5f30995b6caaa700ea789902af499d63b2
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 12:22:45 2019 +0530

    add new generation logic to travis

commit 365557021fe254d059d0f7065bb0c94686208723
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 12:17:27 2019 +0530

    remove legacy version check code

commit b6bfbe97f8430a4dfe05791363719d61cb921793
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 12:16:07 2019 +0530

    update appveyor to go 1.11, add support for code coverage generation in tests

commit 826954c97919610e0f4086ab3bcc007ac3326184
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 11:09:34 2019 +0530

    update app name and remove version

commit 04948f21526ed8343ae9697600e284962d8972be
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 11:06:19 2019 +0530

    generate test with go generate

commit 86e10211dea0c2b369610aabea1c987888bbb01a
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 11:05:50 2019 +0530

    remove redundant go generate from altsrc
    update go generate in cli package to generate both files
    regeneration test

commit c676ed4caa76219414ad737584ec46b03e94cbf8
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 11:04:23 2019 +0530

    indentation fixes in template
    regeneration test

commit c4fc88e46d182072228b2b2a0236b0f77a45e567
Merge: 2a08494 9260850
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 10:29:34 2019 +0530

    Merge branch 'flag-type-generation-golang' of https://github.com/urfave/cli into flag-type-generation-golang

commit 2a084945a47235959c023291f87127ead86fc168
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 10:28:08 2019 +0530

    move around code
    change package to flag-gen to avoid conflict with flag-generator binary
    test code generation

commit 065fe9e9af992d82126929c157edb16b1a1f06ab
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 10:05:44 2019 +0530

    change structure to embed source json and template files
    restructure code to have defaults in place of choices

commit d1ded77768d33ce64657686558884e69cbb5bce4
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sun Aug 4 08:45:29 2019 +0530

    rename utility from fg > flag-generator

commit 7745000a0eabd118a94df095da6feeae887f9a34
Merge: e6cf83e 81acbeb
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sun Aug 4 08:15:00 2019 +0530

    Merge pull request #774 from whereswaldon/patch-1

    Clarify that altsrc supports both TOML and JSON

commit 81acbeb629cebde2c8c3c36421644e42ffd9e8f9
Merge: 8abc5a2 e6cf83e
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sun Aug 4 08:12:24 2019 +0530

    Merge branch 'master' into patch-1

commit e6cf83ec39f6e1158ced1927d4ed14578fda8edb
Merge: 244eba7 eee6ce8
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 3 19:37:52 2019 -0700

    Merge pull request #829 from urfave/lynncyrin-patch-2

    Release 1.21.0

commit 8abc5a2e49624c8f12a0b1734b57bd12aadf1604
Merge: b2421d1 244eba7
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sun Aug 4 08:04:13 2019 +0530

    Merge branch 'master' into patch-1

commit 9938dec695d6a0ba5a4d84b703766333cd7d10e8
Author: [[ BOT ]] Lynn Cyrin <lynncyrin@gmail.com>
Date:   Sat Aug 3 10:26:07 2019 -0700

    update contributing docs

commit 97dbddb32db290fdc6392e6a669a92acbadef9ff
Author: [[ BOT ]] Lynn Cyrin <lynncyrin@gmail.com>
Date:   Sat Aug 3 10:23:29 2019 -0700

    use codeowners instead of maintainers

commit 92608509a4c011a598dcc5b10d15930040fa403e
Merge: d209be3 244eba7
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 3 22:49:24 2019 +0530

    Merge branch 'master' into flag-type-generation-golang

commit d209be324522a802f8056094f8bb89b4562ca9a3
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sat Aug 3 22:32:36 2019 +0530

    update go generate command
    test file generation

commit add69c7d4fbef52ac8541c2f7dfa465fdd9be2c3
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sat Aug 3 22:16:25 2019 +0530

    updated flag types generated courtesy fg cli

commit c133a5aeb16e0978e3c29f8446ddf02922b7e150
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sat Aug 3 22:15:43 2019 +0530

    add explicit true/false choices for value and dest keys due to go default false for bool types

commit 16c7a60528bc5f7d98030e09630a38f3d8fc9ddc
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sat Aug 3 22:14:58 2019 +0530

    finish generation of flag types for altsrc package
    rename package to fg (flag generator)

commit 32ddef5ca7f20a9aa0e7b80484e59d1653e856e4
Author: Ajitem Sahasrabuddhe <ajitem.sahasrabuddhe@perennialsys.com>
Date:   Sat Aug 3 21:48:48 2019 +0530

    finish generation of flag types for cli package

commit 9766be8d3e11e71a2a4bb5c235fc6961278df983
Author: mingrammer <mingrammer@gmail.com>
Date:   Thu Mar 7 00:04:18 2019 +0900

    get latest changes from master

commit e01e3c540c7aea9e4e9740d002bd212491c2fe00
Author: mingrammer <mingrammer@gmail.com>
Date:   Wed Mar 6 23:51:22 2019 +0900

    Fix the unaligned indents for the commands that have no categories

commit b1a7c502eba6a479bf9432052136f73d7740a69b
Merge: c75a689 244eba7
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 3 09:36:27 2019 -0700

    Merge branch 'master' into master

commit eee6ce83c075e8aeb1d71a22f69e6fddbc70a248
Merge: 8a7f65e 244eba7
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 3 09:06:47 2019 -0700

    Merge branch 'master' into lynncyrin-patch-2

commit 11c9e598b06a0a2201f356f945c4cd2355a8ccbf
Merge: 01ab016 244eba7
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Sat Aug 3 15:52:08 2019 +0200

    Merge branch 'master' into master

commit 244eba7e4c24eb9a416bb1edadaf74d943a7bb89
Merge: 1169906 4627bbe
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 3 18:04:31 2019 +0530

    Merge pull request #831 from saschagrunert/go-modules

    Add go module support

commit b2421d123539be62bc9f2f9bdd77fce3cc2d1af6
Merge: 3e14507 1169906
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sat Aug 3 13:25:34 2019 +0100

    Merge branch 'master' into patch-1

commit 4627bbe109fb902221de0a86f20048ad5679ea0c
Author: Sascha Grunert <mail@saschagrunert.de>
Date:   Sat Aug 3 12:55:06 2019 +0200

    Add go module support

    This adds a go.{mod,sum} file to official support go modules.

    Signed-off-by: Sascha Grunert <mail@saschagrunert.de>

commit 1169906f575ec070559cc1a6fc083b38498160c0
Merge: 07c1638 94f4f83
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sat Aug 3 11:02:34 2019 +0100

    Merge pull request #773 from teresy/redundant-nil-check-slice

    Remove redundant nil checks

commit 94f4f8367278436d4554b3e7a72ec6b66c209312
Merge: da581b2 07c1638
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Sat Aug 3 02:05:33 2019 -0700

    Merge branch 'master' into redundant-nil-check-slice

commit 07c163896936a0fb3ad24dcbf05b25e9c7aaee53
Merge: 842e3fe 7a51175
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 3 08:47:32 2019 +0530

    Merge pull request #806 from mingrammer/fix-help-indentation

    Fix the unaligned indents for the command help messages

commit 7a51175ce1cb78d5009e91001697240c1af84ee2
Merge: 330a914 842e3fe
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 3 08:37:34 2019 +0530

    Merge branch 'master' into fix-help-indentation

commit 842e3fe1b6adbb257f711a1f66553ad03174c5c0
Merge: 7675649 fa51d00
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Sat Aug 3 08:12:05 2019 +0530

    Merge pull request #828 from urfave/lynncyrin-patch-1

    Update maintainers for current reality

commit 8a7f65e05215a76a2246b7b42cb9c082b5eae483
Author: Lynn Cyrin (they/them) <lynn@textio.com>
Date:   Fri Aug 2 19:30:41 2019 -0700

    Update CHANGELOG.md

commit e8eac43d9d73e96f6b034f89770ab2fdbec2cb5b
Author: Lynn Cyrin <lynn@textio.com>
Date:   Fri Aug 2 18:26:41 2019 -0700

    Update CHANGELOG.md

commit 330a9143fb1ebfe61bb382b5e45833c02a405c0c
Merge: ddc3453 7675649
Author: Lynn Cyrin <lynn@textio.com>
Date:   Fri Aug 2 18:14:50 2019 -0700

    Merge branch 'master' into fix-help-indentation

commit fa51d00dc6fb57edca14295fde308401d7720e61
Author: Lynn Cyrin <lynn@textio.com>
Date:   Fri Aug 2 18:06:15 2019 -0700

    Update maintainers for current reality

commit 01ab0164275805b0c43bc5f7f534a2d8f6abe48e
Merge: d79d2a0 7675649
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Fri Aug 2 22:03:55 2019 +0200

    Merge branch 'master' into master

commit c75a689f629137700e8a30651f95cc41cf12a6d1
Author: Jordan Christiansen <Jordan.Christiansen@target.com>
Date:   Fri Aug 2 14:28:57 2019 -0500

    Make exit code example more clear

    The purpose of this example is to show that you can exit with an error
    code if a flag is unspecified, but with the code as it is, the only way
    to cause a non-zero exit is by adding the flag `--ginger-crouton=false`,
    which is not explained in the example.

    In this new version of the example, running the command with no flag
    will exit with an error, and running it with the flag will exit
    normally.

commit 7675649a174ac724b1d7fa9d5932eff3dd1582a2
Merge: 656063a f8ba505
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Fri Aug 2 22:52:32 2019 +0530

    Merge pull request #819 from lynncyrin/required-flags-take-2

    Required flags

commit f8ba505a7cc01559767d9d961fb68bda833d5d3d
Merge: 60fb297 656063a
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Fri Aug 2 22:49:29 2019 +0530

    Merge branch 'master' into required-flags-take-2

commit 656063a84689d3e45f16ab9c40706e4df219190a
Merge: 693af58 6505336
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Fri Aug 2 22:49:09 2019 +0530

    Merge pull request #788 from benzvan/master

    adds test coverage to context

commit 60fb2972328d6a7487c6821a58a86d476167c2bd
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 23:27:34 2019 -0700

    remove help assertion stuff

commit d7ec4e801357fa5ccfab53669a42f78fc1a69d39
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 23:26:43 2019 -0700

    add env var tests

commit f4128a02f3215e532dff0b96e21a8e2cb08389a1
Author: Lynn Cyrin <lynncyrin@gmail.com>
Date:   Thu Aug 1 22:54:15 2019 -0700

    Update command.go

commit 38f9e1622d2d4a5e1a86afd8f8f9d6cbf0157816
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 22:52:21 2019 -0700

    add environment variable support 🎉

commit f21b22dd904b638518d9ea321d718f219bd6593c
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 22:10:18 2019 -0700

    cleanup some issues with error display

commit fdd4d106912b363ccffa03eec51a56dd3a6a822b
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 21:48:52 2019 -0700

    update comments

commit ef9acb4a3b846728c98844f7f92964ae2a79f259
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 21:46:56 2019 -0700

    rename cases

commit 45f2b3d8e71e11822cf591f1c370f8587726c425
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 21:45:11 2019 -0700

    more test cases

commit 78db152323afb7934f9f0dd207eeaf34147bb300
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 21:35:15 2019 -0700

    add typed error assertions

commit d4740d10d0cbde53a8e3132a0964464b2b50fc0b
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 20:58:08 2019 -0700

    more test cases

commit 595382c50970039261f765043aee4c647aeccbd5
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 20:39:37 2019 -0700

    expand test cases

commit 3d6eec825ac768894a385ca3c3156a7905c27ce3
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 20:35:23 2019 -0700

    add test cases

commit 7b9e16b6b5255803ea279fe1ee0e41973f49a42e
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 20:30:43 2019 -0700

    update test names

commit 95d3a8624d8aa6661831d0009550b602458fcb4d
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 20:27:51 2019 -0700

    update test to reflect app flag usage

commit 714a73f028fa5a3c5b6512bf5e55b94be388de8f
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 19:57:14 2019 -0700

    remove unused thing

commit 9438aba3b89e7053070ef277121a14e5fb95947e
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Aug 1 19:54:57 2019 -0700

    remove showFlagError, we can use the help printer assertion to accomplish the same goal

commit 386b379d1950e8939c8a3dbba0335cf79903f421
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sun Jul 28 22:45:43 2019 -0700

    Revert "reset generated flags changes"

    This reverts commit 9ec594d5290b846de59b0bc350849b848cabfbd7.

commit 9ec594d5290b846de59b0bc350849b848cabfbd7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sun Jul 28 22:34:07 2019 -0700

    reset generated flags changes

commit 23f09ac1e82395dc1a70c36d649ab03929e32d79
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sun Jul 28 22:19:35 2019 -0700

    cleanup tests, check required flags in more places

commit d79d2a04242b21441061e00475287f4b826614f8
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Wed Jul 24 16:08:47 2019 +0200

    Fix issue with source command completion

    Avoid competion for bash builtin `source` and fallback to default implementation as it throws below error
    ```
    -bash: source: --: invalid option
    source: usage: source filename [arguments]
    ```

commit 7ce0af189ed431005f47e583a63648ea9a0a99ea
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 18 00:52:24 2019 -0700

    remove unused code

commit d8985dc6d56ac75b35f0422d8efbc04814bf17f3
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 18 00:51:16 2019 -0700

    reduce diff

commit 19140e1fb52f458727a3c718f82fb93861d5849c
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 18 00:48:09 2019 -0700

    show errors

commit 2299852c3c3512dafac738a10847da3bb3699b62
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 18 00:47:18 2019 -0700

    cleanup subcommand and specs

commit 300288670fe7713da8ae6e4a449d12e6c911b713
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 18 00:20:32 2019 -0700

    add subcommand

commit cc1cf8c459c947156bb429ef319f4cf762b1e468
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 18 00:09:07 2019 -0700

    wording shift

commit 32d84d8e870a7f475c228c3c58f2c879f6a4009e
Author: Lynn Cyrin <lynncyrin@gmail.com>
Date:   Wed Jul 17 00:25:13 2019 -0700

    copy update

commit 01d5cfab7066912c97eeaf94cbbda8f90fc490f7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Wed Jul 17 00:20:44 2019 -0700

    use strings.Join

commit cdc7af744e07ac8dbb34793f8b392af46ba443f7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Wed Jul 17 00:16:40 2019 -0700

    add handling for multiple required flags

commit 9293f5b3cc6f5a96a1976bf2f810c957044c5ee8
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sun Jul 14 21:00:16 2019 -0700

    visually shorten logic

commit f00f35ce8c1a6ebd7a3900901dd8b05049fbefc7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 14:02:45 2019 -0700

    docs

commit 17108e1db49db34480170f575131e642b22bda2d
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 13:59:29 2019 -0700

    tabs

commit cf824804c2353572e0f99b098829f2d7ffe2a0ec
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 13:57:06 2019 -0700

    update tests

commit 80d7e91191cfb38c7e3fccbcf2b1320807d4b05d
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 03:51:26 2019 -0700

    fill out test cases

commit 746866c10daf9425d41140f78ffc518ee4d9ae01
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 03:44:39 2019 -0700

    add update integration with the help output

commit 550ed20ea429e19b7b132984a6e34c057acabc42
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 01:26:47 2019 -0700

    update tests

commit f6777bf4bf44867abbcaa63a97a67db60469ea80
Author: Lynn Cyrin <lynn@textio.com>
Date:   Sat Jul 13 01:03:46 2019 -0700

    quote the flag name

commit 6a2ae783730e54eb7ea91cf6839ed46446134017
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 21:53:10 2019 -0700

    backwards compatible RequiredFlag implementation

commit 922d2318916c3b59eafde03c36b12551a71f2d51
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 21:28:09 2019 -0700

    ./generate-flag-types cli -i flag-types.json -o flag_generated.go

commit 8a58b7e039e37b0631d6ced0ab3279c319c4d8c8
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:47:47 2019 -0700

    remove manual isRequired funcs

commit 62e99ad1c16714cda6c9f8b980dd9483372771e2
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:46:22 2019 -0700

    add IsRequired to generator

commit 310bfeb1942571dfe0ac9f60f45e75df11189e4e
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:44:41 2019 -0700

    add required attr to generator

commit af627c73c3ddc2d4ff1e4c0847c3355bc0a47c0d
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:34:17 2019 -0700

    update func name

commit 3d2d6975b4fffee753c9422f3440d6b7c114ef40
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:32:42 2019 -0700

    reduce diff

commit 0608059cc709e86905bfd18886d6649275c9937e
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:32:15 2019 -0700

    reduce diff

commit 9c299e7e8af265e017adf7abf431a0fe0c89dd95
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:28:29 2019 -0700

    reduce diff

commit 30a71dc427bc2634f00d9fe315e5717022e0eb66
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:25:52 2019 -0700

    update Run command

commit f7d5e2c21e4cca02de26a7f448d69f4dac531af7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:22:16 2019 -0700

    reduce diff

commit e6842c0b7521b5e608da30a4e8a5ed06e6469cf7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:21:05 2019 -0700

    merge in test file

commit fa8187f2ce6a7d8258899b46ccfe081c9c0ea6f7
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:19:42 2019 -0700

    reduce diff

commit ce1630141e70b2ca599a21fd9494e98b88f25b2d
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:18:52 2019 -0700

    reduce diff???

commit 138dbaafec9db29d5b0b10af383ca7c6848a2c0d
Merge: aba73ce 693af58
Author: Lynn Cyrin <lynn@textio.com>
Date:   Thu Jul 11 20:07:55 2019 -0700

    Merge branch 'master' into required_flags

commit da581b24e88a3b8d5b3d4b7685f9eee32ec0df8e
Merge: 6aa7f35 693af58
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Fri Jun 28 07:55:04 2019 +0100

    Merge branch 'master' into redundant-nil-check-slice

commit 65053360c7533fc585bdb9f53abada1e9b39f564
Author: Ben Zvan <ben.zvan@target.com>
Date:   Wed Jun 26 09:41:11 2019 -0500

    Revert "Created using Colaboratory"

    This reverts commit 83b99c4109dce6ac7a6b5d2048e26f1ad60ef3f3.
    This commit was randomly created here when I connected to colaboratory

commit 83b99c4109dce6ac7a6b5d2048e26f1ad60ef3f3
Author: Ben Zvan <ben@zvan.net>
Date:   Tue Jun 25 18:47:58 2019 -0500

    Created using Colaboratory

commit 23042d37079702af32e4bcae8d41bb72569431da
Merge: 4a76377 693af58
Author: Ben Zvan <ben@zvan.net>
Date:   Fri Jun 14 10:14:07 2019 -0500

    Merge branch 'master' into master

commit 62f02f21ef0b5c3c0aa67d3240aee15bc8a53457
Author: Yogesh Lonkar <ylonkar@whitehedge.com>
Date:   Thu Apr 11 10:57:58 2019 +0530

    Don't complete hidden flags

commit 1d7a2b08d6f8e9764e2f2b911b1bb9fa49596f92
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Thu Mar 21 13:01:48 2019 +0530

    Add default completion on commands, test cases, refactor code

commit fb1421d9031313c5e0f3c4a92625ed9cf5739b0d
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Wed Mar 20 21:34:56 2019 +0530

    Fix duplicate completion of existing flag

commit 58a072d5733d4bb2dc61ffbc3557ec9592e34adc
Author: Yogesh Lonkar <lonkar.yogeshr@gmail.com>
Date:   Wed Mar 20 20:28:51 2019 +0530

    Add bash completion support for flags

commit ddc3453179ea450663473db4689f7c256225a72b
Author: mingrammer <mingrammer@gmail.com>
Date:   Thu Mar 7 00:04:18 2019 +0900

    Update README.md

commit a0453b2200cafa97ce263a4a5df87f5087d2abda
Author: mingrammer <mingrammer@gmail.com>
Date:   Wed Mar 6 23:51:22 2019 +0900

    Fix the unaligned indents for the commands that have no categories

commit 693af58b4d51b8fcc7f9d89576da170765980581
Merge: e229212 d7c3be8
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sun Feb 3 18:40:40 2019 +0000

    Merge pull request #766 from agis/patch-1

    Fix README typo

commit 6aa7f352fa56438b4c8fcaff43e1050855526051
Merge: 21dfc6e e229212
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sun Feb 3 18:39:49 2019 +0000

    Merge branch 'master' into redundant-nil-check-slice

commit e2292127695d01e9fc3511f2ec7ef651bf3ca8af
Merge: b67dcf9 5b83c89
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Sun Feb 3 18:37:18 2019 +0000

    Merge pull request #798 from Quasilyte/patch-1

    use type switch instead of if/else

commit 5b83c895a70b7714548f0aa4f43deb3fa5fc1601
Author: Iskander (Alex) Sharipov <quasilyte@gmail.com>
Date:   Tue Jan 29 22:51:02 2019 +0300

    use type switch instead of if/else

    This reduces the syntax noise of the code by
    removing excessive type assertions.

    Signed-off-by: Iskander Sharipov <quasilyte@gmail.com>

commit 4a76377775cebfc3dca4af752ba2837f9694b9d8
Author: Ben Zvan <benjamin.zvan@target.com>
Date:   Wed Dec 26 12:48:12 2018 -0600

    go fmt

commit d63733fe14aad10beca5490a453904bc1d67fe16
Author: Ben Zvan <benjamin.zvan@target.com>
Date:   Wed Dec 26 12:41:27 2018 -0600

    adds test coverage to context

commit b67dcf995b6a7b7f14fad5fcb7cc5441b05e814b
Merge: cbebba9 11ab68f
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Mon Oct 29 21:32:00 2018 +0000

    Merge pull request #776 from gliptak/patch-2

    Bring Go version current

commit 11ab68f24d392fc36615c650bc6241c0b96c4318
Merge: 769f6d5 cbebba9
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Mon Oct 29 21:19:45 2018 +0000

    Merge branch 'master' into patch-2

commit cbebba941b23ee6f666b057c9f3d0937263ddd01
Merge: 934abfb 9587fc2
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Mon Oct 29 21:18:40 2018 +0000

    Merge pull request #775 from gliptak/patch-1

    Correct typo

commit 769f6d543bd3c9b36b98e3a46ad646cf63769120
Author: Gábor Lipták <gliptak@gmail.com>
Date:   Thu Oct 18 21:00:02 2018 -0400

    Bring Go version current

commit 9587fc27bd923141975eac8c34288bcf8de5cca2
Author: Gábor Lipták <gliptak@gmail.com>
Date:   Thu Oct 18 20:56:13 2018 -0400

    Correct typo

commit 3e145076abdbaf5c6e47e311b5e659251604a49b
Author: Christopher Waldon <christopher.waldon.dev@gmail.com>
Date:   Fri Oct 12 11:30:46 2018 -0400

    Clarify that altsrc supports both TOML and JSON

commit 21dfc6eb8302c4db3547a22b0843c43e36fe058e
Author: teresy <hi.teresy@gmail.com>
Date:   Wed Oct 10 14:54:48 2018 -0400

    Remove redundant nil checks

commit d7c3be82673f869fed4ea77a0c5e3f13bd65ba89
Author: Agis Anastasopoulos <827224+agis@users.noreply.github.com>
Date:   Tue Aug 21 11:19:37 2018 +0300

    Fix README typo

commit 934abfb2f102315b5794e15ebc7949e4ca253920
Merge: 8e01ec4 3e5a935
Author: Audrius Butkevicius <audrius.butkevicius@gmail.com>
Date:   Tue Aug 21 07:40:27 2018 +0100

    Merge pull request #758 from vrothberg/fix-short-opts-parsing

    short opt handling: fix parsing

commit 3e5a935ed3cafadcddc6f5ab2fe7ddd2aa0c3cea
Author: Valentin Rothberg <vrothberg@suse.com>
Date:   Tue Aug 21 08:33:42 2018 +0200

    fix `go vet` warning

    command_test.go:342:3 value declared but not used

    Signed-off-by: Valentin Rothberg <vrothberg@suse.com>

commit c23dfba7018a4666892af705d89150a5f1ac8293
Author: Valentin Rothberg <vrothberg@suse.com>
Date:   Thu Jun 28 16:41:02 2018 +0200

    short opt handling: fix parsing

    Only split a given string (e.g., "-abc") into short options (e.g., "-a",
    "-b", "-c") if all those are flags.  To further avoid mistakenly
    transform common arguments, catch "flag provided but not defined" errors
    to iteratively transform short options.

    Signed-off-by: Valentin Rothberg <vrothberg@suse.com>
    Fixes: https://github.com/projectatomic/libpod/issues/714

commit 8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff
Merge: d4bf9ce 8dc47eb
Author: Dan Buch <dan@meatballhat.com>
Date:   Sun Feb 25 22:02:53 2018 -0500

    Merge pull request #598 from urfave/backport-json-support

    Backport JSON InputSource to v1

commit 8dc47eb3cbaea99e0d3e04424f6f3649657a6944
Merge: f551359 d4bf9ce
Author: Dan Buch <dan@meatballhat.com>
Date:   Sun Feb 25 16:09:48 2018 -0500

    Merge branch 'master' into backport-json-support

commit d4bf9ce8609adfc60775b62676fa3471e7d978e0
Merge: b09aafd e59e474
Author: Dan Buch <dan@meatballhat.com>
Date:   Sun Feb 25 16:09:04 2018 -0500

    Merge pull request #498 from urfave/merging-jereksel-zsh

    Merging #489 (plus hack)

commit e59e4743b884a22e06b1b78a893c68513c0702b7
Merge: 5fc8124 b09aafd
Author: Dan Buch <dan@meatballhat.com>
Date:   Sat Feb 24 22:02:40 2018 -0500

    Merge branch 'master' into merging-jereksel-zsh

commit b09aafdfe9ad3fa4353b82eaed4e80598878c128
Merge: 446f49e bc77a15
Author: Dan Buch <dan@meatballhat.com>
Date:   Sat Feb 24 22:02:19 2018 -0500

    Merge pull request #681 from urfave/check-run-error-in-readme

    Update README examples to check for errors

commit 5fc8124af17aae44085140a2a7b3141a973dbda0
Merge: 688c5a9 446f49e
Author: Dan Buch <dan@meatballhat.com>
Date:   Sat Feb 24 21:58:26 2018 -0500

    Merge branch 'master' into merging-jereksel-zsh

commit bc77a15c69f9aadd39b15be9373abc4916c8ad53
Merge: 59e1ddb 446f49e
Author: Dan Buch <dan@meatballhat.com>
Date:   Sat Feb 24 21:54:06 2018 -0500

    Merge branch 'master' into check-run-error-in-readme

commit 446f49e78f79a164079a99a88446182703331c75
Merge: a1c7408 45289ea
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Tue Feb 20 21:16:31 2018 -0800

    Merge pull request #715 from urfave/maintainers-notice

    Adjust contribution and maintainer prose per current reality

commit 45289ea7a0de564a71532e13b9916961a38abc8e
Author: Dan Buch <dan@meatballhat.com>
Date:   Tue Feb 20 12:40:43 2018 -0500

    Adjust contribution and maintainer prose per current reality

commit 59e1ddb43ed48e52bf1a0aca8a310d9bca9897d7
Merge: 9838c8b a1c7408
Author: Dan Buch <dan@meatballhat.com>
Date:   Tue Feb 13 15:27:04 2018 -0500

    Merge branch 'master' into check-run-error-in-readme

commit a1c7408de3f632d86eee604a3bb755f1ffb68226
Merge: 803d066 3a87b13
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sat Feb 10 18:18:39 2018 -0800

    Merge pull request #712 from windler/fix_args_reorder

    Fix args reordering when bool flags are present

commit 3a87b13b01ac8628694f1e1b20bdb452cc0f54d2
Author: Nico Windler <nico.windler@gmail.com>
Date:   Sat Feb 10 13:35:23 2018 +0100

    Fix args reordering when bool flags are present

commit 803d0665796d3b09d3190067803fc285d1604732
Merge: 75104e9 d7555e1
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Fri Feb 2 13:13:18 2018 -0800

    Merge pull request #704 from dolmen/replace-unneeded-Sprintf

    Fix unnecessary uses of Sprintf

commit d7555e172994da8d058334aa1fe69533b1685924
Author: Olivier Mengué <dolmen@cpan.org>
Date:   Fri Jan 26 21:14:34 2018 +0100

    Fix unnecessary uses of Sprintf

    - use strconv directly
    - use concatenation for "%s%s"

commit 75104e932ac2ddb944a6ea19d9f9f26316ff1145
Merge: 39908eb e38e4ae
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sat Jan 6 11:10:48 2018 -0800

    Merge pull request #697 from urfave/fix-skip-flag-parsing

    Fix regression of SkipFlagParsing behavior

commit e38e4ae2d05acf5b5164c160a67fb7048e1358b0
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Fri Dec 29 13:38:18 2017 -0500

    Fix regression of SkipFlagParsing behavior

    Introduced by df562bf1a8626f2d16f91fcbf7230a5bdca3d592

    Was mistakenly prepending the command name.

commit 39908eb08fee7c10d842622a114a5c133fb0a3c6
Merge: 119bb65 2610681
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Tue Dec 12 08:34:29 2017 -0800

    Merge pull request #691 from urfave/refactor-686

    Refactor flag handling logic

commit 2610681040722bb0a9d04c3a784a44d2efb52379
Merge: 0671b16 119bb65
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Mon Dec 11 18:51:46 2017 -0800

    Merge branch 'master' into refactor-686

commit 0671b166dcacb3dc1215ba65bf986dab194581dc
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Mon Dec 4 09:23:40 2017 -0800

    Add tests for flag reordering

commit 119bb6564841921ce6f1401e0f5d75317bdd9f4d
Merge: c9eba3f c6eb2a0
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sun Dec 3 13:42:37 2017 -0800

    Merge pull request #690 from gliptak/patch-1

    Correct go vet for Go tip

commit df562bf1a8626f2d16f91fcbf7230a5bdca3d592
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sun Dec 3 13:38:50 2017 -0800

    Refactor flag handling logic

    Refactor logic introduced by #686

commit c9eba3f37a524c4fed60a8f3585ea5f304fd436d
Merge: c6af884 ceaac7c
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sun Dec 3 12:48:28 2017 -0800

    Merge pull request #686 from baude/shortoptionSkipArg

    Handle ShortOptions and SkipArgReorder

commit c6eb2a051026c083d4e33591f8d6e95d5f4189dc
Author: Gábor Lipták <gliptak@gmail.com>
Date:   Thu Nov 30 19:43:12 2017 -0500

    Correct go vet for Go tip

    https://travis-ci.org/cloudflare/logshare/jobs/309796141#L646

commit ceaac7c9152121e6ba0f3b492b3254d61346f92a
Author: baude <bbaude@redhat.com>
Date:   Mon Nov 20 09:32:03 2017 -0600

    Handle ShortOptions and SkipArgReorder

    There was a bug in parsing when both ShortOptions
    and SkipArgReorder were being used together.

    Signed-off-by: baude <bbaude@redhat.com>

commit c6af8847eb2b7b297d07c3ede98903e95e680ef9
Merge: 7ace96b 37b7abb
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Mon Nov 27 19:55:04 2017 -0800

    Merge pull request #687 from joshuarubin/master

    Don't clobber slices with EnvVar

commit 37b7abb1c491c8c3630a2a98bb02a7051efbcc06
Author: Joshua Rubin <jrubin@zvelo.com>
Date:   Tue Nov 21 15:21:31 2017 -0700

    dont clobber slices with envvar

    Signed-off-by: Joshua Rubin <jrubin@zvelo.com>

commit 7ace96b43d4bdc46f81d0d1219742b2469874cf6
Merge: 44cb242 fd5382e
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Wed Nov 15 20:56:12 2017 -0800

    Merge pull request #684 from baude/shortOptionHandling

    Combine bool short names

commit fd5382e7a539858cc19d7eed7755f7102bae5da9
Author: baude <bbaude@redhat.com>
Date:   Mon Nov 13 15:28:23 2017 -0600

    Combine bool short names

    Adds the ability to allow the combination of bool
    short-name options.  For example,

    cmd foobar -ov

    This is done through a bool "UseShortOptionHandler" set in
    the command struct.

    Built upon PR #621

    Signed-off-by: baude <bbaude@redhat.com>

commit 9838c8bcaa19fdb33259f6e0f9740d9fd3cbe13c
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sat Nov 11 16:23:24 2017 -0800

    Update README examples to check for errors

    To encourage good practices.

commit 43c8c02cf5a10196e5a4c458fdbfee90a561e97c
Author: zhuchensong <zhuchensong93@163.com>
Date:   Mon Apr 17 00:47:04 2017 +0800

    Support POSIX-style short flag combining

commit 44cb242eeb4d76cc813fdc69ba5c4b224677e799
Merge: 7f4b273 f971fca
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Fri Nov 3 19:35:40 2017 -0700

    Merge pull request #675 from jmccann/continue3

    Ability to load variable from file - UPDATED

commit f971fca2b2664c4dec0cee24225dc3c415211498
Author: Jacob McCann <jacob.mccann2@target.com>
Date:   Thu Oct 26 13:08:03 2017 -0500

    Allow FilePath to take []string

commit 18a556e1927fbe11c31fae47a7e3acf275ef6ae4
Author: Brad Rydzewski <brad.rydzewski@gmail.com>
Date:   Mon Apr 10 16:45:51 2017 +0200

    fix FilePath documentation in README.md

commit 4cc453ba6792515a8013340f8919e6c4b44851b7
Author: Brad Rydzewski <brad.rydzewski@gmail.com>
Date:   Sat Apr 1 12:55:46 2017 +0900

    document field in README

commit c698b821b896e9723d53c4ad1e81680f39a8cdc1
Author: Brad Rydzewski <brad.rydzewski@gmail.com>
Date:   Sat Apr 1 12:37:06 2017 +0900

    unit tests for load from file

commit 21fcab0dee7dab6969e929cf1740306bae1e16ad
Author: Brad Rydzewski <brad.rydzewski@gmail.com>
Date:   Fri Mar 31 16:24:15 2017 +0900

    ability to load variable from file

commit 7f4b273a05858e05b96b6adf0a7907b7b695c352
Merge: 7bc6a0a b44660a
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Mon Oct 30 19:55:34 2017 -0700

    Merge pull request #676 from rliebz/lexicographic-sort

    Consider case when sorting strings

commit b44660ac3da2f8e651372c40ae803782bddea283
Author: Robert Liebowitz <rliebz@gmail.com>
Date:   Sat Oct 28 03:00:11 2017 -0400

    Consider case when sorting strings

    This makes sorting flags and other sections consistent with how most
    command line tools function, by placing both flags `-A` and `-a` before
    a flag `-B`.

commit 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
Merge: 2997500 40263f4
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sat Oct 14 13:27:26 2017 -0700

    Merge pull request #628 from phinnaeus/master

    Allow custom ExitError handler function

commit 40263f4d6aaf7383fb53bd97a5c34c02be39eda8
Merge: 7233c50 2997500
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Fri Oct 13 12:05:14 2017 -0700

    Merge branch 'master' into master

commit 2997500ba5f393daa5d76c18544c6367b2c48d16
Merge: ac24947 c202606
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sat Oct 7 13:56:23 2017 -0700

    Merge pull request #672 from sierraechobravo/master

    fix go report card issues

commit c202606a17a763fcc1b320cac6cf584662e31364
Author: Sebastian Sprenger <Sebastian.Sprenger@booxware.de>
Date:   Fri Oct 6 07:29:13 2017 +0200

    fix golint issues

commit c3cc74dac756e33c2919ab998481809e8720e068
Author: Sebastian Sprenger <Sebastian.Sprenger@booxware.de>
Date:   Fri Oct 6 07:28:43 2017 +0200

    fix ineffective assigns

commit 67ee172e6da2cdad8e48af107eef0fbfd1e85eec
Author: Sebastian Sprenger <Sebastian.Sprenger@booxware.de>
Date:   Fri Oct 6 07:28:18 2017 +0200

    fix misspelling issue

commit ac249472b7de27a9e8990819566d9be95ab5b816
Merge: 7fb9c86 cbbe4c1
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Mon Sep 25 20:41:18 2017 -0700

    Merge pull request #661 from rliebz/custom-flag-help

    Allow customization of prefixes and environment variable hints in flag help strings

commit 7233c502e31b3e6d93e3e8cf5fe0616f4d32c020
Merge: 5dc55f2 7fb9c86
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Fri Sep 22 10:08:36 2017 -0700

    Merge branch 'master' into master

commit cbbe4c1a2c34e52c8ad0937c01c9c15ef407a6d5
Author: Robert Liebowitz <rliebz@gmail.com>
Date:   Mon Sep 18 00:44:42 2017 -0400

    Add tests for custom flag prefix/env hints

commit 11d45572f9727acfbc93daa8565f379d396125d6
Author: rliebz <rliebz@gmail.com>
Date:   Sat Aug 26 07:42:25 2017 -0400

    Export funcs to configure flag prefix/env hints

    This will allow users to customize the prefix section or env hint
    section of the flag entries in the help menu without having to
    reimplement the rest of the logic required in defining FlagStringer.

commit 7fb9c86b14e6a702a4157ccb5a863f07d844a207
Merge: f017f86 1d334f1
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Sun Sep 10 21:08:19 2017 -0700

    Merge pull request #664 from maguro/master

    Add newline before command categories

commit 1d334f10ce73c2b9e65c50a2290a86be3c743ff2
Author: Alan D. Cabrera <adc@toolazydogs.com>
Date:   Fri Sep 8 10:37:48 2017 -0700

    Add newline before command categories

    The simple formatting change adds a nice blank line before each command
    category.  Documentation in README.md is also updated to be more
    accurate.

commit 5dc55f22878a35487bdc17393f391bf25142c6e3
Merge: 10e81ba f017f86
Author: Dan Buch <dan@meatballhat.com>
Date:   Sun Aug 13 12:42:49 2017 -0400

    Merge branch 'master' into master

commit f017f86fccc5a039a98f23311f34fdf78b014f78
Merge: cfb3883 44c6487
Author: Dan Buch <dan@meatballhat.com>
Date:   Sun Aug 13 10:59:49 2017 -0400

    Merge pull request #659 from urfave/define-flag-precedence

    Define flag source precedence in README

commit 44c648739b75283265541baca66ed984476a17f5
Merge: e1fa109 cfb3883
Author: Dan Buch <dan@meatballhat.com>
Date:   Sun Aug 13 10:54:04 2017 -0400

    Merge branch 'master' into define-flag-precedence

commit cfb38830724cc34fedffe9a2a29fb54fa9169cd1
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Thu Aug 10 18:42:03 2017 -0700

    Prepare CHANGELOG for v1.20.0 release

commit f5513590f52b5f90566a10ad9452f52dffd469f8
Merge: 6a70c4c b99aa81
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Thu Aug 10 18:06:12 2017 -0700

    Merge branch 'master' into backport-json-support

commit e1fa109a3195a9fedcb635841ca1907b764ada1f
Author: Jesse Szwedko <jesse.szwedko@gmail.com>
Date:   Thu Aug 10 17:54:24 2017 -0700

    Define flag source precedence in README

    Fixes #646

commit 688c5a9d4f3beffff9d4fa50bd85907b7067d9a4
Merge: 7250c97 4b90d79
Author: Dan Buch <dan@meatballhat.com>
Date:   Thu Aug 3 14:38:20 2017 -0400

    Merge branch 'master' into merging-jereksel-zsh

commit 10e81bacd12f5c5a44d3a3e2d6e168d0c2533245
Merge: 5d528e2 4b90d79
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Thu Jul 20 12:44:56 2017 -0700

    Merge branch 'master' into master

commit 5d528e2052b3e7a49293d6aa0fac245047ea61e3
Author: Tyler Davis <tyler.davis@gmail.com>
Date:   Wed Jun 28 13:04:09 2017 -0700

    use exit errors in uts

commit 58450552ee1bada60f4175897aff8d69f7c904a1
Author: Tyler Davis <tyler.davis@gmail.com>
Date:   Wed Jun 28 12:52:50 2017 -0700

    Add Test

commit 71bdf81f5a65dc253482cb727c2ae973ae3b3830
Author: Tyler Davis <tyler.davis@gmail.com>
Date:   Wed Jun 28 10:10:11 2017 -0700

    sigh... fix one more named parameter issue

commit 172bb92059ed885c8b4249230f3ccbe9e3e1272b
Author: Tyler Davis <tyler.davis@gmail.com>
Date:   Wed Jun 28 10:07:25 2017 -0700

    fix named parameter issue

commit 530df59178874f8d792d2d9cfd745464076f1eda
Author: Tyler Davis <tyler.davis@gmail.com>
Date:   Wed Jun 28 09:52:12 2017 -0700

    Pass context into handleExitCoder

commit 9d61cbad0260bc7f2a72b07142a0120072e3800a
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Tue Apr 25 12:45:08 2017 -0700

    Updated command.go to use App handleExitCoder

commit ceee6408d5cbbb9f113157d0a62b1ffed1f2b510
Author: Tyler Davis <tyler.davis@gmail.com>
Date:   Tue Apr 25 13:02:05 2017 -0700

    Revert "Fix how to do defaults in app.go"

    This reverts commit 8906567dc2ad52fd31c50cf02fa606505a1323ba.

commit 80b09a4d1117ad69430582685e59dfe560caa948
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Tue Apr 25 11:20:41 2017 -0700

    Fix how to do defaults in app.go

commit 827da610b4bff0ffbc06cd2d92eddae552f7d1a2
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Tue Apr 25 09:33:54 2017 -0700

    Add a bit more documentation

commit 538742687bbd979a7b4f975468af76ce5cffb972
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Tue Apr 25 09:31:53 2017 -0700

    Add ExitErrHandlerFunc type

commit c48a82964028acd0f19ee17257789f7c9f5afc78
Author: Tyler Davis <phinnaeus@users.noreply.github.com>
Date:   Tue Apr 25 09:29:43 2017 -0700

    Allow custom exit err handlers

commit 6a70c4cc923c7359bacfa0500dc234d62e0ca986
Author: John Weldon <johnweldon4@gmail.com>
Date:   Sat Jul 2 12:35:48 2016 -0700

    Add JSON InputSource to altsrc package

     - Implement NewJSONSource* functions for returning an InputSource from
       various JSON data sources.
     - Copy and modify YAML tests for the JSON InputSource

    Changes:

    * Reverted the method calls and structs to match the v1 interface

commit 7250c97913c213f17c721cb3fac5e2f555b198ca
Merge: 363d9c9 0bdedde
Author: Dan Buch <dan@meatballhat.com>
Date:   Wed Dec 21 15:11:00 2016 -0500

    Merge branch 'master' into merging-jereksel-zsh

commit 363d9c9a314cdb9ed68cad1a27c767b45eee8840
Author: Dan Buch <daniel.buch@gmail.com>
Date:   Sun Jul 24 17:29:13 2016 -0400

    Add a hack so that zsh completion only runs for zsh

commit 1cbb9a7f300b11a8e5a92b1fb24d8aeb168e0275
Merge: e43a9fb ceeebab
Author: Dan Buch <daniel.buch@gmail.com>
Date:   Sun Jul 24 17:12:43 2016 -0400

    Merge branch 'zsh' of https://github.com/jereksel/cli into jereksel-zsh

commit ceeebaba04790bab2ecd03caded488528b0caf97
Author: Andrzej Ressel <jereksel@gmail.com>
Date:   Thu Jul 21 00:02:16 2016 +0200

    [PoC] Improve zsh autocompletions

commit aba73cedacbb7b1cec2efb9962460683cd00a90c
Author: jhowarth <jhowarth@riotgames.com>
Date:   Tue Mar 3 14:02:42 2015 -0800

    Copy the writer of the App to the subcommand App

commit a6482d268753644175e769dd91ca3a4dfe838964
Merge: b5844af 50c77ec
Author: jhowarth <jhowarth@riotgames.com>
Date:   Mon Mar 2 15:21:01 2015 -0800

    Merge remote-tracking branch 'upstream/master'

    Conflicts:
    	app.go
    	command.go
    	flag.go

commit b5844af29892a881ea1d22ed0082f1e0a1559bfa
Merge: 8f1fb06 145da32
Author: Jesse Howarth <jahowarth@gmail.com>
Date:   Mon Mar 2 14:53:57 2015 -0800

    Merge pull request #2 from ivey/requiredFlags

    Required flags

commit 145da3210f41f401b1f42a08385d11ee8a80ec97
Author: jhowarth <jhowarth@riotgames.com>
Date:   Mon Mar 2 12:06:42 2015 -0800

    don't require flags when the help flag is included

commit 6023f370c1dfea78d4ff99a6ecc6be261347bfc9
Author: jhowarth <jhowarth@riotgames.com>
Date:   Mon Mar 2 12:00:21 2015 -0800

    dry error messages

commit e67e05f617978eec7bba579a6c86f3d0c11ad96b
Author: jhowarth <jhowarth@riotgames.com>
Date:   Mon Mar 2 11:56:29 2015 -0800

    DRY error handling

commit cbd95292ac9c4ba7eb30ca121fbe3825ced64f72
Author: jhowarth <jhowarth@riotgames.com>
Date:   Mon Mar 2 11:18:59 2015 -0800

    Remove debugging

commit 8f1fb06a585610fdb76d38bc67a5edc89da4e82f
Merge: 9908e96 4b2fcdb
Author: Jesse Howarth <jahowarth@gmail.com>
Date:   Tue Dec 2 15:23:01 2014 -0800

    Merge pull request #1 from ivey/required_flags

    Required flags

commit 4b2fcdb1ade79300c56074de8e7a7bf754cd407e
Author: Jesse Howarth and Michael Ivey <datdevs+jhowarth+michael.ivey@riotgames.com>
Date:   Tue Dec 2 21:08:24 2014 +0000

    Add tests for required flags

commit 73e64a14fde90fc3e85fdebb3647af6024e48de0
Author: Jesse Howarth and Michael Ivey <datdevs+jhowarth+michael.ivey@riotgames.com>
Date:   Tue Dec 2 19:02:56 2014 +0000

    Add (required) to help of flags that are required.

commit 7e0532002650b69f219f34f4614656261be45363
Author: Jesse Howarth and Michael Ivey <datdevs+jhowarth+michael.ivey@riotgames.com>
Date:   Tue Dec 2 17:44:55 2014 +0000

    Implement required flags
This commit is contained in:
Ajitem Sahasrabuddhe 2019-09-13 04:56:14 +05:30
parent b626059537
commit c75fee9224
No known key found for this signature in database
GPG Key ID: 782DEBC01D3967A5
72 changed files with 5468 additions and 2738 deletions

4
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,4 @@
# See https://help.github.com/articles/about-codeowners/
# for more info about CODEOWNERS file
* @urfave/cli

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.coverprofile
node_modules/
vendor

View File

@ -1,28 +1,35 @@
language: go
sudo: false
dist: trusty
osx_image: xcode8.3
go: 1.11.x
dist: bionic
osx_image: xcode10
go:
- 1.11.x
- 1.12.x
- 1.13.x
os:
- linux
- osx
- linux
- osx
env:
GO111MODULE=on
GOPROXY=https://proxy.golang.org
cache:
directories:
- node_modules
- node_modules
before_script:
- if [[ $(uname) == Darwin ]]; then
sudo pip2 install flake8;
else
pip install --user flake8;
fi
- mkdir -p ${GOPATH%%:*}/src/gopkg.in/urfave
- rm -rvf ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2
- rm -rvf ${GOPATH%%:*}/pkg/*/gopkg.in/urfave/cli.v2.a
- ln -sv ${TRAVIS_BUILD_DIR} ${GOPATH%%:*}/src/gopkg.in/urfave/cli.v2
- go get github.com/urfave/gfmrun/cmd/gfmrun
- go get golang.org/x/tools/cmd/goimports
- npm install markdown-toc
- go mod tidy
script:
- flake8 runtests cli-v1-to-v2 generate-flag-types
- make all
- go run build.go vet
- go run build.go test
- go run build.go gfmrun
- go run build.go toc
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -39,11 +39,110 @@
### Removed
## [1.22.1] - 2019-09-11
### Fixed
### Deprecated
* Hide output of hidden commands on man pages in [urfave/cli/pull/889](https://github.com/urfave/cli/pull/889) via [@crosbymichael](https://github.com/crosbymichael)
* Don't generate fish completion for hidden commands [urfave/cli/pull/891](https://github.com/urfave/891) via [@saschagrunert](https://github.com/saschagrunert)
* Using short flag names for required flags throws an error in [urfave/cli/pull/890](https://github.com/urfave/cli/pull/890) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
### Security
### Changed
* Remove flag code generation logic, legacy python test runner in [urfave/cli/pull/883](https://github.com/urfave/cli/pull/883) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
* Enable Go Modules support, drop support for `Go 1.10` add support for `Go 1.13` in [urfave/cli/pull/885](https://github.com/urfave/cli/pull/885) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
## [1.22.0] - 2019-09-07
### Fixed
* Fix Subcommands not falling back to `app.ExitEventHandler` in [urfave/cli/pull/856](https://github.com/urfave/cli/pull/856) via [@FaranIdo](https://github.com/FaranIdo)
### Changed
* Clarify that altsrc supports both TOML and JSON in [urfave/cli/pull/774](https://github.com/urfave/cli/pull/774) via [@whereswaldon](https://github.com/whereswaldon)
* Made the exit code example more clear in [urfave/cli/pull/823](https://github.com/urfave/cli/pull/823) via [@xordspar0](https://github.com/xordspar0)
* Removed the use of python for internal flag generation in [urfave/cli/pull/836](https://github.com/urfave/cli/pull/836) via [@asahasrabuddhe](https://github.com/asahasrabuddhe)
* Changed the supported go versions to `1.10`, `1.11`, `1.12` in [urfave/cli/pull/843](https://github.com/urfave/cli/pull/843) via [@lafriks](https://github.com/lafriks)
* Changed the v1 releases section in the readme in [urfave/cli/pull/862](https://github.com/urfave/cli/pull/862) via [@russoj88](https://github.com/russoj88)
* Cleaned up go modules in [urfave/cli/pull/874](https://github.com/urfave/cli/pull/874) via [@saschagrunert](https://github.com/saschagrunert)
### Added
* Added `UseShortOptionHandling` for combining short flags in [urfave/cli/pull/735](https://github.com/urfave/cli/pull/735) via [@rliebz](https://github.com/rliebz)
* Added support for flags bash completion in [urfave/cli/pull/808](https://github.com/urfave/cli/pull/808) via [@yogeshlonkar](https://github.com/yogeshlonkar)
* Added the `TakesFile` indicator to flag in [urfave/cli/pull/851](https://github.com/urfave/cli/pull/851) via [@saschagrunert](https://github.com/saschagrunert)
* Added fish shell completion support in [urfave/cli/pull/848](https://github.com/urfave/cli/pull/848) via [@saschagrunert](https://github.com/saschagrunert)
## [1.21.0] - 2019-08-02
### Fixed
* Fix using "slice" flag types with `EnvVar` in [urfave/cli/pull/687](https://github.com/urfave/cli/pull/687) via [@joshuarubin](https://github.com/joshuarubin)
* Fix regression of `SkipFlagParsing` behavior in [urfave/cli/pull/697](https://github.com/urfave/cli/pull/697) via [@jszwedko](https://github.com/jszwedko)
* Fix handling `ShortOptions` and `SkipArgReorder` in [urfave/cli/pull/686](https://github.com/urfave/cli/pull/686) via [@baude](https://github.com/baude)
* Fix args reordering when bool flags are present in [urfave/cli/pull/712](https://github.com/urfave/cli/pull/712) via [@windler](https://github.com/windler)
* Fix parsing of short options in [urfave/cli/pull/758](https://github.com/urfave/cli/pull/758) via [@vrothberg](https://github.com/vrothberg)
* Fix unaligned indents for the command help messages in [urfave/cli/pull/806](https://github.com/urfave/cli/pull/806) via [@mingrammer](https://github.com/mingrammer)
### Changed
* Cleaned up help output in [urfave/cli/pull/664](https://github.com/urfave/cli/pull/664) via [@maguro](https://github.com/maguro)
* Remove redundant nil checks in [urfave/cli/pull/773](https://github.com/urfave/cli/pull/773) via [@teresy](https://github.com/teresy)
* Case is now considered when sorting strings in [urfave/cli/pull/676](https://github.com/urfave/cli/pull/676) via [@rliebz](https://github.com/rliebz)
### Added
* Added _"required flags"_ support in [urfave/cli/pull/819](https://github.com/urfave/cli/pull/819) via [@lynncyrin](https://github.com/lynncyrin/)
* Backport JSON `InputSource` to v1 in [urfave/cli/pull/598](https://github.com/urfave/cli/pull/598) via [@jszwedko](https://github.com/jszwedko)
* Allow more customization of flag help strings in [urfave/cli/pull/661](https://github.com/urfave/cli/pull/661) via [@rliebz](https://github.com/rliebz)
* Allow custom `ExitError` handler function in [urfave/cli/pull/628](https://github.com/urfave/cli/pull/628) via [@phinnaeus](https://github.com/phinnaeus)
* Allow loading a variable from a file in [urfave/cli/pull/675](https://github.com/urfave/cli/pull/675) via [@jmccann](https://github.com/jmccann)
* Allow combining short bool names in [urfave/cli/pull/684](https://github.com/urfave/cli/pull/684) via [@baude](https://github.com/baude)
* Added test coverage to context in [urfave/cli/pull/788](https://github.com/urfave/cli/pull/788) via [@benzvan](https://github.com/benzvan)
* Added go module support in [urfave/cli/pull/831](https://github.com/urfave/cli/pull/831) via [@saschagrunert](https://github.com/saschagrunert)
## [1.20.0] - 2017-08-10
### Fixed
* `HandleExitCoder` is now correctly iterates over all errors in
a `MultiError`. The exit code is the exit code of the last error or `1` if
there are no `ExitCoder`s in the `MultiError`.
* Fixed YAML file loading on Windows (previously would fail validate the file path)
* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly
propogated
* `ErrWriter` is now passed downwards through command structure to avoid the
need to redefine it
* Pass `Command` context into `OnUsageError` rather than parent context so that
all fields are avaiable
* Errors occuring in `Before` funcs are no longer double printed
* Use `UsageText` in the help templates for commands and subcommands if
defined; otherwise build the usage as before (was previously ignoring this
field)
* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if
a program calls `Set` or `GlobalSet` directly after flag parsing (would
previously only return `true` if the flag was set during parsing)
### Changed
* No longer exit the program on command/subcommand error if the error raised is
not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was
determined to be a regression in functionality. See [the
PR](https://github.com/urfave/cli/pull/595) for discussion.
### Added
* `CommandsByName` type was added to make it easy to sort `Command`s by name,
alphabetically
* `altsrc` now handles loading of string and int arrays from TOML
* Support for definition of custom help templates for `App` via
`CustomAppHelpTemplate`
* Support for arbitrary key/value fields on `App` to be used with
`CustomAppHelpTemplate` via `ExtraInfo`
* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be
`cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag`
interface to be used.
## [1.19.1] - 2016-11-21
### Fixed
@ -403,7 +502,13 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Added
- Initial implementation.
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD
[Unreleased]: https://github.com/urfave/cli/compare/v1.22.1...HEAD
[1.22.1]: https://github.com/urfave/cli/compare/v1.22.0...v1.22.1
[1.22.0]: https://github.com/urfave/cli/compare/v1.21.0...v1.22.0
[1.21.0]: https://github.com/urfave/cli/compare/v1.20.0...v1.21.0
[1.20.0]: https://github.com/urfave/cli/compare/v1.19.1...v1.20.0
[1.19.1]: https://github.com/urfave/cli/compare/v1.19.0...v1.19.1
[1.19.0]: https://github.com/urfave/cli/compare/v1.18.0...v1.19.0
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0

74
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be
reviewed and investigated and will result in a response that is deemed necessary
and appropriate to the circumstances. The project team is obligated to maintain
confidentiality with regard to the reporter of an incident. Further details of
specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org

18
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,18 @@
## Contributing
Use @urfave/cli to ping the maintainers.
Feel free to put up a pull request to fix a bug or maybe add a feature. We will
give it a code review and make sure that it does not break backwards
compatibility. If collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability
to merge others pull requests. It is very important that new code does not
break existing code, so be careful about what code you do choose to merge.
If you feel like you have contributed to the project but have not yet been added
as a collaborator, we probably forgot to add you :sweat_smile:. Please open an
issue!

366
README.md
View File

@ -3,15 +3,11 @@ cli
[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli)
[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli)
[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli)
[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli)
[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) /
[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc)
**Notice:** This is the library formerly known as
`github.com/codegangsta/cli` -- Github will automatically redirect requests
to this repository, but we recommend updating your references for clarity.
[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli)
cli is a simple, fast, and fun package for building command line apps in Go. The
goal is to enable developers to write fast and distributable command line
@ -23,7 +19,7 @@ applications in an expressive way.
- [Installation](#installation)
* [Supported platforms](#supported-platforms)
* [Using the `v2` branch](#using-the-v2-branch)
* [Pinning to the `v1` releases](#pinning-to-the-v1-releases)
* [Using `v1` releases](#using-v1-releases)
- [Getting Started](#getting-started)
- [Examples](#examples)
* [Arguments](#arguments)
@ -32,12 +28,22 @@ applications in an expressive way.
+ [Alternate Names](#alternate-names)
+ [Ordering](#ordering)
+ [Values from the Environment](#values-from-the-environment)
+ [Values from files](#values-from-files)
+ [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others)
<<<<<<< HEAD
+ [Default Values for help output](#default-values-for-help-output)
* [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code)
* [Shell Completion](#shell-completion)
=======
+ [Precedence](#precedence)
* [Subcommands](#subcommands)
* [Subcommands categories](#subcommands-categories)
* [Exit code](#exit-code)
* [Combining short options](#combining-short-options)
* [Bash Completion](#bash-completion)
>>>>>>> master
+ [Enabling](#enabling)
+ [Distribution](#distribution)
+ [Customization](#customization)
@ -62,12 +68,12 @@ organized, and expressive!
## Installation
Make sure you have a working Go environment. Go version 1.2+ is supported. [See
Make sure you have a working Go environment. Go version 1.10+ is supported. [See
the install instructions for Go](http://golang.org/doc/install.html).
To install cli, simply run:
```
$ go get github.com/urfave/cli
$ go get github.com/urfave/cli/v2
```
Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can
@ -91,21 +97,21 @@ the new `master` branch once development there has settled down. The current
`master` branch (mirrored as `v1`) is being manually merged into `v2` on
an irregular human-based schedule, but generally if one wants to "upgrade" to
`v2` *now* and accept the volatility (read: "awesomeness") that comes along with
that, please use whatever version pinning of your preference, such as via
`gopkg.in`:
that, please use:
```
$ go get gopkg.in/urfave/cli.v2
$ go get github.com/urfave/cli/v2
```
``` go
...
import (
"gopkg.in/urfave/cli.v2" // imports as package "cli"
"github.com/urfave/cli/v2" // imports as package "cli"
)
...
```
<<<<<<< HEAD
**NOTE**: There is a [migrator (python) script](./cli-v1-to-v2) available to aid
with the transition from the v1 to v2 API.
@ -114,20 +120,22 @@ with the transition from the v1 to v2 API.
Similarly to the section above describing use of the `v2` branch, if one wants
to avoid any unexpected compatibility pains once `v2` becomes `master`, then
pinning to `v1` is an acceptable option, e.g.:
=======
### Using `v1` releases
>>>>>>> master
```
$ go get gopkg.in/urfave/cli.v1
$ go get github.com/urfave/cli
```
``` go
```go
...
import (
"gopkg.in/urfave/cli.v1" // imports as package "cli"
"github.com/urfave/cli/v2"
)
...
```
This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing).
## Getting Started
@ -142,9 +150,10 @@ discovery. So a cli app can be as little as one line of code in `main()`.
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -163,9 +172,10 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -178,7 +188,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -202,9 +215,10 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -217,7 +231,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -266,9 +283,10 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -279,7 +297,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -295,9 +316,10 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -323,7 +345,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -337,10 +362,11 @@ scanned.
package main
import (
"log"
"os"
"fmt"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -369,7 +395,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -390,9 +419,10 @@ For example this:
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -406,7 +436,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -432,9 +465,10 @@ list for the `Name`. e.g.
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -449,7 +483,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -473,10 +510,11 @@ For example this:
package main
import (
"log"
"os"
"sort"
"github.com/urfave/cli"
"github.com/urfave/cli/v2"
)
func main() {
@ -515,7 +553,10 @@ func main() {
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -538,9 +579,10 @@ You can also have the default value set from the environment via `EnvVars`. e.g
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -556,7 +598,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -571,9 +616,10 @@ resolves is used as the default.
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -589,10 +635,52 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
#### Values from files
You can also have the default value set from file via `FilePath`. e.g.
<!-- {
"args": ["&#45;&#45;help"],
"output": "password for the mysql database"
} -->
``` go
package main
import (
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := cli.NewApp()
app.Flags = []cli.Flag {
cli.StringFlag{
Name: "password, p",
Usage: "password for the mysql database",
FilePath: "/etc/mysql/password",
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
Note that default values set from file (e.g. `FilePath`) take precedence over
default values set from the environment (e.g. `EnvVar`).
#### Values from alternate input sources (YAML, TOML, and others)
There is a separate package altsrc that adds support for getting flag values
@ -600,6 +688,7 @@ from other file input sources.
Currently supported input source formats:
* YAML
* JSON
* TOML
In order to get values for a flag from an alternate input source the following
@ -622,9 +711,9 @@ the yaml input source for any flags that are defined on that command. As a note
the "load" flag used would also have to be defined on the command flags in order
for this code snipped to work.
Currently only the aboved specified formats are supported but developers can
add support for other input sources by implementing the
altsrc.InputSourceContext for their given sources.
Currently only YAML, JSON, and TOML files are supported but developers can add support
for other input sources by implementing the altsrc.InputSourceContext for their
given sources.
Here is a more complete sample of a command using YAML support:
@ -637,10 +726,11 @@ package notmain
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"gopkg.in/urfave/cli.v2/altsrc"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)
func main() {
@ -678,7 +768,7 @@ package main
import (
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -693,7 +783,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -703,6 +796,14 @@ Will result in help output like:
--port value Use a randomized port (default: random)
```
#### Precedence
The precedence for flag value sources is as follows (highest to lowest):
0. Command line flag value from user
0. Environment variable (if specified)
0. Configuration file (if specified)
0. Default defined on the flag
### Subcommands
@ -717,9 +818,10 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -769,7 +871,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -785,9 +890,10 @@ E.g.
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -807,7 +913,10 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -815,7 +924,7 @@ Will include:
```
COMMANDS:
noop
noop
Template actions:
add
@ -828,14 +937,17 @@ Calling `App.Run` will not automatically call `os.Exit`, which means that by
default the exit code will "fall through" to being `0`. An explicit exit code
may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a
`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.:
<!-- {
"error": "Ginger croutons are not in the soup"
} -->
``` go
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -855,13 +967,86 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
### Shell Completion
### Combining short options
You can enable completion commands by setting the `EnableShellCompletion`
Traditional use of options using their shortnames look like this:
```
$ cmd -s -o -m "Some message"
```
Suppose you want users to be able to combine options with their shortnames. This
can be done using the `UseShortOptionHandling` bool in your app configuration,
or for individual commands by attaching it to the command configuration. For
example:
<!-- {
"args": ["short", "&#45;som", "Some message"],
"output": "serve: true\noption: true\nmessage: Some message\n"
} -->
``` go
package main
import (
"fmt"
"log"
"os"
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{}
app.UseShortOptionHandling = true
app.Commands = []*cli.Command{
{
Name: "short",
Usage: "complete a task on the list",
Flags: []cli.Flag{
cli.BoolFlag{Name: "serve, s"},
cli.BoolFlag{Name: "option, o"},
cli.StringFlag{Name: "message, m"},
},
Action: func(c *cli.Context) error {
fmt.Println("serve:", c.Bool("serve"))
fmt.Println("option:", c.Bool("option"))
fmt.Println("message:", c.String("message"))
return nil
},
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
If your program has any number of bool flags such as `serve` and `option`, and
optionally one non-bool flag `message`, with the short options of `-s`, `-o`,
and `-m` respectively, setting `UseShortOptionHandling` will also support the
following syntax:
```
$ cmd -som "Some message"
```
If you enable `UseShortOptionHandling`, then you must not use any flags that
have a single leading `-` or this will result in failures. For example,
`-option` can no longer be used. Flags with two leading dashes (such as
`--options`) are still valid.
### Bash Completion
You can enable completion commands by setting the `EnableBashCompletion`
flag on the `App` object. By default, this setting will only auto-complete to
show an app's subcommands, but you can write your own completion methods for
the App or its subcommands.
@ -875,16 +1060,17 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"}
app := &cli.App{
EnableShellCompletion: true,
EnableBashCompletion: true,
Commands: []*cli.Command{
{
Name: "complete",
@ -894,7 +1080,7 @@ func main() {
fmt.Println("completed task: ", c.Args().First())
return nil
},
ShellComplete: func(c *cli.Context) {
BashComplete: func(c *cli.Context) {
// This will complete if no args are passed
if c.NArg() > 0 {
return
@ -907,23 +1093,19 @@ func main() {
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
#### Enabling
You can generate bash or zsh completion code by using the flag `--init-completion bash` or `--init-completion zsh`.
To setup for bash:
Source the `autocomplete/bash_autocomplete` file in your .bashrc file while setting the `PROG` variable to the name of your program:
```
eval "`myprogram --init-completion bash`"
```
Alternatively, you can put the completion code in your `.bashrc` file:
```
myprogram --init-completion bash >> ~/.bashrc
PROG=myprogram source /.../cli/autocomplete/bash_autocomplete
```
#### Distribution
@ -955,9 +1137,10 @@ The default shell completion flag (`--generate-completion`) is defined as
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -974,7 +1157,10 @@ func main() {
},
},
}
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
```
@ -1000,10 +1186,11 @@ package main
import (
"fmt"
"log"
"io"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -1042,7 +1229,7 @@ VERSION:
cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) {
fmt.Println("Ha HA. I pwnd the help!!1")
}
(&cli.App{}).Run(os.Args)
}
```
@ -1058,9 +1245,10 @@ setting `cli.HelpFlag`, e.g.:
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -1069,7 +1257,7 @@ func main() {
Usage: "HALP",
EnvVars: []string{"SHOW_HALP", "HALPPLZ"},
}
(&cli.App{}).Run(os.Args)
}
```
@ -1093,9 +1281,10 @@ setting `cli.VersionFlag`, e.g.:
package main
import (
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func main() {
@ -1103,7 +1292,7 @@ func main() {
Name: "print-version", Aliases: []string{"V"},
Usage: "print only the version",
}
app := &cli.App{
Name: "partay",
Version: "v19.99.0",
@ -1123,9 +1312,10 @@ package main
import (
"fmt"
"log"
"os"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
var (
@ -1136,7 +1326,7 @@ func main() {
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision)
}
app := &cli.App{
Name: "partay",
Version: "v19.99.0",
@ -1165,7 +1355,7 @@ import (
"os"
"time"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func init() {
@ -1217,7 +1407,7 @@ func (g *genericType) String() string {
}
func main() {
app := cli.App{
app := &cli.App{
Name: "kənˈtrīv",
Version: "v19.99.0",
Compiled: time.Now(),
@ -1406,7 +1596,7 @@ func main() {
app.Writer = &hexWriter{}
app.ErrWriter = &hexWriter{}
}
app.Run(os.Args)
}
@ -1418,16 +1608,4 @@ func wopAction(c *cli.Context) error {
## Contribution Guidelines
Feel free to put up a pull request to fix a bug or maybe add a feature. I will
give it a code review and make sure that it does not break backwards
compatibility. If I or any other collaborators agree that it is in line with
the vision of the project, we will work with you to get the code into
a mergeable state and merge it into the master branch.
If you have contributed something significant to the project, we will most
likely add you as a collaborator. As a collaborator you are given the ability
to merge others pull requests. It is very important that new code does not
break existing code, so be careful about what code you do choose to merge.
If you feel like you have contributed to the project but have not yet been
added as a collaborator, we probably forgot to add you, please open an issue.
See [./CONTRIBUTING.md](./CONTRIBUTING.md)

View File

@ -1,3 +0,0 @@
package altsrc
//go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go

View File

@ -6,7 +6,7 @@ import (
"strconv"
"syscall"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
// FlagInputSourceExtension is an extension interface of cli.Flag that
@ -72,7 +72,7 @@ func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourc
}
if value != nil {
for _, name := range f.Names() {
f.set.Set(name, value.String())
_ = f.set.Set(name, value.String())
}
}
}
@ -135,7 +135,7 @@ func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo
}
if value {
for _, name := range f.Names() {
f.set.Set(name, strconv.FormatBool(value))
_ = f.set.Set(name, strconv.FormatBool(value))
}
}
}
@ -153,7 +153,7 @@ func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSource
}
if value != "" {
for _, name := range f.Names() {
f.set.Set(name, value)
_ = f.set.Set(name, value)
}
}
}
@ -181,7 +181,7 @@ func (f *PathFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCo
value = filepath.Join(filepath.Dir(basePathAbs), value)
}
f.set.Set(name, value)
_ = f.set.Set(name, value)
}
}
}
@ -199,7 +199,7 @@ func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceCon
}
if value > 0 {
for _, name := range f.Names() {
f.set.Set(name, strconv.FormatInt(int64(value), 10))
_ = f.set.Set(name, strconv.FormatInt(int64(value), 10))
}
}
}
@ -217,7 +217,7 @@ func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSour
}
if value > 0 {
for _, name := range f.Names() {
f.set.Set(name, value.String())
_ = f.set.Set(name, value.String())
}
}
}
@ -236,7 +236,7 @@ func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourc
if value > 0 {
floatStr := float64ToString(value)
for _, name := range f.Names() {
f.set.Set(name, floatStr)
_ = f.set.Set(name, floatStr)
}
}
}

View File

@ -2,12 +2,9 @@ package altsrc
import (
"flag"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
// WARNING: This file is generated!
// BoolFlag is the flag type that wraps cli.BoolFlag to allow
// for other values to be specified
type BoolFlag struct {
@ -20,18 +17,11 @@ func NewBoolFlag(fl *cli.BoolFlag) *BoolFlag {
return &BoolFlag{BoolFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped BoolFlag.Apply
func (f *BoolFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped BoolFlag.Apply
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.BoolFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped BoolFlag.ApplyWithError
func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.BoolFlag.ApplyWithError(set)
return f.BoolFlag.Apply(set)
}
// DurationFlag is the flag type that wraps cli.DurationFlag to allow
@ -46,18 +36,11 @@ func NewDurationFlag(fl *cli.DurationFlag) *DurationFlag {
return &DurationFlag{DurationFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped DurationFlag.Apply
func (f *DurationFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped DurationFlag.Apply
func (f *DurationFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.DurationFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped DurationFlag.ApplyWithError
func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.DurationFlag.ApplyWithError(set)
return f.DurationFlag.Apply(set)
}
// Float64Flag is the flag type that wraps cli.Float64Flag to allow
@ -72,18 +55,11 @@ func NewFloat64Flag(fl *cli.Float64Flag) *Float64Flag {
return &Float64Flag{Float64Flag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped Float64Flag.Apply
func (f *Float64Flag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped Float64Flag.Apply
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
f.set = set
f.Float64Flag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Float64Flag.ApplyWithError
func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Float64Flag.ApplyWithError(set)
return f.Float64Flag.Apply(set)
}
// GenericFlag is the flag type that wraps cli.GenericFlag to allow
@ -98,18 +74,11 @@ func NewGenericFlag(fl *cli.GenericFlag) *GenericFlag {
return &GenericFlag{GenericFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped GenericFlag.Apply
func (f *GenericFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped GenericFlag.Apply
func (f *GenericFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.GenericFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped GenericFlag.ApplyWithError
func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.GenericFlag.ApplyWithError(set)
return f.GenericFlag.Apply(set)
}
// Int64Flag is the flag type that wraps cli.Int64Flag to allow
@ -124,18 +93,11 @@ func NewInt64Flag(fl *cli.Int64Flag) *Int64Flag {
return &Int64Flag{Int64Flag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped Int64Flag.Apply
func (f *Int64Flag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped Int64Flag.Apply
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
f.set = set
f.Int64Flag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Int64Flag.ApplyWithError
func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Int64Flag.ApplyWithError(set)
return f.Int64Flag.Apply(set)
}
// IntFlag is the flag type that wraps cli.IntFlag to allow
@ -150,18 +112,11 @@ func NewIntFlag(fl *cli.IntFlag) *IntFlag {
return &IntFlag{IntFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped IntFlag.Apply
func (f *IntFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped IntFlag.Apply
func (f *IntFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.IntFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped IntFlag.ApplyWithError
func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.IntFlag.ApplyWithError(set)
return f.IntFlag.Apply(set)
}
// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow
@ -176,18 +131,11 @@ func NewIntSliceFlag(fl *cli.IntSliceFlag) *IntSliceFlag {
return &IntSliceFlag{IntSliceFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped IntSliceFlag.Apply
func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped IntSliceFlag.Apply
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.IntSliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped IntSliceFlag.ApplyWithError
func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.IntSliceFlag.ApplyWithError(set)
return f.IntSliceFlag.Apply(set)
}
// Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow
@ -202,18 +150,11 @@ func NewInt64SliceFlag(fl *cli.Int64SliceFlag) *Int64SliceFlag {
return &Int64SliceFlag{Int64SliceFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped Int64SliceFlag.Apply
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped Int64SliceFlag.Apply
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.Int64SliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Int64SliceFlag.ApplyWithError
func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Int64SliceFlag.ApplyWithError(set)
return f.Int64SliceFlag.Apply(set)
}
// Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow
@ -230,16 +171,9 @@ func NewFloat64SliceFlag(fl *cli.Float64SliceFlag) *Float64SliceFlag {
// Apply saves the flagSet for later usage calls, then calls the
// wrapped Float64SliceFlag.Apply
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) {
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.Float64SliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Float64SliceFlag.ApplyWithError
func (f *Float64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Float64SliceFlag.ApplyWithError(set)
return f.Float64SliceFlag.Apply(set)
}
// StringFlag is the flag type that wraps cli.StringFlag to allow
@ -254,18 +188,11 @@ func NewStringFlag(fl *cli.StringFlag) *StringFlag {
return &StringFlag{StringFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped StringFlag.Apply
func (f *StringFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped StringFlag.Apply
func (f *StringFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.StringFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped StringFlag.ApplyWithError
func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.StringFlag.ApplyWithError(set)
return f.StringFlag.Apply(set)
}
// PathFlag is the flag type that wraps cli.PathFlag to allow
@ -282,16 +209,9 @@ func NewPathFlag(fl *cli.PathFlag) *PathFlag {
// Apply saves the flagSet for later usage calls, then calls the
// wrapped PathFlag.Apply
func (f *PathFlag) Apply(set *flag.FlagSet) {
func (f *PathFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.PathFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped PathFlag.ApplyWithError
func (f *PathFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.PathFlag.ApplyWithError(set)
return f.PathFlag.Apply(set)
}
// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow
@ -306,18 +226,11 @@ func NewStringSliceFlag(fl *cli.StringSliceFlag) *StringSliceFlag {
return &StringSliceFlag{StringSliceFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped StringSliceFlag.Apply
func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped StringSliceFlag.Apply
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.StringSliceFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped StringSliceFlag.ApplyWithError
func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.StringSliceFlag.ApplyWithError(set)
return f.StringSliceFlag.Apply(set)
}
// Uint64Flag is the flag type that wraps cli.Uint64Flag to allow
@ -332,18 +245,11 @@ func NewUint64Flag(fl *cli.Uint64Flag) *Uint64Flag {
return &Uint64Flag{Uint64Flag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped Uint64Flag.Apply
func (f *Uint64Flag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped Uint64Flag.Apply
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
f.set = set
f.Uint64Flag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped Uint64Flag.ApplyWithError
func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.Uint64Flag.ApplyWithError(set)
return f.Uint64Flag.Apply(set)
}
// UintFlag is the flag type that wraps cli.UintFlag to allow
@ -358,16 +264,9 @@ func NewUintFlag(fl *cli.UintFlag) *UintFlag {
return &UintFlag{UintFlag: fl, set: nil}
}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped UintFlag.Apply
func (f *UintFlag) Apply(set *flag.FlagSet) {
// Apply saves the flagSet for later usage calls, then calls
// the wrapped UintFlag.Apply
func (f *UintFlag) Apply(set *flag.FlagSet) error {
f.set = set
f.UintFlag.Apply(set)
}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped UintFlag.ApplyWithError
func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
f.set = set
return f.UintFlag.ApplyWithError(set)
return f.UintFlag.Apply(set)
}

View File

@ -3,13 +3,12 @@ package altsrc
import (
"flag"
"fmt"
"github.com/urfave/cli/v2"
"os"
"runtime"
"strings"
"testing"
"time"
"gopkg.in/urfave/cli.v2"
)
type testApplyInputSource struct {
@ -252,30 +251,30 @@ func TestDurationApplyInputSourceMethodSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
FlagName: "test",
MapValue: time.Duration(30 * time.Second),
MapValue: 30 * time.Second,
})
expect(t, time.Duration(30*time.Second), c.Duration("test"))
expect(t, 30*time.Second, c.Duration("test"))
}
func TestDurationApplyInputSourceMethodContextSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test"}),
FlagName: "test",
MapValue: time.Duration(30 * time.Second),
ContextValueString: time.Duration(15 * time.Second).String(),
MapValue: 30 * time.Second,
ContextValueString: (15 * time.Second).String(),
})
expect(t, time.Duration(15*time.Second), c.Duration("test"))
expect(t, 15*time.Second, c.Duration("test"))
}
func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) {
c := runTest(t, testApplyInputSource{
Flag: NewDurationFlag(&cli.DurationFlag{Name: "test", EnvVars: []string{"TEST"}}),
FlagName: "test",
MapValue: time.Duration(30 * time.Second),
MapValue: 30 * time.Second,
EnvVarName: "TEST",
EnvVarValue: time.Duration(15 * time.Second).String(),
EnvVarValue: (15 * time.Second).String(),
})
expect(t, time.Duration(15*time.Second), c.Duration("test"))
expect(t, 15*time.Second, c.Duration("test"))
}
func TestFloat64ApplyInputSourceMethodSet(t *testing.T) {
@ -316,19 +315,19 @@ func runTest(t *testing.T, test testApplyInputSource) *cli.Context {
set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError)
c := cli.NewContext(nil, set, nil)
if test.EnvVarName != "" && test.EnvVarValue != "" {
os.Setenv(test.EnvVarName, test.EnvVarValue)
_ = os.Setenv(test.EnvVarName, test.EnvVarValue)
defer os.Setenv(test.EnvVarName, "")
}
test.Flag.Apply(set)
if test.ContextValue != nil {
flag := set.Lookup(test.FlagName)
flag.Value = test.ContextValue
f := set.Lookup(test.FlagName)
f.Value = test.ContextValue
}
if test.ContextValueString != "" {
set.Set(test.FlagName, test.ContextValueString)
_ = set.Set(test.FlagName, test.ContextValueString)
}
test.Flag.ApplyInputSourceValue(c, inputSource)
_ = test.Flag.ApplyInputSourceValue(c, inputSource)
return c
}

View File

@ -1,9 +1,8 @@
package altsrc
import (
"github.com/urfave/cli/v2"
"time"
"gopkg.in/urfave/cli.v2"
)
// InputSourceContext is an interface used to allow

View File

@ -2,11 +2,10 @@ package altsrc
import (
"flag"
"github.com/urfave/cli/v2"
"io/ioutil"
"os"
"testing"
"gopkg.in/urfave/cli.v2"
)
const (
@ -22,7 +21,7 @@ func TestCommandJSONFileTest(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -52,11 +51,11 @@ func TestCommandJSONFileTestGlobalEnvVarWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
os.Setenv("THE_TEST", "10")
_ = os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -87,11 +86,11 @@ func TestCommandJSONFileTestGlobalEnvVarWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
os.Setenv("THE_TEST", "10")
_ = os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -123,7 +122,7 @@ func TestCommandJSONFileTestSpecifiedFlagWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
test := []string{"test-cmd", "--load", fileName, "--test", "7"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -155,7 +154,7 @@ func TestCommandJSONFileTestSpecifiedFlagWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
test := []string{"test-cmd", "--load", fileName, "--top.test", "7"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -187,7 +186,7 @@ func TestCommandJSONFileTestDefaultValueFileWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -219,7 +218,7 @@ func TestCommandJSONFileTestDefaultValueFileWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -250,11 +249,11 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWins(t *testing.T
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
os.Setenv("THE_TEST", "11")
_ = os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -284,11 +283,11 @@ func TestCommandJSONFileFlagHasDefaultGlobalEnvJSONSetGlobalEnvWinsNested(t *tes
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
os.Setenv("THE_TEST", "11")
_ = os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", fileName}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)

View File

@ -3,12 +3,11 @@ package altsrc
import (
"encoding/json"
"fmt"
"github.com/urfave/cli/v2"
"io"
"io/ioutil"
"strings"
"time"
"gopkg.in/urfave/cli.v2"
)
// NewJSONSourceFromFlagFunc returns a func that takes a cli.Context
@ -29,13 +28,8 @@ func NewJSONSourceFromFile(f string) (InputSourceContext, error) {
if err != nil {
return nil, err
}
s, err := newJSONSource(data)
if err != nil {
return nil, err
}
s.file = f
return s, nil
return NewJSONSource(data)
}
// NewJSONSourceFromReader returns an InputSourceContext suitable for
@ -51,10 +45,6 @@ func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) {
// NewJSONSource returns an InputSourceContext suitable for retrieving
// config variables from raw JSON data.
func NewJSONSource(data []byte) (InputSourceContext, error) {
return newJSONSource(data)
}
func newJSONSource(data []byte) (*jsonSource, error) {
var deserialized map[string]interface{}
if err := json.Unmarshal(data, &deserialized); err != nil {
return nil, err
@ -77,9 +67,9 @@ func (x *jsonSource) Int(name string) (int, error) {
case int:
return v, nil
case float64:
return int(float64(v)), nil
return int(v), nil
case float32:
return int(float32(v)), nil
return int(v), nil
}
}

View File

@ -2,11 +2,10 @@ package altsrc
import (
"fmt"
"github.com/urfave/cli/v2"
"reflect"
"strings"
"time"
"gopkg.in/urfave/cli.v2"
)
// MapInputSource implements InputSourceContext to return
@ -23,15 +22,15 @@ func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool
if sections := strings.Split(name, "."); len(sections) > 1 {
node := tree
for _, section := range sections[:len(sections)-1] {
if child, ok := node[section]; !ok {
child, ok := node[section]
if !ok {
return nil, false
} else {
if ctype, ok := child.(map[interface{}]interface{}); !ok {
return nil, false
} else {
node = ctype
}
}
ctype, ok := child.(map[interface{}]interface{})
if !ok {
return nil, false
}
node = ctype
}
if val, ok := node[sections[len(sections)-1]]; ok {
return val, true

View File

@ -7,20 +7,19 @@ package altsrc
import (
"flag"
"github.com/urfave/cli/v2"
"io/ioutil"
"os"
"testing"
"gopkg.in/urfave/cli.v2"
)
func TestCommandTomFileTest(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -45,15 +44,15 @@ func TestCommandTomFileTest(t *testing.T) {
}
func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "10")
_ = os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -79,15 +78,15 @@ func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) {
}
func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "10")
_ = os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -113,13 +112,13 @@ func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) {
}
func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml", "--test", "7"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -145,14 +144,14 @@ func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) {
}
func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte(`[top]
_ = ioutil.WriteFile("current.toml", []byte(`[top]
test = 15`), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -178,13 +177,13 @@ func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) {
}
func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -210,13 +209,13 @@ func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) {
}
func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
defer os.Remove("current.toml")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -244,14 +243,14 @@ func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) {
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) {
app := (&cli.App{})
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("test = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "11")
_ = os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -276,16 +275,16 @@ func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T
}
func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) {
app := (&cli.App{})
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
_ = ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666)
defer os.Remove("current.toml")
os.Setenv("THE_TEST", "11")
_ = os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.toml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)

View File

@ -10,7 +10,7 @@ import (
"reflect"
"github.com/BurntSushi/toml"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
type tomlMap struct {
@ -28,7 +28,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
case reflect.String:
ret[key] = val.(string)
case reflect.Int:
ret[key] = int(val.(int))
ret[key] = val.(int)
case reflect.Int8:
ret[key] = int(val.(int8))
case reflect.Int16:
@ -50,7 +50,7 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
case reflect.Float32:
ret[key] = float64(val.(float32))
case reflect.Float64:
ret[key] = float64(val.(float64))
ret[key] = val.(float64)
case reflect.Map:
if tmp, err := unmarshalMap(val); err == nil {
ret[key] = tmp
@ -66,9 +66,9 @@ func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) {
return ret, nil
}
func (self *tomlMap) UnmarshalTOML(i interface{}) error {
func (tm *tomlMap) UnmarshalTOML(i interface{}) error {
if tmp, err := unmarshalMap(i); err == nil {
self.Map = tmp
tm.Map = tmp
} else {
return err
}

View File

@ -10,17 +10,16 @@ import (
"io/ioutil"
"os"
"testing"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
)
func TestCommandYamlFileTest(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
_ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
defer os.Remove("current.yaml")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -47,13 +46,13 @@ func TestCommandYamlFileTest(t *testing.T) {
func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
_ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
defer os.Remove("current.yaml")
os.Setenv("THE_TEST", "10")
_ = os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -81,14 +80,14 @@ func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) {
func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte(`top:
_ = ioutil.WriteFile("current.yaml", []byte(`top:
test: 15`), 0666)
defer os.Remove("current.yaml")
os.Setenv("THE_TEST", "10")
_ = os.Setenv("THE_TEST", "10")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -116,11 +115,11 @@ func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) {
func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
_ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
defer os.Remove("current.yaml")
test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -148,12 +147,12 @@ func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) {
func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte(`top:
_ = ioutil.WriteFile("current.yaml", []byte(`top:
test: 15`), 0666)
defer os.Remove("current.yaml")
test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -181,11 +180,11 @@ func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) {
func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
_ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
defer os.Remove("current.yaml")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -213,12 +212,12 @@ func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) {
func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte(`top:
_ = ioutil.WriteFile("current.yaml", []byte(`top:
test: 15`), 0666)
defer os.Remove("current.yaml")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -246,14 +245,14 @@ func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) {
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
_ = ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666)
defer os.Remove("current.yaml")
os.Setenv("THE_TEST", "11")
_ = os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)
@ -280,15 +279,15 @@ func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T
func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) {
app := &cli.App{}
set := flag.NewFlagSet("test", 0)
ioutil.WriteFile("current.yaml", []byte(`top:
_ = ioutil.WriteFile("current.yaml", []byte(`top:
test: 15`), 0666)
defer os.Remove("current.yaml")
os.Setenv("THE_TEST", "11")
_ = os.Setenv("THE_TEST", "11")
defer os.Setenv("THE_TEST", "")
test := []string{"test-cmd", "--load", "current.yaml"}
set.Parse(test)
_ = set.Parse(test)
c := cli.NewContext(app, set, nil)

View File

@ -14,7 +14,7 @@ import (
"runtime"
"strings"
"gopkg.in/urfave/cli.v2"
"github.com/urfave/cli/v2"
"gopkg.in/yaml.v2"
)
@ -86,7 +86,7 @@ func loadDataFrom(filePath string) ([]byte, error) {
return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath)
}
return ioutil.ReadFile(filePath)
} else {
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
}
return nil, fmt.Errorf("unable to determine how to load from path %s", filePath)
}

195
app.go
View File

@ -1,9 +1,9 @@
package cli
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@ -11,7 +11,21 @@ import (
"time"
)
// App is the main structure of a cli application.
var (
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md"
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL)
// unused variable. commented for now. will remove in future if agreed upon by everyone
//runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL)
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you."
errInvalidActionType = NewExitError("ERROR invalid Action type. "+
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+
fmt.Sprintf("See %s", appActionDeprecationURL), 2)
)
// App is the main structure of a cli application. It is recommended that
// an app be created with the cli.NewApp() function
type App struct {
// The name of the program. Defaults to path.Base(os.Args[0])
Name string
@ -31,8 +45,8 @@ type App struct {
Commands []*Command
// List of flags to parse
Flags []Flag
// Boolean to enable shell completion commands
EnableShellCompletion bool
// Boolean to enable bash completion commands
EnableBashCompletion bool
// Boolean to hide built-in help command
HideHelp bool
// Boolean to hide built-in version flag and the VERSION section of help
@ -40,7 +54,7 @@ type App struct {
// Categories contains the categorized commands and is populated on app startup
Categories CommandCategories
// An action to execute when the shell completion flag is set
ShellComplete ShellCompleteFunc
BashComplete BashCompleteFunc
// An action to execute before any subcommands are run, but after the context is ready
// If a non-nil error is returned, no subcommands are run
Before BeforeFunc
@ -63,6 +77,9 @@ type App struct {
Writer io.Writer
// ErrWriter writes error output
ErrWriter io.Writer
// Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to
// function as a default, so this is optional.
ExitErrHandler ExitErrHandlerFunc
// Other custom info
Metadata map[string]interface{}
// Carries a function which returns app specific info.
@ -71,6 +88,10 @@ type App struct {
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
CustomAppHelpTemplate string
// Boolean to enable short-option handling so user can combine several
// single-character bool arguements into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
didSetup bool
}
@ -85,6 +106,22 @@ func compileTime() time.Time {
return info.ModTime()
}
// NewApp creates a new cli Application with some reasonable defaults for Name,
// Usage, Version and Action.
func NewApp() *App {
return &App{
Name: filepath.Base(os.Args[0]),
HelpName: filepath.Base(os.Args[0]),
Usage: "A new cli application",
UsageText: "",
Version: "0.0.0",
BashComplete: DefaultAppComplete,
Action: helpCommand.Action,
Compiled: compileTime(),
Writer: os.Stdout,
}
}
// Setup runs initialization code to ensure all data structures are ready for
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
// will return early if setup has already happened.
@ -111,8 +148,8 @@ func (a *App) Setup() {
a.Version = "0.0.0"
}
if a.ShellComplete == nil {
a.ShellComplete = DefaultAppComplete
if a.BashComplete == nil {
a.BashComplete = DefaultAppComplete
}
if a.Action == nil {
@ -127,14 +164,15 @@ func (a *App) Setup() {
a.Writer = os.Stdout
}
newCmds := []*Command{}
var newCommands []*Command
for _, c := range a.Commands {
if c.HelpName == "" {
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name)
}
newCmds = append(newCmds, c)
newCommands = append(newCommands, c)
}
a.Commands = newCmds
a.Commands = newCommands
if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.appendCommand(helpCommand)
@ -144,10 +182,10 @@ func (a *App) Setup() {
}
}
if a.EnableShellCompletion {
a.appendFlag(GenerateCompletionFlag)
a.appendFlag(InitCompletionFlag)
}
//if a.EnableShellCompletion {
// a.appendFlag(GenerateCompletionFlag)
// a.appendFlag(InitCompletionFlag)
//}
if !a.HideVersion {
a.appendFlag(VersionFlag)
@ -168,6 +206,14 @@ func (a *App) Setup() {
}
}
func (a *App) newFlagSet() (*flag.FlagSet, error) {
return flagSet(a.Name, a.Flags)
}
func (a *App) useShortOptionHandling() bool {
return a.UseShortOptionHandling
}
// Run is the entry point to the cli app. Parses the arguments slice and routes
// to the proper flag/args combination
func (a *App) Run(arguments []string) (err error) {
@ -181,19 +227,17 @@ func (a *App) Run(arguments []string) (err error) {
// always appends the completion flag at the end of the command
shellComplete, arguments := checkShellCompleteFlag(a, arguments)
// parse flags
set, err := flagSet(a.Name, a.Flags)
_, err = a.newFlagSet()
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(arguments[1:])
set, err := parseIter(a, arguments[1:])
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, nil)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
ShowAppHelp(context)
_, _ = fmt.Fprintln(a.Writer, nerr)
_ = ShowAppHelp(context)
return nerr
}
context.shellComplete = shellComplete
@ -212,17 +256,22 @@ func (a *App) Run(arguments []string) (err error) {
if err != nil {
if a.OnUsageError != nil {
//<<<<<<< HEAD
err = a.OnUsageError(context, err, false)
HandleExitCoder(err)
//=======
// err := a.OnUsageError(context, err, false)
// a.handleExitCoder(context, err)
//>>>>>>> master
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowAppHelp(context)
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
_ = ShowAppHelp(context)
return err
}
if !a.HideHelp && checkHelp(context) {
ShowAppHelp(context)
_ = ShowAppHelp(context)
return nil
}
@ -231,6 +280,12 @@ func (a *App) Run(arguments []string) (err error) {
return nil
}
cerr := checkRequiredFlags(a.Flags, context)
if cerr != nil {
_ = ShowAppHelp(context)
return cerr
}
if a.After != nil {
defer func() {
if afterErr := a.After(context); afterErr != nil {
@ -246,8 +301,9 @@ func (a *App) Run(arguments []string) (err error) {
if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
ShowAppHelp(context)
HandleExitCoder(beforeErr)
_, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr)
_ = ShowAppHelp(context)
a.handleExitCoder(context, beforeErr)
err = beforeErr
return err
}
@ -269,10 +325,22 @@ func (a *App) Run(arguments []string) (err error) {
// Run default Action
err = a.Action(context)
HandleExitCoder(err)
a.handleExitCoder(context, err)
return err
}
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
//
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
// to cli.App.Run. This will cause the application to exit with the given eror
// code in the cli.ExitCoder
func (a *App) RunAndExitOnError() {
if err := a.Run(os.Args); err != nil {
_, _ = fmt.Fprintln(a.errWriter(), err)
OsExiter(1)
}
}
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
// generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) (err error) {
@ -298,29 +366,32 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
a.Commands = newCmds
// append flags
if a.EnableShellCompletion {
a.appendFlag(GenerateCompletionFlag)
}
// parse flags
set, err := flagSet(a.Name, a.Flags)
//<<<<<<< HEAD
// // append flags
// if a.EnableShellCompletion {
// a.appendFlag(GenerateCompletionFlag)
// }
//
// // parse flags
// set, err := flagSet(a.Name, a.Flags)
//=======
_, err = a.newFlagSet()
//>>>>>>> master
if err != nil {
return err
}
set.SetOutput(ioutil.Discard)
err = set.Parse(ctx.Args().Tail())
set, err := parseIter(a, ctx.Args().Tail())
nerr := normalizeFlags(a.Flags, set)
context := NewContext(a, set, ctx)
if nerr != nil {
fmt.Fprintln(a.Writer, nerr)
fmt.Fprintln(a.Writer)
_, _ = fmt.Fprintln(a.Writer, nerr)
_, _ = fmt.Fprintln(a.Writer)
if len(a.Commands) > 0 {
ShowSubcommandHelp(context)
_ = ShowSubcommandHelp(context)
} else {
ShowCommandHelp(ctx, context.Args().First())
_ = ShowCommandHelp(ctx, context.Args().First())
}
return nerr
}
@ -332,11 +403,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
if err != nil {
if a.OnUsageError != nil {
err = a.OnUsageError(context, err, true)
HandleExitCoder(err)
a.handleExitCoder(context, err)
return err
}
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
ShowSubcommandHelp(context)
_, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
_ = ShowSubcommandHelp(context)
return err
}
@ -350,11 +421,17 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
}
}
cerr := checkRequiredFlags(a.Flags, context)
if cerr != nil {
_ = ShowSubcommandHelp(context)
return cerr
}
if a.After != nil {
defer func() {
afterErr := a.After(context)
if afterErr != nil {
HandleExitCoder(err)
a.handleExitCoder(context, err)
if err != nil {
err = newMultiError(err, afterErr)
} else {
@ -367,7 +444,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
if a.Before != nil {
beforeErr := a.Before(context)
if beforeErr != nil {
HandleExitCoder(beforeErr)
a.handleExitCoder(context, beforeErr)
err = beforeErr
return err
}
@ -385,7 +462,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) {
// Run default Action
err = a.Action(context)
HandleExitCoder(err)
a.handleExitCoder(context, err)
return err
}
@ -419,7 +496,7 @@ func (a *App) VisibleCategories() []CommandCategory {
// VisibleCommands returns a slice of the Commands with Hidden=false
func (a *App) VisibleCommands() []*Command {
ret := []*Command{}
var ret []*Command
for _, command := range a.Commands {
if !command.Hidden {
ret = append(ret, command)
@ -444,7 +521,6 @@ func (a *App) hasFlag(flag Flag) bool {
}
func (a *App) errWriter() io.Writer {
// When the app ErrWriter is nil use the package level one.
if a.ErrWriter == nil {
return ErrWriter
@ -465,6 +541,14 @@ func (a *App) appendCommand(c *Command) {
}
}
func (a *App) handleExitCoder(context *Context, err error) {
if a.ExitErrHandler != nil {
a.ExitErrHandler(context, err)
} else {
HandleExitCoder(err)
}
}
// Author represents someone who has contributed to a cli project.
type Author struct {
Name string // The Authors name
@ -488,3 +572,20 @@ func DefaultCommand(name string) ActionFunc {
return ctx.App.Command(name).Run(ctx)
}
}
// HandleAction attempts to figure out which Action signature was used. If
// it's an ActionFunc or a func with the legacy signature for Action, the func
// is run!
func HandleAction(action interface{}, context *Context) (err error) {
switch a := action.(type) {
case ActionFunc:
return a(context)
case func(*Context) error:
return a(context)
case func(*Context): // deprecated function signature
a(context)
return nil
}
return errInvalidActionType
}

View File

@ -86,7 +86,7 @@ func ExampleApp_Run_subcommand() {
},
}
app.Run(os.Args)
_ = app.Run(os.Args)
// Output:
// Hello, Jeremy
}
@ -119,7 +119,7 @@ func ExampleApp_Run_appHelp() {
},
},
}
app.Run(os.Args)
_ = app.Run(os.Args)
// Output:
// NAME:
// greet - A new cli application
@ -138,8 +138,8 @@ func ExampleApp_Run_appHelp() {
// Oliver Allen <oliver@toyshop.com>
//
// COMMANDS:
// describeit, d use it to see a description
// help, h Shows a list of commands or help for one command
// describeit, d use it to see a description
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --name value a name to say (default: "bob")
@ -169,7 +169,7 @@ func ExampleApp_Run_commandHelp() {
},
},
}
app.Run(os.Args)
_ = app.Run(os.Args)
// Output:
// NAME:
// greet describeit - use it to see a description
@ -184,7 +184,7 @@ func ExampleApp_Run_commandHelp() {
func ExampleApp_Run_noAction() {
app := App{}
app.Name = "greet"
app.Run([]string{"greet"})
_ = app.Run([]string{"greet"})
// Output:
// NAME:
// greet - A new cli application
@ -196,7 +196,7 @@ func ExampleApp_Run_noAction() {
// 0.0.0
//
// COMMANDS:
// help, h Shows a list of commands or help for one command
// help, h Shows a list of commands or help for one command
//
// GLOBAL OPTIONS:
// --help, -h show help (default: false)
@ -215,7 +215,7 @@ func ExampleApp_Run_subcommandNoAction() {
},
},
}
app.Run([]string{"greet", "describeit"})
_ = app.Run([]string{"greet", "describeit"})
// Output:
// NAME:
// greet describeit - use it to see a description
@ -231,13 +231,103 @@ func ExampleApp_Run_subcommandNoAction() {
}
func ExampleApp_Run_shellComplete() {
func ExampleApp_Run_bashComplete_withShortFlag() {
os.Args = []string{"greet", "-", "--generate-bash-completion"}
app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Flags = []Flag{
&IntFlag{
Name: "other",
Aliases: []string{"o"},
},
&StringFlag{
Name: "xyz",
Aliases: []string{"x"},
},
}
_ = app.Run(os.Args)
// Output:
// --other
// -o
// --xyz
// -x
// --help
// -h
// --version
// -v
}
func ExampleApp_Run_bashComplete_withLongFlag() {
os.Args = []string{"greet", "--s", "--generate-bash-completion"}
app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Flags = []Flag{
&IntFlag{
Name: "other",
Aliases: []string{"o"},
},
&StringFlag{
Name: "xyz",
Aliases: []string{"x"},
},
&StringFlag{
Name: "some-flag,s",
},
&StringFlag{
Name: "similar-flag",
},
}
_ = app.Run(os.Args)
// Output:
// --some-flag
// --similar-flag
}
func ExampleApp_Run_bashComplete_withMultipleLongFlag() {
os.Args = []string{"greet", "--st", "--generate-bash-completion"}
app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Flags = []Flag{
&IntFlag{
Name: "int-flag",
Aliases: []string{"i"},
},
&StringFlag{
Name: "string",
Aliases: []string{"s"},
},
&StringFlag{
Name: "string-flag-2",
},
&StringFlag{
Name: "similar-flag",
},
&StringFlag{
Name: "some-flag",
},
}
_ = app.Run(os.Args)
// Output:
// --string
// --string-flag-2
}
func ExampleApp_Run_bashComplete() {
// set args for examples sake
os.Args = []string{"greet", fmt.Sprintf("--%s", genCompName())}
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
app := &App{
Name: "greet",
EnableShellCompletion: true,
EnableBashCompletion: true,
Commands: []*Command{
{
Name: "describeit",
@ -260,7 +350,7 @@ func ExampleApp_Run_shellComplete() {
},
}
app.Run(os.Args)
_ = app.Run(os.Args)
// Output:
// describeit
// d
@ -269,6 +359,44 @@ func ExampleApp_Run_shellComplete() {
// h
}
func ExampleApp_Run_zshComplete() {
// set args for examples sake
os.Args = []string{"greet", "--generate-bash-completion"}
_ = os.Setenv("_CLI_ZSH_AUTOCOMPLETE_HACK", "1")
app := NewApp()
app.Name = "greet"
app.EnableBashCompletion = true
app.Commands = []*Command{
{
Name: "describeit",
Aliases: []string{"d"},
Usage: "use it to see a description",
Description: "This is how we describe describeit the function",
Action: func(c *Context) error {
fmt.Printf("i like to describe things")
return nil
},
}, {
Name: "next",
Usage: "next example",
Description: "more stuff to see when generating bash completion",
Action: func(c *Context) error {
fmt.Printf("the next example")
return nil
},
},
}
_ = app.Run(os.Args)
// Output:
// describeit:use it to see a description
// d:use it to see a description
// next:next example
// help:Shows a list of commands or help for one command
// h:Shows a list of commands or help for one command
}
func TestApp_Run(t *testing.T) {
s := ""
@ -317,6 +445,63 @@ func TestApp_Setup_defaultsWriter(t *testing.T) {
expect(t, app.Writer, os.Stdout)
}
func TestApp_CommandWithArgBeforeFlags(t *testing.T) {
var parsedOption, firstArg string
app := NewApp()
command := &Command{
Name: "cmd",
Flags: []Flag{
&StringFlag{Name: "option", Value: "", Usage: "some option"},
},
Action: func(c *Context) error {
parsedOption = c.String("option")
firstArg = c.Args().First()
return nil
},
}
app.Commands = []*Command{command}
_ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"})
expect(t, parsedOption, "my-option")
expect(t, firstArg, "my-arg")
}
func TestApp_CommandWithArgBeforeBoolFlags(t *testing.T) {
var parsedOption, parsedSecondOption, firstArg string
var parsedBool, parsedSecondBool bool
app := NewApp()
command := &Command{
Name: "cmd",
Flags: []Flag{
&StringFlag{Name: "option", Value: "", Usage: "some option"},
&StringFlag{Name: "secondOption", Value: "", Usage: "another option"},
&BoolFlag{Name: "boolflag", Usage: "some bool"},
&BoolFlag{Name: "b", Usage: "another bool"},
},
Action: func(c *Context) error {
parsedOption = c.String("option")
parsedSecondOption = c.String("secondOption")
parsedBool = c.Bool("boolflag")
parsedSecondBool = c.Bool("b")
firstArg = c.Args().First()
return nil
},
}
app.Commands = []*Command{command}
_ = app.Run([]string{"", "cmd", "my-arg", "--boolflag", "--option", "my-option", "-b", "--secondOption", "fancy-option"})
expect(t, parsedOption, "my-option")
expect(t, parsedSecondOption, "fancy-option")
expect(t, parsedBool, true)
expect(t, parsedSecondBool, true)
expect(t, firstArg, "my-arg")
}
func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
var context *Context
@ -339,7 +524,7 @@ func TestApp_RunAsSubcommandParseFlags(t *testing.T) {
},
},
}
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
_ = a.Run([]string{"", "foo", "--lang", "spanish", "abcd"})
expect(t, context.Args().Get(0), "abcd")
expect(t, context.String("lang"), "spanish")
@ -355,7 +540,7 @@ func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) {
}
set := flag.NewFlagSet("", flag.ContinueOnError)
set.Parse([]string{"", "---foo"})
_ = set.Parse([]string{"", "---foo"})
c := &Context{flagSet: set}
err := a.RunAsSubcommand(c)
@ -383,7 +568,7 @@ func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) {
},
}
app.Run([]string{"", "cmd", "--option", "my-option", "my-arg", "--", "--notARealFlag"})
_ = app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"})
expect(t, parsedOption, "my-option")
expect(t, args.Get(0), "my-arg")
@ -406,7 +591,7 @@ func TestApp_CommandWithDash(t *testing.T) {
},
}
app.Run([]string{"", "cmd", "my-arg", "-"})
_ = app.Run([]string{"", "cmd", "my-arg", "-"})
expect(t, args.Get(0), "my-arg")
expect(t, args.Get(1), "-")
@ -427,7 +612,7 @@ func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) {
},
}
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
_ = app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"})
expect(t, args.Get(0), "my-arg")
expect(t, args.Get(1), "--")
@ -485,6 +670,93 @@ func TestApp_VisibleCommands(t *testing.T) {
}
}
func TestApp_UseShortOptionHandling(t *testing.T) {
var one, two bool
var name string
expected := "expectedName"
app := NewApp()
app.UseShortOptionHandling = true
app.Flags = []Flag{
&BoolFlag{Name: "one", Aliases: []string{"o"}},
&BoolFlag{Name: "two", Aliases: []string{"t"}},
&StringFlag{Name: "name", Aliases: []string{"n"}},
}
app.Action = func(c *Context) error {
one = c.Bool("one")
two = c.Bool("two")
name = c.String("name")
return nil
}
_ = app.Run([]string{"", "-on", expected})
expect(t, one, true)
expect(t, two, false)
expect(t, name, expected)
}
func TestApp_UseShortOptionHandlingCommand(t *testing.T) {
var one, two bool
var name string
expected := "expectedName"
app := NewApp()
app.UseShortOptionHandling = true
command := &Command{
Name: "cmd",
Flags: []Flag{
&BoolFlag{Name: "one", Aliases: []string{"o"}},
&BoolFlag{Name: "two", Aliases: []string{"t"}},
&StringFlag{Name: "name", Aliases: []string{"n"}},
},
Action: func(c *Context) error {
one = c.Bool("one")
two = c.Bool("two")
name = c.String("name")
return nil
},
}
app.Commands = []*Command{command}
_ = app.Run([]string{"", "cmd", "-on", expected})
expect(t, one, true)
expect(t, two, false)
expect(t, name, expected)
}
func TestApp_UseShortOptionHandlingSubCommand(t *testing.T) {
var one, two bool
var name string
expected := "expectedName"
app := NewApp()
app.UseShortOptionHandling = true
command := &Command{
Name: "cmd",
}
subCommand := &Command{
Name: "sub",
Flags: []Flag{
&BoolFlag{Name: "one", Aliases: []string{"o"}},
&BoolFlag{Name: "two", Aliases: []string{"t"}},
&StringFlag{Name: "name", Aliases: []string{"n"}},
},
Action: func(c *Context) error {
one = c.Bool("one")
two = c.Bool("two")
name = c.String("name")
return nil
},
}
app.Commands = []*Command{command, subCommand}
err := app.Run([]string{"", "cmd", "sub", "-on", expected})
expect(t, err, nil)
expect(t, one, true)
expect(t, two, false)
expect(t, name, expected)
}
func TestApp_Float64Flag(t *testing.T) {
var meters float64
@ -498,7 +770,7 @@ func TestApp_Float64Flag(t *testing.T) {
},
}
app.Run([]string{"", "--height", "1.93"})
_ = app.Run([]string{"", "--height", "1.93"})
expect(t, meters, 1.93)
}
@ -528,7 +800,7 @@ func TestApp_ParseSliceFlags(t *testing.T) {
var _ = parsedOption
var _ = firstArg
app.Run([]string{"", "cmd", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4", "my-arg"})
_ = app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"})
IntsEquals := func(a, b []int) bool {
if len(a) != len(b) {
@ -586,7 +858,7 @@ func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) {
},
}
app.Run([]string{"", "cmd", "-a", "2", "-str", "A", "my-arg"})
_ = app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"})
var expectedIntSlice = []int{2}
var expectedStringSlice = []string{"A"}
@ -818,6 +1090,145 @@ func TestAppNoHelpFlag(t *testing.T) {
}
}
func TestRequiredFlagAppRunBehavior(t *testing.T) {
tdata := []struct {
testCase string
appFlags []Flag
appRunInput []string
appCommands []*Command
expectedAnError bool
}{
// assertion: empty input, when a required flag is present, errors
{
testCase: "error_case_empty_input_with_required_flag_on_app",
appRunInput: []string{"myCLI"},
appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
expectedAnError: true,
},
{
testCase: "error_case_empty_input_with_required_flag_on_command",
appRunInput: []string{"myCLI", "myCommand"},
appCommands: []*Command{{
Name: "myCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
}},
expectedAnError: true,
},
{
testCase: "error_case_empty_input_with_required_flag_on_subcommand",
appRunInput: []string{"myCLI", "myCommand", "mySubCommand"},
appCommands: []*Command{{
Name: "myCommand",
Subcommands: []*Command{{
Name: "mySubCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
}},
}},
expectedAnError: true,
},
// assertion: inputing --help, when a required flag is present, does not error
{
testCase: "valid_case_help_input_with_required_flag_on_app",
appRunInput: []string{"myCLI", "--help"},
appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
},
{
testCase: "valid_case_help_input_with_required_flag_on_command",
appRunInput: []string{"myCLI", "myCommand", "--help"},
appCommands: []*Command{{
Name: "myCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
}},
},
{
testCase: "valid_case_help_input_with_required_flag_on_subcommand",
appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--help"},
appCommands: []*Command{{
Name: "myCommand",
Subcommands: []*Command{{
Name: "mySubCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
}},
}},
},
// assertion: giving optional input, when a required flag is present, errors
{
testCase: "error_case_optional_input_with_required_flag_on_app",
appRunInput: []string{"myCLI", "--optional", "cats"},
appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}},
expectedAnError: true,
},
{
testCase: "error_case_optional_input_with_required_flag_on_command",
appRunInput: []string{"myCLI", "myCommand", "--optional", "cats"},
appCommands: []*Command{{
Name: "myCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}},
}},
expectedAnError: true,
},
{
testCase: "error_case_optional_input_with_required_flag_on_subcommand",
appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--optional", "cats"},
appCommands: []*Command{{
Name: "myCommand",
Subcommands: []*Command{{
Name: "mySubCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}, &StringFlag{Name: "optional"}},
}},
}},
expectedAnError: true,
},
// assertion: when a required flag is present, inputting that required flag does not error
{
testCase: "valid_case_required_flag_input_on_app",
appRunInput: []string{"myCLI", "--requiredFlag", "cats"},
appFlags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
},
{
testCase: "valid_case_required_flag_input_on_command",
appRunInput: []string{"myCLI", "myCommand", "--requiredFlag", "cats"},
appCommands: []*Command{{
Name: "myCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
}},
},
{
testCase: "valid_case_required_flag_input_on_subcommand",
appRunInput: []string{"myCLI", "myCommand", "mySubCommand", "--requiredFlag", "cats"},
appCommands: []*Command{{
Name: "myCommand",
Subcommands: []*Command{{
Name: "mySubCommand",
Flags: []Flag{&StringFlag{Name: "requiredFlag", Required: true}},
}},
}},
},
}
for _, test := range tdata {
t.Run(test.testCase, func(t *testing.T) {
// setup
app := NewApp()
app.Flags = test.appFlags
app.Commands = test.appCommands
// logic under test
err := app.Run(test.appRunInput)
// assertions
if test.expectedAnError && err == nil {
t.Errorf("expected an error, but there was none")
}
if _, ok := err.(requiredFlagsErr); test.expectedAnError && !ok {
t.Errorf("expected a requiredFlagsErr, but got: %s", err)
}
if !test.expectedAnError && err != nil {
t.Errorf("did not expected an error, but there was one: %s", err)
}
})
}
}
func TestAppHelpPrinter(t *testing.T) {
oldPrinter := HelpPrinter
defer func() {
@ -830,7 +1241,7 @@ func TestAppHelpPrinter(t *testing.T) {
}
app := &App{}
app.Run([]string{"-h"})
_ = app.Run([]string{"-h"})
if wasCalled == false {
t.Errorf("Help printer expected to be called, but was not")
@ -876,7 +1287,7 @@ func TestApp_CommandNotFound(t *testing.T) {
},
}
app.Run([]string{"command", "foo"})
_ = app.Run([]string{"command", "foo"})
expect(t, counts.CommandNotFound, 1)
expect(t, counts.SubCommand, 0)
@ -889,9 +1300,9 @@ func TestApp_OrderOfOperations(t *testing.T) {
resetCounts := func() { counts = &opCounts{} }
app := &App{
EnableShellCompletion: true,
ShellComplete: func(c *Context) {
fmt.Fprintf(os.Stderr, "---> ShellComplete(%#v)\n", c)
EnableBashCompletion: true,
BashComplete: func(c *Context) {
_, _ = fmt.Fprintf(os.Stderr, "---> BashComplete(%#v)\n", c)
counts.Total++
counts.ShellComplete = counts.Total
},
@ -956,7 +1367,7 @@ func TestApp_OrderOfOperations(t *testing.T) {
resetCounts()
_ = app.Run([]string{"command", fmt.Sprintf("--%s", genCompName())})
_ = app.Run([]string{"command", fmt.Sprintf("--%s", "--generate-bash-completion")})
expect(t, counts.ShellComplete, 1)
expect(t, counts.Total, 1)
@ -1306,7 +1717,7 @@ func TestApp_Run_Categories(t *testing.T) {
Writer: buf,
}
app.Run([]string{"categories"})
_ = app.Run([]string{"categories"})
expect := commandCategories([]*commandCategory{
{
@ -1569,6 +1980,18 @@ func (c *customBoolFlag) Names() []string {
return []string{c.Nombre}
}
func (c *customBoolFlag) TakesValue() bool {
return false
}
func (c *customBoolFlag) GetValue() string {
return "value"
}
func (c *customBoolFlag) GetUsage() string {
return "usage"
}
func (c *customBoolFlag) Apply(set *flag.FlagSet) {
set.String(c.Nombre, c.Nombre, "")
}
@ -1613,6 +2036,59 @@ func TestCustomHelpVersionFlags(t *testing.T) {
}
}
func TestHandleExitCoder_Default(t *testing.T) {
app := NewApp()
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
ctx := NewContext(app, fs, nil)
app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42))
output := fakeErrWriter.String()
if !strings.Contains(output, "Default") {
t.Fatalf("Expected Default Behavior from Error Handler but got: %s", output)
}
}
func TestHandleExitCoder_Custom(t *testing.T) {
app := NewApp()
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
app.ExitErrHandler = func(_ *Context, _ error) {
_, _ = fmt.Fprintln(ErrWriter, "I'm a Custom error handler, I print what I want!")
}
ctx := NewContext(app, fs, nil)
app.handleExitCoder(ctx, NewExitError("Default Behavior Error", 42))
output := fakeErrWriter.String()
if !strings.Contains(output, "Custom") {
t.Fatalf("Expected Custom Behavior from Error Handler but got: %s", output)
}
}
func TestHandleAction_WithUnknownPanic(t *testing.T) {
defer func() { refute(t, recover(), nil) }()
var fn ActionFunc
app := NewApp()
app.Action = func(ctx *Context) error {
_ = fn(ctx)
return nil
}
fs, err := flagSet(app.Name, app.Flags)
if err != nil {
t.Errorf("error creating FlagSet: %s", err)
}
_ = HandleAction(app.Action, NewContext(app, fs, nil))
}
func TestShellCompletionForIncompleteFlags(t *testing.T) {
app := &App{
Flags: []Flag{
@ -1620,30 +2096,30 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
Name: "test-completion",
},
},
EnableShellCompletion: true,
ShellComplete: func(ctx *Context) {
EnableBashCompletion: true,
BashComplete: func(ctx *Context) {
for _, command := range ctx.App.Commands {
if command.Hidden {
continue
}
for _, name := range command.Names() {
fmt.Fprintln(ctx.App.Writer, name)
_, _ = fmt.Fprintln(ctx.App.Writer, name)
}
}
for _, flag := range ctx.App.Flags {
for _, name := range flag.Names() {
if name == genCompName() {
if name == BashCompletionFlag.Names()[0] {
continue
}
switch name = strings.TrimSpace(name); len(name) {
case 0:
case 1:
fmt.Fprintln(ctx.App.Writer, "-"+name)
_, _ = fmt.Fprintln(ctx.App.Writer, "-"+name)
default:
fmt.Fprintln(ctx.App.Writer, "--"+name)
_, _ = fmt.Fprintln(ctx.App.Writer, "--"+name)
}
}
}
@ -1652,8 +2128,62 @@ func TestShellCompletionForIncompleteFlags(t *testing.T) {
return fmt.Errorf("should not get here")
},
}
err := app.Run([]string{"", "--test-completion", "--" + genCompName()})
err := app.Run([]string{"", "--test-completion", "--" + "generate-bash-completion"})
if err != nil {
t.Errorf("app should not return an error: %s", err)
}
}
func TestWhenExitSubCommandWithCodeThenAppQuitUnexpectedly(t *testing.T) {
testCode := 104
app := NewApp()
app.Commands = []*Command{
{
Name: "cmd",
Subcommands: []*Command{
{
Name: "subcmd",
Action: func(c *Context) error {
return NewExitError("exit error", testCode)
},
},
},
},
}
// set user function as ExitErrHandler
var exitCodeFromExitErrHandler int
app.ExitErrHandler = func(c *Context, err error) {
if exitErr, ok := err.(ExitCoder); ok {
t.Log(exitErr)
exitCodeFromExitErrHandler = exitErr.ExitCode()
}
}
// keep and restore original OsExiter
origExiter := OsExiter
defer func() {
OsExiter = origExiter
}()
// set user function as OsExiter
var exitCodeFromOsExiter int
OsExiter = func(exitCode int) {
exitCodeFromOsExiter = exitCode
}
_ = app.Run([]string{
"myapp",
"cmd",
"subcmd",
})
if exitCodeFromOsExiter != 0 {
t.Errorf("exitCodeFromOsExiter should not change, but its value is %v", exitCodeFromOsExiter)
}
if exitCodeFromExitErrHandler != testCode {
t.Errorf("exitCodeFromOsExiter valeu should be %v, but its value is %v", testCode, exitCodeFromExitErrHandler)
}
}

View File

@ -11,26 +11,17 @@ cache:
environment:
GOPATH: C:\gopath
GOVERSION: 1.8.x
PYTHON: C:\Python36-x64
PYTHON_VERSION: 3.6.x
PYTHON_ARCH: 64
GOVERSION: 1.11.x
install:
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version
- go env
- go get github.com/urfave/gfmrun/...
- rmdir c:\gopath\src\gopkg.in\urfave\cli.v2 /s /q
- rmdir c:\gopath\pkg /s /q
- git clone . c:\gopath\src\gopkg.in\urfave\cli.v2
- go get -v -t ./...
- if not exist node_modules\.bin\markdown-toc npm install markdown-toc
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH%
- go version
- go env
- go get github.com/urfave/gfmrun/...
- go get -v -t ./...
build_script:
- python runtests vet
- python runtests test
- python runtests gfmrun
- python cli-v1-to-v2 --selftest
- python runtests migrations
- python runtests toc
- go run build.go vet
- go run build.go test
- go run build.go gfmrun

View File

@ -3,14 +3,19 @@
: ${PROG:=$(basename ${BASH_SOURCE})}
_cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
if [[ "$cur" == "-"* ]]; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion )
else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion )
fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -F _cli_bash_autocomplete $PROG
complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete $PROG
unset PROG

View File

@ -1,5 +1,11 @@
autoload -U compinit && compinit
autoload -U bashcompinit && bashcompinit
_cli_zsh_autocomplete() {
script_dir=$(dirname $0)
source ${script_dir}/bash_autocomplete
local -a opts
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}")
_describe 'values' opts
return
}
compdef _cli_zsh_autocomplete $PROG

164
build.go Normal file
View File

@ -0,0 +1,164 @@
//+build ignore
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
"github.com/urfave/cli"
)
var packages = []string{"cli", "altsrc"}
func main() {
app := cli.NewApp()
app.Name = "builder"
app.Usage = "Generates a new urfave/cli build!"
app.Commands = cli.Commands{
cli.Command{
Name: "vet",
Action: VetActionFunc,
},
cli.Command{
Name: "test",
Action: TestActionFunc,
},
cli.Command{
Name: "gfmrun",
Action: GfmrunActionFunc,
},
cli.Command{
Name: "toc",
Action: TocActionFunc,
},
}
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}
func runCmd(arg string, args ...string) error {
cmd := exec.Command(arg, args...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func VetActionFunc(_ *cli.Context) error {
return runCmd("go", "vet")
}
func TestActionFunc(c *cli.Context) error {
for _, pkg := range packages {
var packageName string
if pkg == "cli" {
packageName = "github.com/urfave/cli"
} else {
packageName = fmt.Sprintf("github.com/urfave/cli/%s", pkg)
}
coverProfile := fmt.Sprintf("--coverprofile=%s.coverprofile", pkg)
err := runCmd("go", "test", "-v", coverProfile, packageName)
if err != nil {
return err
}
}
return testCleanup()
}
func testCleanup() error {
var out bytes.Buffer
for _, pkg := range packages {
file, err := os.Open(fmt.Sprintf("%s.coverprofile", pkg))
if err != nil {
return err
}
b, err := ioutil.ReadAll(file)
if err != nil {
return err
}
out.Write(b)
err = file.Close()
if err != nil {
return err
}
err = os.Remove(fmt.Sprintf("%s.coverprofile", pkg))
if err != nil {
return err
}
}
outFile, err := os.Create("coverage.txt")
if err != nil {
return err
}
_, err = out.WriteTo(outFile)
if err != nil {
return err
}
err = outFile.Close()
if err != nil {
return err
}
return nil
}
func GfmrunActionFunc(_ *cli.Context) error {
file, err := os.Open("README.md")
if err != nil {
return err
}
var counter int
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "package main") {
counter++
}
}
err = scanner.Err()
if err != nil {
return err
}
return runCmd("gfmrun", "-c", fmt.Sprint(counter), "-s", "README.md")
}
func TocActionFunc(_ *cli.Context) error {
err := runCmd("node_modules/.bin/markdown-toc", "-i", "README.md")
if err != nil {
return err
}
err = runCmd("git", "diff", "--exit-code")
if err != nil {
return err
}
return nil
}

View File

@ -15,7 +15,7 @@ func newCommandCategories() CommandCategories {
}
func (c *commandCategories) Less(i, j int) bool {
return (*c)[i].Name() < (*c)[j].Name()
return lexicographicLess((*c)[i].Name(), (*c)[j].Name() )
}
func (c *commandCategories) Len() int {
@ -35,7 +35,7 @@ func (c *commandCategories) AddCommand(category string, command *Command) {
}
newVal := commandCategories(append(*c,
&commandCategory{name: category, commands: []*Command{command}}))
(*c) = newVal
*c = newVal
}
func (c *commandCategories) Categories() []CommandCategory {
@ -75,7 +75,7 @@ func (c *commandCategory) VisibleCommands() []*Command {
c.commands = []*Command{}
}
ret := []*Command{}
var ret []*Command
for _, command := range c.commands {
if !command.Hidden {
ret = append(ret, command)

2
cli.go
View File

@ -20,4 +20,4 @@
// }
package cli
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go

View File

@ -1,6 +1,7 @@
package cli
import (
"flag"
"fmt"
"io/ioutil"
"sort"
@ -23,8 +24,8 @@ type Command struct {
ArgsUsage string
// The category the command is part of
Category string
// The function to call when checking for shell command completions
ShellComplete ShellCompleteFunc
// The function to call when checking for bash command completions
BashComplete BashCompleteFunc
// An action to execute before any sub-subcommands are run, but after the context is ready
// If a non-nil error is returned, no sub-subcommands are run
Before BeforeFunc
@ -45,6 +46,10 @@ type Command struct {
HideHelp bool
// Boolean to hide this command from help or completion
Hidden bool
// Boolean to enable short-option handling so user can combine several
// single-character bool arguments into one
// i.e. foobar -o -v -> foobar -ov
UseShortOptionHandling bool
// Full name of command for help, defaults to full command name, including parent commands.
HelpName string
@ -63,7 +68,7 @@ func (c CommandsByName) Len() int {
}
func (c CommandsByName) Less(i, j int) bool {
return c[i].Name < c[j].Name
return lexicographicLess(c[i].Name, c[j].Name)
}
func (c CommandsByName) Swap(i, j int) {
@ -90,10 +95,14 @@ func (c *Command) Run(ctx *Context) (err error) {
c.appendFlag(HelpFlag)
}
if ctx.App.EnableShellCompletion {
c.appendFlag(GenerateCompletionFlag)
if ctx.App.UseShortOptionHandling {
c.UseShortOptionHandling = true
}
//if ctx.App.EnableShellCompletion {
// c.appendFlag(GenerateCompletionFlag)
//}
set, err := flagSet(c.Name, c.Flags)
if err != nil {
return err
@ -108,9 +117,9 @@ func (c *Command) Run(ctx *Context) (err error) {
nerr := normalizeFlags(c.Flags, set)
if nerr != nil {
fmt.Fprintln(ctx.App.Writer, nerr)
fmt.Fprintln(ctx.App.Writer)
ShowCommandHelp(ctx, c.Name)
_, _ = fmt.Fprintln(ctx.App.Writer, nerr)
_, _ = fmt.Fprintln(ctx.App.Writer)
_ = ShowCommandHelp(ctx, c.Name)
return nerr
}
@ -126,9 +135,9 @@ func (c *Command) Run(ctx *Context) (err error) {
HandleExitCoder(err)
return err
}
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
fmt.Fprintln(context.App.Writer)
ShowCommandHelp(context, c.Name)
_, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
_, _ = fmt.Fprintln(context.App.Writer)
_ = ShowCommandHelp(context, c.Name)
return err
}
@ -136,6 +145,12 @@ func (c *Command) Run(ctx *Context) (err error) {
return nil
}
cerr := checkRequiredFlags(c.Flags, context)
if cerr != nil {
_ = ShowCommandHelp(context, c.Name)
return cerr
}
if c.After != nil {
defer func() {
afterErr := c.After(context)
@ -172,6 +187,14 @@ func (c *Command) Run(ctx *Context) (err error) {
return err
}
func (c *Command) newFlagSet() (*flag.FlagSet, error) {
return flagSet(c.Name, c.Flags)
}
func (c *Command) useShortOptionHandling() bool {
return c.UseShortOptionHandling
}
// Names returns the names including short names and aliases.
func (c *Command) Names() []string {
return append([]string{c.Name}, c.Aliases...)
@ -217,6 +240,7 @@ func (c *Command) startApp(ctx *Context) error {
app.Compiled = ctx.App.Compiled
app.Writer = ctx.App.Writer
app.ErrWriter = ctx.App.ErrWriter
app.UseShortOptionHandling = ctx.App.UseShortOptionHandling
app.Categories = newCommandCategories()
for _, command := range c.Subcommands {
@ -226,9 +250,9 @@ func (c *Command) startApp(ctx *Context) error {
sort.Sort(app.Categories.(*commandCategories))
// bash completion
app.EnableShellCompletion = ctx.App.EnableShellCompletion
if c.ShellComplete != nil {
app.ShellComplete = c.ShellComplete
app.EnableBashCompletion = ctx.App.EnableBashCompletion
if c.BashComplete != nil {
app.BashComplete = c.BashComplete
}
// set the actions

View File

@ -11,32 +11,40 @@ import (
func TestCommandFlagParsing(t *testing.T) {
cases := []struct {
testArgs []string
skipFlagParsing bool
expectedErr error
testArgs []string
skipFlagParsing bool
skipArgReorder bool
expectedErr error
UseShortOptionHandling bool
}{
// Test normal "not ignoring flags" flow
{[]string{"test-cmd", "-break", "blah", "blah"}, false, errors.New("flag provided but not defined: -break")},
{[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break"), false},
{[]string{"test-cmd", "blah", "blah"}, true, nil}, // Test SkipFlagParsing without any args that look like flags
{[]string{"test-cmd", "blah", "-break"}, true, nil}, // Test SkipFlagParsing with random flag arg
{[]string{"test-cmd", "blah", "-help"}, true, nil}, // Test SkipFlagParsing with "special" help flag arg
// Test no arg reorder
{[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil, false},
{[]string{"test-cmd", "blah", "blah", "-break", "ls", "-l"}, false, true, nil, true},
{[]string{"test-cmd", "blah", "blah"}, true, false, nil, false}, // Test SkipFlagParsing without any args that look like flags
{[]string{"test-cmd", "blah", "-break"}, true, false, nil, false}, // Test SkipFlagParsing with random flag arg
{[]string{"test-cmd", "blah", "-help"}, true, false, nil, false}, // Test SkipFlagParsing with "special" help flag arg
{[]string{"test-cmd", "blah"}, false, false, nil, true}, // Test UseShortOptionHandling
}
for _, c := range cases {
app := &App{Writer: ioutil.Discard}
set := flag.NewFlagSet("test", 0)
set.Parse(c.testArgs)
_ = set.Parse(c.testArgs)
context := NewContext(app, set, nil)
command := Command{
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) error { return nil },
SkipFlagParsing: c.skipFlagParsing,
Name: "test-cmd",
Aliases: []string{"tc"},
Usage: "this is for testing",
Description: "testing",
Action: func(_ *Context) error { return nil },
SkipFlagParsing: c.skipFlagParsing,
UseShortOptionHandling: c.UseShortOptionHandling,
}
err := command.Run(context)
@ -46,6 +54,51 @@ func TestCommandFlagParsing(t *testing.T) {
}
}
func TestParseAndRunShortOpts(t *testing.T) {
cases := []struct {
testArgs []string
expectedErr error
expectedArgs []string
}{
{[]string{"foo", "test", "-a"}, nil, []string{}},
{[]string{"foo", "test", "-c", "arg1", "arg2"}, nil, []string{"arg1", "arg2"}},
{[]string{"foo", "test", "-f"}, nil, []string{}},
{[]string{"foo", "test", "-ac", "--fgh"}, nil, []string{}},
{[]string{"foo", "test", "-af"}, nil, []string{}},
{[]string{"foo", "test", "-cf"}, nil, []string{}},
{[]string{"foo", "test", "-acf"}, nil, []string{}},
{[]string{"foo", "test", "-invalid"}, errors.New("flag provided but not defined: -invalid"), []string{}},
{[]string{"foo", "test", "-acf", "arg1", "-invalid"}, nil, []string{"arg1", "-invalid"}},
}
var args Args
cmd := Command{
Name: "test",
Usage: "this is for testing",
Description: "testing",
Action: func(c *Context) error {
args = c.Args()
return nil
},
UseShortOptionHandling: true,
Flags: []Flag{
&BoolFlag{Name: "abc", Aliases: []string{"a"}},
&BoolFlag{Name: "cde", Aliases: []string{"c"}},
&BoolFlag{Name: "fgh", Aliases: []string{"f"}},
},
}
for _, c := range cases {
app := NewApp()
app.Commands = []Command{cmd}
err := app.Run(c.testArgs)
expect(t, err, c.expectedErr)
expect(t, args, c.expectedArgs)
}
}
func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) {
app := &App{
Commands: []*Command{
@ -238,3 +291,77 @@ func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) {
t.Fatal(err)
}
}
func TestCommandFlagReordering(t *testing.T) {
cases := []struct {
testArgs []string
expectedValue string
expectedArgs []string
expectedErr error
}{
{[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, "foo", []string{"some-arg"}, nil},
{[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, "foo", []string{"some-arg"}, nil},
{[]string{"some-exec", "some-command", "--flag=foo", "some-arg"}, "foo", []string{"some-arg"}, nil},
}
for _, c := range cases {
value := ""
var args Args
app := &App{
Commands: []*Command{
{
Name: "some-command",
Flags: []Flag{
&StringFlag{Name: "flag"},
},
Action: func(c *Context) error {
fmt.Printf("%+v\n", c.String("flag"))
value = c.String("flag")
args = c.Args()
return nil
},
},
},
}
err := app.Run(c.testArgs)
expect(t, err, c.expectedErr)
expect(t, value, c.expectedValue)
expect(t, args, c.expectedArgs)
}
}
func TestCommandSkipFlagParsing(t *testing.T) {
cases := []struct {
testArgs []string
expectedArgs []string
expectedErr error
}{
{[]string{"some-exec", "some-command", "some-arg", "--flag", "foo"}, []string{"some-arg", "--flag", "foo"}, nil},
{[]string{"some-exec", "some-command", "some-arg", "--flag=foo"}, []string{"some-arg", "--flag=foo"}, nil},
}
for _, c := range cases {
var args Args
app := &App{
Commands: []*Command{
{
SkipFlagParsing: true,
Name: "some-command",
Flags: []Flag{
&StringFlag{Name: "flag"},
},
Action: func(c *Context) error {
fmt.Printf("%+v\n", c.String("flag"))
args = c.Args()
return nil
},
},
},
}
err := app.Run(c.testArgs)
expect(t, err, c.expectedErr)
expect(t, args, c.expectedArgs)
}
}

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/signal"
"reflect"
@ -20,7 +21,7 @@ type Context struct {
App *App
Command *Command
shellComplete bool
setFlags map[string]bool
flagSet *flag.FlagSet
parentContext *Context
}
@ -65,41 +66,56 @@ func (c *Context) IsSet(name string) bool {
isSet = true
}
})
if isSet {
return true
// XXX hack to support IsSet for flags with EnvVar
//
// There isn't an easy way to do this with the current implementation since
// whether a flag was set via an environment variable is very difficult to
// determine here. Instead, we intend to introduce a backwards incompatible
// change in version 2 to add `IsSet` to the Flag interface to push the
// responsibility closer to where the information required to determine
// whether a flag is set by non-standard means such as environment
// variables is available.
//
// See https://github.com/urfave/cli/issues/294 for additional discussion
flags := c.Command.Flags
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
if c.App != nil {
flags = c.App.Flags
}
}
}
for _, f := range flags {
for _, name := range f.Names() {
if isSet, ok := c.setFlags[name]; isSet || !ok {
continue
}
// XXX hack to support IsSet for flags with EnvVar
//
// There isn't an easy way to do this with the current implementation since
// whether a flag was set via an environment variable is very difficult to
// determine here. Instead, we intend to introduce a backwards incompatible
// change in version 2 to add `IsSet` to the Flag interface to push the
// responsibility closer to where the information required to determine
// whether a flag is set by non-standard means such as environment
// variables is avaliable.
//
// See https://github.com/urfave/cli/issues/294 for additional discussion
f := lookupFlag(name, c)
if f == nil {
return false
}
val := reflect.ValueOf(f)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
val := reflect.ValueOf(f)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
filePathValue := val.FieldByName("FilePath")
if filePathValue.IsValid() {
eachName(filePathValue.String(), func(filePath string) {
if _, err := os.Stat(filePath); err == nil {
c.setFlags[name] = true
return
}
})
}
envVarValues := val.FieldByName("EnvVars")
if !envVarValues.IsValid() {
return false
}
for _, envVar := range envVarValues.Interface().([]string) {
envVar = strings.TrimSpace(envVar)
if envVal := os.Getenv(envVar); envVal != "" {
return true
envVarValues := val.FieldByName("EnvVars")
if envVarValues.IsValid() {
for _, envVar := range envVarValues.Interface().([]string) {
envVar = strings.TrimSpace(envVar)
if _, ok := syscall.Getenv(envVar); ok {
c.setFlags[name] = true
continue
}
}
}
}
}
}
@ -108,7 +124,7 @@ func (c *Context) IsSet(name string) bool {
// LocalFlagNames returns a slice of flag names used in this context.
func (c *Context) LocalFlagNames() []string {
names := []string{}
var names []string
c.flagSet.Visit(makeFlagNameVisitor(&names))
return names
}
@ -116,17 +132,42 @@ func (c *Context) LocalFlagNames() []string {
// FlagNames returns a slice of flag names used by the this context and all of
// its parent contexts.
func (c *Context) FlagNames() []string {
names := []string{}
var names []string
for _, ctx := range c.Lineage() {
ctx.flagSet.Visit(makeFlagNameVisitor(&names))
}
return names
}
// FlagNames returns a slice of flag names used in this context.
//func (c *Context) FlagNames() (names []string) {
// for _, f := range c.Command.Flags {
// name := strings.Split(f.GetName(), ",")[0]
// if name == "help" {
// continue
// }
// names = append(names, name)
// }
// return
//}
// GlobalFlagNames returns a slice of global flag names used by the app.
//func (c *Context) GlobalFlagNames() (names []string) {
// for _, f := range c.App.Flags {
// name := strings.Split(f.GetName(), ",")[0]
// if name == "help" || name == "version" {
// continue
// }
// names = append(names, name)
// }
// return names
//}
// Lineage returns *this* context and all of its ancestor contexts in order from
// child to parent
func (c *Context) Lineage() []*Context {
lineage := []*Context{}
var lineage []*Context
for cur := c; cur != nil; cur = cur.parentContext {
lineage = append(lineage, cur)
@ -191,10 +232,10 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet {
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
switch ff.Value.(type) {
case Serializeder:
set.Set(name, ff.Value.(Serializeder).Serialized())
case Serializer:
_ = set.Set(name, ff.Value.(Serializer).Serialize())
default:
set.Set(name, ff.Value.String())
_ = set.Set(name, ff.Value.String())
}
}
@ -244,7 +285,58 @@ func makeFlagNameVisitor(names *[]string) func(*flag.Flag) {
}
if name != "" {
(*names) = append(*names, name)
*names = append(*names, name)
}
}
}
type requiredFlagsErr interface {
error
getMissingFlags() []string
}
type errRequiredFlags struct {
missingFlags []string
}
func (e *errRequiredFlags) Error() string {
numberOfMissingFlags := len(e.missingFlags)
if numberOfMissingFlags == 1 {
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
}
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}
func (e *errRequiredFlags) getMissingFlags() []string {
return e.missingFlags
}
func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr {
var missingFlags []string
for _, f := range flags {
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
var flagPresent bool
var flagName string
for _, key := range f.Names() {
if len(key) > 1 {
flagName = key
}
if context.IsSet(strings.TrimSpace(key)) {
flagPresent = true
}
}
if !flagPresent && flagName != "" {
missingFlags = append(missingFlags, flagName)
}
}
}
if len(missingFlags) != 0 {
return &errRequiredFlags{missingFlags: missingFlags}
}
return nil
}

View File

@ -3,7 +3,12 @@ package cli
import (
"context"
"flag"
"sort"
"os"
"strings"
"testing"
"time"
)
@ -91,9 +96,11 @@ func TestContext_Float64(t *testing.T) {
func TestContext_Duration(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Duration("myflag", 12*time.Second, "doc")
parentSet := flag.NewFlagSet("test", 0)
parentSet.Duration("top-flag", 13*time.Second, "doc")
parentCtx := NewContext(nil, parentSet, nil)
c := NewContext(nil, set, parentCtx)
expect(t, c.Duration("myflag"), 12*time.Second)
expect(t, c.Duration("top-flag"), 13*time.Second)
@ -136,7 +143,7 @@ func TestContext_Args(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
c := NewContext(nil, set, nil)
set.Parse([]string{"--myflag", "bat", "baz"})
_ = set.Parse([]string{"--myflag", "bat", "baz"})
expect(t, c.Args().Len(), 2)
expect(t, c.Bool("myflag"), true)
}
@ -145,7 +152,7 @@ func TestContext_NArg(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
c := NewContext(nil, set, nil)
set.Parse([]string{"--myflag", "bat", "baz"})
_ = set.Parse([]string{"--myflag", "bat", "baz"})
expect(t, c.NArg(), 2)
}
@ -159,8 +166,8 @@ func TestContext_IsSet(t *testing.T) {
parentCtx := NewContext(nil, parentSet, nil)
ctx := NewContext(nil, set, parentCtx)
set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"})
parentSet.Parse([]string{"--top-flag"})
_ = set.Parse([]string{"--one-flag", "--two-flag", "--three-flag", "frob"})
_ = parentSet.Parse([]string{"--top-flag"})
expect(t, ctx.IsSet("one-flag"), true)
expect(t, ctx.IsSet("two-flag"), true)
@ -169,6 +176,121 @@ func TestContext_IsSet(t *testing.T) {
expect(t, ctx.IsSet("bogus"), false)
}
// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
// Should be moved to `flag_test` in v2
func TestContext_IsSet_fromEnv(t *testing.T) {
var (
timeoutIsSet, tIsSet bool
noEnvVarIsSet, nIsSet bool
passwordIsSet, pIsSet bool
unparsableIsSet, uIsSet bool
)
os.Clearenv()
_ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
_ = os.Setenv("APP_PASSWORD", "")
a := App{
Flags: []Flag{
&Float64Flag{Name: "timeout", Aliases: []string{"t"}, EnvVars: []string{"APP_TIMEOUT_SECONDS"}},
&StringFlag{Name: "password", Aliases: []string{"p"}, EnvVars: []string{"APP_PASSWORD"}},
&Float64Flag{Name: "unparsable", Aliases: []string{"u"}, EnvVars: []string{"APP_UNPARSABLE"}},
&Float64Flag{Name: "no-env-var", Aliases: []string{"n"}},
},
Action: func(ctx *Context) error {
timeoutIsSet = ctx.IsSet("timeout")
tIsSet = ctx.IsSet("t")
passwordIsSet = ctx.IsSet("password")
pIsSet = ctx.IsSet("p")
unparsableIsSet = ctx.IsSet("unparsable")
uIsSet = ctx.IsSet("u")
noEnvVarIsSet = ctx.IsSet("no-env-var")
nIsSet = ctx.IsSet("n")
return nil
},
}
_ = a.Run([]string{"run"})
expect(t, timeoutIsSet, true)
expect(t, tIsSet, true)
expect(t, passwordIsSet, true)
expect(t, pIsSet, true)
expect(t, noEnvVarIsSet, false)
expect(t, nIsSet, false)
_ = os.Setenv("APP_UNPARSABLE", "foobar")
_ = a.Run([]string{"run"})
expect(t, unparsableIsSet, false)
expect(t, uIsSet, false)
}
// XXX Corresponds to hack in context.IsSet for flags with EnvVar field
// TODO: Should be moved to `flag_test` in v2
//func TestContext_GlobalIsSet_fromEnv(t *testing.T) {
// var (
// timeoutIsSet, tIsSet bool
// noEnvVarIsSet, nIsSet bool
// passwordIsSet, pIsSet bool
// passwordValue string
// unparsableIsSet, uIsSet bool
// overrideIsSet, oIsSet bool
// overrideValue string
// )
//
// os.Clearenv()
// _ = os.Setenv("APP_TIMEOUT_SECONDS", "15.5")
// _ = os.Setenv("APP_PASSWORD", "badpass")
// _ = os.Setenv("APP_OVERRIDE", "overridden")
// a := App{
// Flags: []Flag{
// Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"},
// StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"},
// Float64Flag{Name: "no-env-var, n"},
// Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"},
// StringFlag{Name: "overrides-default, o", Value: "default", EnvVar: "APP_OVERRIDE"},
// },
// Commands: []Command{
// {
// Name: "hello",
// Action: func(ctx *Context) error {
// timeoutIsSet = ctx.GlobalIsSet("timeout")
// tIsSet = ctx.GlobalIsSet("t")
// passwordIsSet = ctx.GlobalIsSet("password")
// pIsSet = ctx.GlobalIsSet("p")
// passwordValue = ctx.GlobalString("password")
// unparsableIsSet = ctx.GlobalIsSet("unparsable")
// uIsSet = ctx.GlobalIsSet("u")
// noEnvVarIsSet = ctx.GlobalIsSet("no-env-var")
// nIsSet = ctx.GlobalIsSet("n")
// overrideIsSet = ctx.GlobalIsSet("overrides-default")
// oIsSet = ctx.GlobalIsSet("o")
// overrideValue = ctx.GlobalString("overrides-default")
// return nil
// },
// },
// },
// }
// if err := a.Run([]string{"run", "hello"}); err != nil {
// t.Logf("error running Run(): %+v", err)
// }
// expect(t, timeoutIsSet, true)
// expect(t, tIsSet, true)
// expect(t, passwordIsSet, true)
// expect(t, pIsSet, true)
// expect(t, passwordValue, "badpass")
// expect(t, unparsableIsSet, false)
// expect(t, noEnvVarIsSet, false)
// expect(t, nIsSet, false)
// expect(t, overrideIsSet, true)
// expect(t, oIsSet, true)
// expect(t, overrideValue, "overridden")
//
// _ = os.Setenv("APP_UNPARSABLE", "foobar")
// if err := a.Run([]string{"run"}); err != nil {
// t.Logf("error running Run(): %+v", err)
// }
// expect(t, unparsableIsSet, false)
// expect(t, uIsSet, false)
//}
func TestContext_NumFlags(t *testing.T) {
set := flag.NewFlagSet("test", 0)
set.Bool("myflag", false, "doc")
@ -177,8 +299,8 @@ func TestContext_NumFlags(t *testing.T) {
globalSet.Bool("myflagGlobal", true, "doc")
globalCtx := NewContext(nil, globalSet, nil)
c := NewContext(nil, set, globalCtx)
set.Parse([]string{"--myflag", "--otherflag=foo"})
globalSet.Parse([]string{"--myflagGlobal"})
_ = set.Parse([]string{"--myflag", "--otherflag=foo"})
_ = globalSet.Parse([]string{"--myflagGlobal"})
expect(t, c.NumFlags(), 2)
}
@ -188,7 +310,7 @@ func TestContext_Set(t *testing.T) {
c := NewContext(nil, set, nil)
expect(t, c.IsSet("int"), false)
c.Set("int", "1")
_ = c.Set("int", "1")
expect(t, c.Int("int"), 1)
expect(t, c.IsSet("int"), true)
}
@ -293,3 +415,153 @@ func TestContextPropagation(t *testing.T) {
t.Fatal("expected context to not be nil even if the parent's context is nil")
}
}
func TestCheckRequiredFlags(t *testing.T) {
tdata := []struct {
testCase string
parseInput []string
envVarInput [2]string
flags []Flag
expectedAnError bool
expectedErrorContents []string
}{
{
testCase: "empty",
},
{
testCase: "optional",
flags: []Flag{
&StringFlag{Name: "optionalFlag"},
},
},
{
testCase: "required",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
},
expectedAnError: true,
expectedErrorContents: []string{"requiredFlag"},
},
{
testCase: "required_and_present",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
},
parseInput: []string{"--requiredFlag", "myinput"},
},
{
testCase: "required_and_present_via_env_var",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true, EnvVar: "REQUIRED_FLAG"},
},
envVarInput: [2]string{"REQUIRED_FLAG", "true"},
},
{
testCase: "required_and_optional",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
&StringFlag{Name: "optionalFlag"},
},
expectedAnError: true,
},
{
testCase: "required_and_optional_and_optional_present",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
&StringFlag{Name: "optionalFlag"},
},
parseInput: []string{"--optionalFlag", "myinput"},
expectedAnError: true,
},
{
testCase: "required_and_optional_and_optional_present_via_env_var",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
&StringFlag{Name: "optionalFlag", EnvVar: "OPTIONAL_FLAG"},
},
envVarInput: [2]string{"OPTIONAL_FLAG", "true"},
expectedAnError: true,
},
{
testCase: "required_and_optional_and_required_present",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
&StringFlag{Name: "optionalFlag"},
},
parseInput: []string{"--requiredFlag", "myinput"},
},
{
testCase: "two_required",
flags: []Flag{
&StringFlag{Name: "requiredFlagOne", Required: true},
&StringFlag{Name: "requiredFlagTwo", Required: true},
},
expectedAnError: true,
expectedErrorContents: []string{"requiredFlagOne", "requiredFlagTwo"},
},
{
testCase: "two_required_and_one_present",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
&StringFlag{Name: "requiredFlagTwo", Required: true},
},
parseInput: []string{"--requiredFlag", "myinput"},
expectedAnError: true,
},
{
testCase: "two_required_and_both_present",
flags: []Flag{
&StringFlag{Name: "requiredFlag", Required: true},
&StringFlag{Name: "requiredFlagTwo", Required: true},
},
parseInput: []string{"--requiredFlag", "myinput", "--requiredFlagTwo", "myinput"},
},
{
testCase: "required_flag_with_short_name",
flags: []Flag{
&StringSliceFlag{Name: "names, N", Required: true},
},
parseInput: []string{"-N", "asd", "-N", "qwe"},
},
{
testCase: "required_flag_with_multiple_short_names",
flags: []Flag{
&StringSliceFlag{Name: "names, N, n", Required: true},
},
parseInput: []string{"-n", "asd", "-n", "qwe"},
},
}
for _, test := range tdata {
t.Run(test.testCase, func(t *testing.T) {
// setup
set := flag.NewFlagSet("test", 0)
for _, flags := range test.flags {
flags.Apply(set)
}
_ = set.Parse(test.parseInput)
if test.envVarInput[0] != "" {
os.Clearenv()
_ = os.Setenv(test.envVarInput[0], test.envVarInput[1])
}
ctx := &Context{}
context := NewContext(ctx.App, set, ctx)
context.Command.Flags = test.flags
// logic under test
err := checkRequiredFlags(test.flags, context)
// assertions
if test.expectedAnError && err == nil {
t.Errorf("expected an error, but there was none")
}
if !test.expectedAnError && err != nil {
t.Errorf("did not expected an error, but there was one: %s", err)
}
for _, errString := range test.expectedErrorContents {
if !strings.Contains(err.Error(), errString) {
t.Errorf("expected error %q to contain %q, but it didn't!", err.Error(), errString)
}
}
})
}
}

148
docs.go Normal file
View File

@ -0,0 +1,148 @@
package cli
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"text/template"
"github.com/cpuguy83/go-md2man/v2/md2man"
)
// ToMarkdown creates a markdown string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMarkdown() (string, error) {
var w bytes.Buffer
if err := a.writeDocTemplate(&w); err != nil {
return "", err
}
return w.String(), nil
}
// ToMan creates a man page string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToMan() (string, error) {
var w bytes.Buffer
if err := a.writeDocTemplate(&w); err != nil {
return "", err
}
man := md2man.Render(w.Bytes())
return string(man), nil
}
type cliTemplate struct {
App *App
Commands []string
GlobalArgs []string
SynopsisArgs []string
}
func (a *App) writeDocTemplate(w io.Writer) error {
const name = "cli"
t, err := template.New(name).Parse(MarkdownDocTemplate)
if err != nil {
return err
}
return t.ExecuteTemplate(w, name, &cliTemplate{
App: a,
Commands: prepareCommands(a.Commands, 0),
GlobalArgs: prepareArgsWithValues(a.Flags),
SynopsisArgs: prepareArgsSynopsis(a.Flags),
})
}
func prepareCommands(commands []Command, level int) []string {
coms := []string{}
for i := range commands {
command := &commands[i]
if command.Hidden {
continue
}
usage := ""
if command.Usage != "" {
usage = command.Usage
}
prepared := fmt.Sprintf("%s %s\n\n%s\n",
strings.Repeat("#", level+2),
strings.Join(command.Names(), ", "),
usage,
)
flags := prepareArgsWithValues(command.Flags)
if len(flags) > 0 {
prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n"))
}
coms = append(coms, prepared)
// recursevly iterate subcommands
if len(command.Subcommands) > 0 {
coms = append(
coms,
prepareCommands(command.Subcommands, level+1)...,
)
}
}
return coms
}
func prepareArgsWithValues(flags []Flag) []string {
return prepareFlags(flags, ", ", "**", "**", `""`, true)
}
func prepareArgsSynopsis(flags []Flag) []string {
return prepareFlags(flags, "|", "[", "]", "[value]", false)
}
func prepareFlags(
flags []Flag,
sep, opener, closer, value string,
addDetails bool,
) []string {
args := []string{}
for _, f := range flags {
flag, ok := f.(DocGenerationFlag)
if !ok {
continue
}
modifiedArg := opener
for _, s := range strings.Split(flag.GetName(), ",") {
trimmed := strings.TrimSpace(s)
if len(modifiedArg) > len(opener) {
modifiedArg += sep
}
if len(trimmed) > 1 {
modifiedArg += fmt.Sprintf("--%s", trimmed)
} else {
modifiedArg += fmt.Sprintf("-%s", trimmed)
}
}
modifiedArg += closer
if flag.TakesValue() {
modifiedArg += fmt.Sprintf("=%s", value)
}
if addDetails {
modifiedArg += flagDetails(flag)
}
args = append(args, modifiedArg+"\n")
}
sort.Strings(args)
return args
}
// flagDetails returns a string containing the flags metadata
func flagDetails(flag DocGenerationFlag) string {
description := flag.GetUsage()
value := flag.GetValue()
if value != "" {
description += " (default: " + value + ")"
}
return ": " + description
}

122
docs_test.go Normal file
View File

@ -0,0 +1,122 @@
package cli
import (
"io/ioutil"
"testing"
)
func testApp() *App {
app := NewApp()
app.Name = "greet"
app.Flags = []Flag{
StringFlag{
Name: "socket, s",
Usage: "some 'usage' text",
Value: "value",
TakesFile: true,
},
StringFlag{Name: "flag, fl, f"},
BoolFlag{
Name: "another-flag, b",
Usage: "another usage text",
},
}
app.Commands = []Command{{
Aliases: []string{"c"},
Flags: []Flag{
StringFlag{
Name: "flag, fl, f",
TakesFile: true,
},
BoolFlag{
Name: "another-flag, b",
Usage: "another usage text",
},
},
Name: "config",
Usage: "another usage test",
Subcommands: []Command{{
Aliases: []string{"s", "ss"},
Flags: []Flag{
StringFlag{Name: "sub-flag, sub-fl, s"},
BoolFlag{
Name: "sub-command-flag, s",
Usage: "some usage text",
},
},
Name: "sub-config",
Usage: "another usage test",
}},
}, {
Aliases: []string{"i", "in"},
Name: "info",
Usage: "retrieve generic information",
}, {
Name: "some-command",
}, {
Name: "hidden-command",
Hidden: true,
}}
app.UsageText = "app [first_arg] [second_arg]"
app.Usage = "Some app"
app.Author = "Harrison"
app.Email = "harrison@lolwut.com"
app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}}
return app
}
func expectFileContent(t *testing.T, file, expected string) {
data, err := ioutil.ReadFile(file)
expect(t, err, nil)
expect(t, string(data), expected)
}
func TestToMarkdownFull(t *testing.T) {
// Given
app := testApp()
// When
res, err := app.ToMarkdown()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-full.md", res)
}
func TestToMarkdownNoFlags(t *testing.T) {
// Given
app := testApp()
app.Flags = nil
// When
res, err := app.ToMarkdown()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-no-flags.md", res)
}
func TestToMarkdownNoCommands(t *testing.T) {
// Given
app := testApp()
app.Commands = nil
// When
res, err := app.ToMarkdown()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-no-commands.md", res)
}
func TestToMan(t *testing.T) {
// Given
app := testApp()
// When
res, err := app.ToMan()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-doc-full.man", res)
}

View File

@ -59,25 +59,33 @@ type ExitCoder interface {
ExitCode() int
}
type exitError struct {
type ExitError struct {
exitCode int
message interface{}
}
// Exit wraps a message and exit code into an ExitCoder suitable for handling by
// HandleExitCoder
func Exit(message interface{}, exitCode int) ExitCoder {
return &exitError{
// NewExitError makes a new *ExitError
func NewExitError(message interface{}, exitCode int) *ExitError {
return &ExitError{
exitCode: exitCode,
message: message,
}
}
func (ee *exitError) Error() string {
// Exit wraps a message and exit code into an ExitCoder suitable for handling by
// HandleExitCoder
func Exit(message interface{}, exitCode int) ExitCoder {
return &ExitError{
exitCode: exitCode,
message: message,
}
}
func (ee *ExitError) Error() string {
return fmt.Sprintf("%v", ee.message)
}
func (ee *exitError) ExitCode() int {
func (ee *ExitError) ExitCode() int {
return ee.exitCode
}

194
fish.go Normal file
View File

@ -0,0 +1,194 @@
package cli
import (
"bytes"
"fmt"
"io"
"strings"
"text/template"
)
// ToFishCompletion creates a fish completion string for the `*App`
// The function errors if either parsing or writing of the string fails.
func (a *App) ToFishCompletion() (string, error) {
var w bytes.Buffer
if err := a.writeFishCompletionTemplate(&w); err != nil {
return "", err
}
return w.String(), nil
}
type fishCompletionTemplate struct {
App *App
Completions []string
AllCommands []string
}
func (a *App) writeFishCompletionTemplate(w io.Writer) error {
const name = "cli"
t, err := template.New(name).Parse(FishCompletionTemplate)
if err != nil {
return err
}
allCommands := []string{}
// Add global flags
completions := a.prepareFishFlags(a.VisibleFlags(), allCommands)
// Add help flag
if !a.HideHelp {
completions = append(
completions,
a.prepareFishFlags([]Flag{HelpFlag}, allCommands)...,
)
}
// Add version flag
if !a.HideVersion {
completions = append(
completions,
a.prepareFishFlags([]Flag{VersionFlag}, allCommands)...,
)
}
// Add commands and their flags
completions = append(
completions,
a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})...,
)
return t.ExecuteTemplate(w, name, &fishCompletionTemplate{
App: a,
Completions: completions,
AllCommands: allCommands,
})
}
func (a *App) prepareFishCommands(commands []Command, allCommands *[]string, previousCommands []string) []string {
completions := []string{}
for i := range commands {
command := &commands[i]
if command.Hidden {
continue
}
var completion strings.Builder
completion.WriteString(fmt.Sprintf(
"complete -r -c %s -n '%s' -a '%s'",
a.Name,
a.fishSubcommandHelper(previousCommands),
strings.Join(command.Names(), " "),
))
if command.Usage != "" {
completion.WriteString(fmt.Sprintf(" -d '%s'",
escapeSingleQuotes(command.Usage)))
}
if !command.HideHelp {
completions = append(
completions,
a.prepareFishFlags([]Flag{HelpFlag}, command.Names())...,
)
}
*allCommands = append(*allCommands, command.Names()...)
completions = append(completions, completion.String())
completions = append(
completions,
a.prepareFishFlags(command.Flags, command.Names())...,
)
// recursevly iterate subcommands
if len(command.Subcommands) > 0 {
completions = append(
completions,
a.prepareFishCommands(
command.Subcommands, allCommands, command.Names(),
)...,
)
}
}
return completions
}
func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string {
completions := []string{}
for _, f := range flags {
flag, ok := f.(DocGenerationFlag)
if !ok {
continue
}
completion := &strings.Builder{}
completion.WriteString(fmt.Sprintf(
"complete -c %s -n '%s'",
a.Name,
a.fishSubcommandHelper(previousCommands),
))
fishAddFileFlag(f, completion)
for idx, opt := range strings.Split(flag.GetName(), ",") {
if idx == 0 {
completion.WriteString(fmt.Sprintf(
" -l %s", strings.TrimSpace(opt),
))
} else {
completion.WriteString(fmt.Sprintf(
" -s %s", strings.TrimSpace(opt),
))
}
}
if flag.TakesValue() {
completion.WriteString(" -r")
}
if flag.GetUsage() != "" {
completion.WriteString(fmt.Sprintf(" -d '%s'",
escapeSingleQuotes(flag.GetUsage())))
}
completions = append(completions, completion.String())
}
return completions
}
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
switch f := flag.(type) {
case GenericFlag:
if f.TakesFile {
return
}
case StringFlag:
if f.TakesFile {
return
}
case StringSliceFlag:
if f.TakesFile {
return
}
}
completion.WriteString(" -f")
}
func (a *App) fishSubcommandHelper(allCommands []string) string {
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name)
if len(allCommands) > 0 {
fishHelper = fmt.Sprintf(
"__fish_seen_subcommand_from %s",
strings.Join(allCommands, " "),
)
}
return fishHelper
}
func escapeSingleQuotes(input string) string {
return strings.Replace(input, `'`, `\'`, -1)
}

17
fish_test.go Normal file
View File

@ -0,0 +1,17 @@
package cli
import (
"testing"
)
func TestFishCompletion(t *testing.T) {
// Given
app := testApp()
// When
res, err := app.ToFishCompletion()
// Then
expect(t, err, nil)
expectFileContent(t, "testdata/expected-fish-full.fish", res)
}

View File

@ -1,98 +0,0 @@
[
{
"name": "Bool",
"type": "bool",
"context_default": "false",
"parser": "strconv.ParseBool(f.Value.String())"
},
{
"name": "Duration",
"type": "time.Duration",
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)",
"context_default": "0",
"parser": "time.ParseDuration(f.Value.String())"
},
{
"name": "Float64",
"type": "float64",
"context_default": "0",
"parser": "strconv.ParseFloat(f.Value.String(), 64)"
},
{
"name": "Generic",
"type": "Generic",
"dest": false,
"context_default": "nil",
"context_type": "interface{}"
},
{
"name": "Int64",
"type": "int64",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)"
},
{
"name": "Int",
"type": "int",
"context_default": "0",
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)",
"parser_cast": "int(parsed)"
},
{
"name": "IntSlice",
"type": "*IntSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]int",
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)"
},
{
"name": "Int64Slice",
"type": "*Int64Slice",
"dest": false,
"context_default": "nil",
"context_type": "[]int64",
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)"
},
{
"name": "Float64Slice",
"type": "*Float64Slice",
"dest": false,
"context_default": "nil",
"context_type": "[]float64",
"parser": "(f.Value.(*Float64Slice)).Value(), error(nil)"
},
{
"name": "String",
"type": "string",
"context_default": "\"\"",
"parser": "f.Value.String(), error(nil)"
},
{
"name": "Path",
"type": "string",
"context_default": "\"\"",
"parser": "f.Value.String(), error(nil)"
},
{
"name": "StringSlice",
"type": "*StringSlice",
"dest": false,
"context_default": "nil",
"context_type": "[]string",
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)"
},
{
"name": "Uint64",
"type": "uint64",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)"
},
{
"name": "Uint",
"type": "uint",
"context_default": "0",
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)",
"parser_cast": "uint(parsed)"
}
]

828
flag.go
View File

@ -1,9 +1,9 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"reflect"
"regexp"
"runtime"
@ -22,50 +22,66 @@ var (
)
// GenerateCompletionFlag enables completion for all commands and subcommands
var GenerateCompletionFlag Flag = &BoolFlag{
Name: "generate-completion",
//var GenerateCompletionFlag Flag = &BoolFlag{
// Name: "generate-completion",
// Hidden: true,
//}
//
//func genCompName() string {
// names := GenerateCompletionFlag.Names()
// if len(names) == 0 {
// return "generate-completion"
// }
// return names[0]
//}
//
//// InitCompletionFlag generates completion code
//var InitCompletionFlag = &StringFlag{
// Name: "init-completion",
// Usage: "generate completion code. Value must be 'bash' or 'zsh'",
//}
// BashCompletionFlag enables bash-completion for all commands and subcommands
var BashCompletionFlag Flag = &BoolFlag{
Name: "generate-bash-completion",
Hidden: true,
}
func genCompName() string {
names := GenerateCompletionFlag.Names()
if len(names) == 0 {
return "generate-completion"
}
return names[0]
}
// InitCompletionFlag generates completion code
var InitCompletionFlag = &StringFlag{
Name: "init-completion",
Usage: "generate completion code. Value must be 'bash' or 'zsh'",
}
// VersionFlag prints the version for the application
var VersionFlag Flag = &BoolFlag{
Name: "version",
Aliases: []string{"v"},
Usage: "print the version",
Name: "version, v",
Usage: "print the version",
}
// HelpFlag prints the help for all commands and subcommands.
// Set to nil to disable the flag. The subcommand
// will still be added unless HideHelp is set to true.
var HelpFlag Flag = &BoolFlag{
Name: "help",
Aliases: []string{"h"},
Usage: "show help",
Name: "help, h",
Usage: "show help",
}
// FlagStringer converts a flag definition to a string. This is used by help
// to display a flag.
var FlagStringer FlagStringFunc = stringifyFlag
// Serializeder is used to circumvent the limitations of flag.FlagSet.Set
type Serializeder interface {
Serialized() string
// Serializer is used to circumvent the limitations of flag.FlagSet.Set
type Serializer interface {
Serialize() string
}
// FlagNamePrefixer converts a full flag name and its placeholder into the help
// message flag prefix. This is used by the default FlagStringer.
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
// FlagEnvHinter annotates flag help message with the environment variable
// details. This is used by the default FlagStringer.
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
// FlagFileHinter annotates flag help message with the environment variable
// details. This is used by the default FlagStringer.
var FlagFileHinter FlagFileHintFunc = withFileHint
// FlagsByName is a slice of Flag.
type FlagsByName []Flag
@ -74,12 +90,16 @@ func (f FlagsByName) Len() int {
}
func (f FlagsByName) Less(i, j int) bool {
//<<<<<<< HEAD
if len(f[j].Names()) == 0 {
return false
} else if len(f[i].Names()) == 0 {
return true
}
return f[i].Names()[0] < f[j].Names()[0]
//=======
// return lexicographicLess(f[i].GetName(), f[j].GetName())
//>>>>>>> master
}
func (f FlagsByName) Swap(i, j int) {
@ -92,711 +112,59 @@ func (f FlagsByName) Swap(i, j int) {
type Flag interface {
fmt.Stringer
// Apply Flag settings to the given flag set
Apply(*flag.FlagSet)
Apply(*flag.FlagSet) error
Names() []string
}
// errorableFlag is an interface that allows us to return errors during apply
// it allows flags defined in this library to return errors in a fashion backwards compatible
// TODO remove in v2 and modify the existing Flag interface to return errors
type errorableFlag interface {
// RequiredFlag is an interface that allows us to mark flags as required
// it allows flags required flags to be backwards compatible with the Flag interface
type RequiredFlag interface {
Flag
ApplyWithError(*flag.FlagSet) error
IsRequired() bool
}
// DocGenerationFlag is an interface that allows documentation generation for the flag
type DocGenerationFlag interface {
Flag
// TakesValue returns true of the flag takes a value, otherwise false
TakesValue() bool
// GetUsage returns the usage string for the flag
GetUsage() string
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string
}
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) {
set := flag.NewFlagSet(name, flag.ContinueOnError)
for _, f := range flags {
//TODO remove in v2 when errorableFlag is removed
if ef, ok := f.(errorableFlag); ok {
if err := ef.ApplyWithError(set); err != nil {
return nil, err
}
} else {
f.Apply(set)
if err := f.Apply(set); err != nil {
return nil, err
}
}
set.SetOutput(ioutil.Discard)
return set, nil
}
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
// Ignores parsing errors
func (f *GenericFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError takes the flagset and calls Set on the generic flag with the
// value provided by the user for parsing by the flag
func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error {
val := f.Value
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
val.Set(envVal)
break
}
}
func eachName(longName string, fn func(string)) {
parts := strings.Split(longName, ",")
for _, name := range parts {
name = strings.Trim(name, " ")
fn(name)
}
for _, name := range f.Names() {
set.Var(val, name, f.Usage)
}
return nil
}
// StringSlice wraps a []string to satisfy flag.Value
type StringSlice struct {
slice []string
hasBeenSet bool
}
// NewStringSlice creates a *StringSlice with default values
func NewStringSlice(defaults ...string) *StringSlice {
return &StringSlice{slice: append([]string{}, defaults...)}
}
// Set appends the string value to the list of values
func (f *StringSlice) Set(value string) error {
if !f.hasBeenSet {
f.slice = []string{}
f.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice)
f.hasBeenSet = true
return nil
}
f.slice = append(f.slice, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *StringSlice) String() string {
return fmt.Sprintf("%s", f.slice)
}
// Serialized allows StringSlice to fulfill Serializeder
func (f *StringSlice) Serialized() string {
jsonBytes, _ := json.Marshal(f.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of strings set by this flag
func (f *StringSlice) Value() []string {
return f.slice
}
// Get returns the slice of strings set by this flag
func (f *StringSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *StringSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewStringSlice()
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %q as string value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
if f.Value == nil {
f.Value = NewStringSlice()
}
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}
// IntSlice wraps an []int to satisfy flag.Value
type IntSlice struct {
slice []int
hasBeenSet bool
}
// NewIntSlice makes an *IntSlice with default values
func NewIntSlice(defaults ...int) *IntSlice {
return &IntSlice{slice: append([]int{}, defaults...)}
}
// NewInt64Slice makes an *Int64Slice with default values
func NewInt64Slice(defaults ...int64) *Int64Slice {
return &Int64Slice{slice: append([]int64{}, defaults...)}
}
// SetInt directly adds an integer to the list of values
func (i *IntSlice) SetInt(value int) {
if !i.hasBeenSet {
i.slice = []int{}
i.hasBeenSet = true
}
i.slice = append(i.slice, value)
}
// Set parses the value into an integer and appends it to the list of values
func (i *IntSlice) Set(value string) error {
if !i.hasBeenSet {
i.slice = []int{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
i.hasBeenSet = true
return nil
}
tmp, err := strconv.ParseInt(value, 0, 64)
if err != nil {
return err
}
i.slice = append(i.slice, int(tmp))
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string {
return fmt.Sprintf("%#v", f.slice)
}
// Serialized allows IntSlice to fulfill Serializeder
func (i *IntSlice) Serialized() string {
jsonBytes, _ := json.Marshal(i.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of ints set by this flag
func (i *IntSlice) Value() []int {
return i.slice
}
// Get returns the slice of ints set by this flag
func (f *IntSlice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *IntSliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewIntSlice()
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
if f.Value == nil {
f.Value = NewIntSlice()
}
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}
// Int64Slice is an opaque type for []int to satisfy flag.Value
type Int64Slice struct {
slice []int64
hasBeenSet bool
}
// Set parses the value into an integer and appends it to the list of values
func (f *Int64Slice) Set(value string) error {
if !f.hasBeenSet {
f.slice = []int64{}
f.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice)
f.hasBeenSet = true
return nil
}
tmp, err := strconv.ParseInt(value, 0, 64)
if err != nil {
return err
}
f.slice = append(f.slice, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Int64Slice) String() string {
return fmt.Sprintf("%#v", f.slice)
}
// Serialized allows Int64Slice to fulfill Serializeder
func (f *Int64Slice) Serialized() string {
jsonBytes, _ := json.Marshal(f.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of ints set by this flag
func (f *Int64Slice) Value() []int64 {
return f.slice
}
// Get returns the slice of ints set by this flag
func (f *Int64Slice) Get() interface{} {
return *f
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewInt64Slice()
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
if err := newVal.Set(s); err != nil {
return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", envVal, f.Name, err)
}
}
f.Value = newVal
break
}
}
}
if f.Value == nil {
f.Value = NewInt64Slice()
}
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *BoolFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
if envVal == "" {
f.Value = false
break
}
envValBool, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("could not parse %q as bool value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValBool
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.BoolVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Bool(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *StringFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.String(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *PathFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *PathFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
f.Value = envVal
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.String(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *IntFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = int(envValInt)
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Int(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *Int64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseInt(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %q as int value for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValInt
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
return nil
}
set.Int64(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *UintFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %q as uint value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint(envValInt)
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
return nil
}
set.Uint(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *Uint64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
envValInt, err := strconv.ParseUint(envVal, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = uint64(envValInt)
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
return nil
}
set.Uint64(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *DurationFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
envValDuration, err := time.ParseDuration(envVal)
if err != nil {
return fmt.Errorf("could not parse %q as duration for flag %s: %s", envVal, f.Name, err)
}
f.Value = envValDuration
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Duration(name, f.Value, f.Usage)
}
return nil
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *Float64Flag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
envValFloat, err := strconv.ParseFloat(envVal, 10)
if err != nil {
return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", envVal, f.Name, err)
}
f.Value = float64(envValFloat)
break
}
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
continue
}
set.Float64(name, f.Value, f.Usage)
}
return nil
}
// NewFloat64Slice makes a *Float64Slice with default values
func NewFloat64Slice(defaults ...float64) *Float64Slice {
return &Float64Slice{slice: append([]float64{}, defaults...)}
}
// Float64Slice is an opaque type for []float64 to satisfy flag.Value
type Float64Slice struct {
slice []float64
hasBeenSet bool
}
// Set parses the value into a float64 and appends it to the list of values
func (f *Float64Slice) Set(value string) error {
if !f.hasBeenSet {
f.slice = []float64{}
f.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice)
f.hasBeenSet = true
return nil
}
tmp, err := strconv.ParseFloat(value, 64)
if err != nil {
return err
}
f.slice = append(f.slice, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Float64Slice) String() string {
return fmt.Sprintf("%#v", f.slice)
}
// Serialized allows Float64Slice to fulfill Serializeder
func (f *Float64Slice) Serialized() string {
jsonBytes, _ := json.Marshal(f.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of float64s set by this flag
func (f *Float64Slice) Value() []float64 {
return f.slice
}
// Apply populates the flag given the flag set and environment
// Ignores errors
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) {
f.ApplyWithError(set)
}
// ApplyWithError populates the flag given the flag set and environment
func (f *Float64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
if f.EnvVars != nil {
for _, envVar := range f.EnvVars {
if envVal, ok := syscall.Getenv(envVar); ok {
newVal := NewFloat64Slice()
for _, s := range strings.Split(envVal, ",") {
s = strings.TrimSpace(s)
err := newVal.Set(s)
if err != nil {
fmt.Fprintf(ErrWriter, err.Error())
}
}
f.Value = newVal
break
}
}
}
if f.Value == nil {
f.Value = NewFloat64Slice()
}
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}
func visibleFlags(fl []Flag) []Flag {
visible := []Flag{}
for _, flag := range fl {
field := flagValue(flag).FieldByName("Hidden")
var visible []Flag
for _, f := range fl {
field := flagValue(f).FieldByName("Hidden")
if !field.IsValid() || !field.Bool() {
visible = append(visible, flag)
visible = append(visible, f)
}
}
return visible
@ -858,6 +226,7 @@ func withEnvHint(envVars []string, str string) string {
suffix = "%"
sep = "%, %"
}
envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
}
return str + envText
@ -902,6 +271,14 @@ func flagStringField(f Flag, name string) string {
return ""
}
func withFileHint(filePath, str string) string {
fileText := ""
if filePath != "" {
fileText = fmt.Sprintf(" [%s]", filePath)
}
return str + fileText
}
func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f)
for fv.Kind() == reflect.Ptr {
@ -956,14 +333,14 @@ func stringifyFlag(f Flag) string {
placeholder = defaultPlaceholder
}
usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString))
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
return withEnvHint(flagStringSliceField(f, "EnvVars"),
fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault))
}
func stringifyIntSliceFlag(f *IntSliceFlag) string {
defaultVals := []string{}
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
@ -974,10 +351,10 @@ func stringifyIntSliceFlag(f *IntSliceFlag) string {
}
func stringifyInt64SliceFlag(f *Int64SliceFlag) string {
defaultVals := []string{}
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, fmt.Sprintf("%d", i))
defaultVals = append(defaultVals, strconv.FormatInt(i, 10))
}
}
@ -985,7 +362,8 @@ func stringifyInt64SliceFlag(f *Int64SliceFlag) string {
}
func stringifyFloat64SliceFlag(f *Float64SliceFlag) string {
defaultVals := []string{}
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, i := range f.Value.Value() {
defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), "."))
@ -996,11 +374,12 @@ func stringifyFloat64SliceFlag(f *Float64SliceFlag) string {
}
func stringifyStringSliceFlag(f *StringSliceFlag) string {
defaultVals := []string{}
var defaultVals []string
if f.Value != nil && len(f.Value.Value()) > 0 {
for _, s := range f.Value.Value() {
if len(s) > 0 {
defaultVals = append(defaultVals, fmt.Sprintf("%q", s))
defaultVals = append(defaultVals, strconv.Quote(s))
}
}
}
@ -1032,3 +411,18 @@ func hasFlag(flags []Flag, fl Flag) bool {
return false
}
func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) {
for _, envVar := range envVars {
envVar = strings.TrimSpace(envVar)
if val, ok := syscall.Getenv(envVar); ok {
return val, true
}
}
for _, fileVar := range strings.Split(filePath, ",") {
if data, err := ioutil.ReadFile(fileVar); err == nil {
return string(data), true
}
}
return "", false
}

107
flag_bool.go Normal file
View File

@ -0,0 +1,107 @@
package cli
import (
"flag"
"fmt"
"strconv"
)
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value bool
DefaultText string
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *BoolFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *BoolFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *BoolFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *BoolFlag) TakesValue() bool {
return false
}
// GetUsage returns the usage string for the flag
func (f *BoolFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *BoolFlag) GetValue() string {
return ""
}
// Apply populates the flag given the flag set and environment
func (f *BoolFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valBool, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", val, f.Name, err)
}
f.Value = valBool
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.BoolVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Bool(name, f.Value, f.Usage)
}
return nil
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
// GlobalBool looks up the value of a global BoolFlag, returns
// false if not found
//func (c *Context) GlobalBool(name string) bool {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupBool(name, fs)
// }
// return false
//}
// TODO: Fix Duplicate
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}

104
flag_duration.go Normal file
View File

@ -0,0 +1,104 @@
package cli
import (
"flag"
"fmt"
"time"
)
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value time.Duration
DefaultText string
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *DurationFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *DurationFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *DurationFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *DurationFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *DurationFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *DurationFlag) GetValue() string {
return f.Value.String()
}
// Apply populates the flag given the flag set and environment
func (f *DurationFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != ""{
valDuration, err := time.ParseDuration(val)
if err != nil {
return fmt.Errorf("could not parse %s as duration value for flag %s: %s", val, f.Name, err)
}
f.Value = valDuration
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.DurationVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Duration(name, f.Value, f.Usage)
}
return nil
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
// GlobalDuration looks up the value of a global DurationFlag, returns
// 0 if not found
//func (c *Context) GlobalDuration(name string) time.Duration {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupDuration(name, fs)
// }
// return 0
//}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}

106
flag_float64.go Normal file
View File

@ -0,0 +1,106 @@
package cli
import (
"flag"
"fmt"
"strconv"
)
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value float64
DefaultText string
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Float64Flag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *Float64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *Float64Flag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *Float64Flag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Float64Flag) GetValue() string {
return fmt.Sprintf("%f", f.Value)
}
// Apply populates the flag given the flag set and environment
func (f *Float64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valFloat, err := strconv.ParseFloat(val, 10)
if err != nil {
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", val, f.Name, err)
}
f.Value = valFloat
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Float64Var(f.Destination, name, f.Value, f.Usage)
continue
}
set.Float64(name, f.Value, f.Usage)
}
return nil
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
// GlobalFloat64 looks up the value of a global Float64Flag, returns
// 0 if not found
//func (c *Context) GlobalFloat64(name string) float64 {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupFloat64(name, fs)
// }
// return 0
//}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}

164
flag_float64_slice.go Normal file
View File

@ -0,0 +1,164 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"strconv"
"strings"
)
// Float64Slice wraps []float64 to satisfy flag.Value
type Float64Slice struct {
slice []float64
hasBeenSet bool
}
// NewFloat64Slice makes a *Float64Slice with default values
func NewFloat64Slice(defaults ...float64) *Float64Slice {
return &Float64Slice{slice: append([]float64{}, defaults...)}
}
// Set parses the value into a float64 and appends it to the list of values
func (f *Float64Slice) Set(value string) error {
if !f.hasBeenSet {
f.slice = []float64{}
f.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice)
f.hasBeenSet = true
return nil
}
tmp, err := strconv.ParseFloat(value, 64)
if err != nil {
return err
}
f.slice = append(f.slice, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (f *Float64Slice) String() string {
return fmt.Sprintf("%#v", f.slice)
}
// Serialize allows Float64Slice to fulfill Serializer
func (f *Float64Slice) Serialize() string {
jsonBytes, _ := json.Marshal(f.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of float64s set by this flag
func (f *Float64Slice) Value() []float64 {
return f.slice
}
// Get returns the slice of float64s set by this flag
func (f *Float64Slice) Get() interface{} {
return *f
}
// Float64SliceFlag is a flag with type *Float64Slice
type Float64SliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value *Float64Slice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64SliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Float64SliceFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *Float64SliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true if the flag takes a value, otherwise false
func (f *Float64SliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *Float64SliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Float64SliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = &Float64Slice{}
for _, s := range strings.Split(val, ",") {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
return fmt.Errorf("could not parse %s as float64 slice value for flag %s: %s", f.Value, f.Name, err)
}
}
}
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &Float64Slice{}
}
set.Var(f.Value, name, f.Usage)
}
return nil
}
// Float64Slice looks up the value of a local Float64SliceFlag, returns
// nil if not found
func (c *Context) Float64Slice(name string) []float64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupFloat64Slice(name, fs)
}
return nil
}
// GlobalFloat64Slice looks up the value of a global Float64SliceFlag, returns
// nil if not found
//func (c *Context) GlobalFloat64Slice(name string) []int {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupFloat64Slice(name, fs)
// }
// return nil
//}
func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}

View File

@ -1,629 +0,0 @@
package cli
import (
"flag"
"strconv"
"time"
)
// WARNING: This file is generated!
// BoolFlag is a flag with type bool
type BoolFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value bool
DefaultText string
Destination *bool
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *BoolFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *BoolFlag) Names() []string {
return flagNames(f)
}
// Bool looks up the value of a local BoolFlag, returns
// false if not found
func (c *Context) Bool(name string) bool {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupBool(name, fs)
}
return false
}
func lookupBool(name string, set *flag.FlagSet) bool {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseBool(f.Value.String())
if err != nil {
return false
}
return parsed
}
return false
}
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
type DurationFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value time.Duration
DefaultText string
Destination *time.Duration
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *DurationFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *DurationFlag) Names() []string {
return flagNames(f)
}
// Duration looks up the value of a local DurationFlag, returns
// 0 if not found
func (c *Context) Duration(name string) time.Duration {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupDuration(name, fs)
}
return 0
}
func lookupDuration(name string, set *flag.FlagSet) time.Duration {
f := set.Lookup(name)
if f != nil {
parsed, err := time.ParseDuration(f.Value.String())
if err != nil {
return 0
}
return parsed
}
return 0
}
// Float64Flag is a flag with type float64
type Float64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value float64
DefaultText string
Destination *float64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Float64Flag) Names() []string {
return flagNames(f)
}
// Float64 looks up the value of a local Float64Flag, returns
// 0 if not found
func (c *Context) Float64(name string) float64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupFloat64(name, fs)
}
return 0
}
func lookupFloat64(name string, set *flag.FlagSet) float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseFloat(f.Value.String(), 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value Generic
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *GenericFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *GenericFlag) Names() []string {
return flagNames(f)
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupGeneric(name, fs)
}
return nil
}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value int64
DefaultText string
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Int64Flag) Names() []string {
return flagNames(f)
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupInt64(name, fs)
}
return 0
}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value int
DefaultText string
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *IntFlag) Names() []string {
return flagNames(f)
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupInt(name, fs)
}
return 0
}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value *IntSlice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntSliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *IntSliceFlag) Names() []string {
return flagNames(f)
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupIntSlice(name, fs)
}
return nil
}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value *Int64Slice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64SliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Int64SliceFlag) Names() []string {
return flagNames(f)
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Float64SliceFlag is a flag with type *Float64Slice
type Float64SliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value *Float64Slice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Float64SliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Float64SliceFlag) Names() []string {
return flagNames(f)
}
// Float64Slice looks up the value of a local Float64SliceFlag, returns
// nil if not found
func (c *Context) Float64Slice(name string) []float64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupFloat64Slice(name, fs)
}
return nil
}
func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value string
DefaultText string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *StringFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *StringFlag) Names() []string {
return flagNames(f)
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupString(name, fs)
}
return ""
}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
// PathFlag is a flag with type string
type PathFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value string
DefaultText string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *PathFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *PathFlag) Names() []string {
return flagNames(f)
}
// Path looks up the value of a local PathFlag, returns
// "" if not found
func (c *Context) Path(name string) string {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupPath(name, fs)
}
return ""
}
func lookupPath(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value *StringSlice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *StringSliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *StringSliceFlag) Names() []string {
return flagNames(f)
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupStringSlice(name, fs)
}
return nil
}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value uint64
DefaultText string
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Uint64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Uint64Flag) Names() []string {
return flagNames(f)
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupUint64(name, fs)
}
return 0
}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value uint
DefaultText string
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *UintFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *UintFlag) Names() []string {
return flagNames(f)
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupUint(name, fs)
}
return 0
}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}

104
flag_generic.go Normal file
View File

@ -0,0 +1,104 @@
package cli
import (
"flag"
"fmt"
)
// Generic is a generic parseable type identified by a specific flag
type Generic interface {
Set(value string) error
String() string
}
// GenericFlag is a flag with type Generic
type GenericFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value Generic
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *GenericFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *GenericFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *GenericFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *GenericFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *GenericFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *GenericFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply takes the flagset and calls Set on the generic flag with the value
// provided by the user for parsing by the flag
func (f GenericFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if err := f.Value.Set(val); err != nil {
return fmt.Errorf("could not parse %s as value for flag %s: %s", val, f.Name, err)
}
}
for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}
// Generic looks up the value of a local GenericFlag, returns
// nil if not found
func (c *Context) Generic(name string) interface{} {
return lookupGeneric(name, c.flagSet)
}
// GlobalGeneric looks up the value of a global GenericFlag, returns
// nil if not found
//func (c *Context) GlobalGeneric(name string) interface{} {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupGeneric(name, fs)
// }
// return nil
//}
func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value, error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}

103
flag_int.go Normal file
View File

@ -0,0 +1,103 @@
package cli
import (
"flag"
"fmt"
"strconv"
)
// IntFlag is a flag with type int
type IntFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value int
DefaultText string
Destination *int
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *IntFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *IntFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *IntFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *IntFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *IntFlag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Apply populates the flag given the flag set and environment
func (f *IntFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valInt, err := strconv.ParseInt(val, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", val, f.Name, err)
}
f.Value = int(valInt)
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.IntVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Int(name, f.Value, f.Usage)
}
return nil
}
// Int looks up the value of a local IntFlag, returns
// 0 if not found
func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet)
}
// GlobalInt looks up the value of a global IntFlag, returns
// 0 if not found
//func (c *Context) GlobalInt(name string) int {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupInt(name, fs)
// }
// return 0
//}
func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return int(parsed)
}
return 0
}

103
flag_int64.go Normal file
View File

@ -0,0 +1,103 @@
package cli
import (
"flag"
"fmt"
"strconv"
)
// Int64Flag is a flag with type int64
type Int64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value int64
DefaultText string
Destination *int64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Int64Flag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *Int64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *Int64Flag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *Int64Flag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Int64Flag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Apply populates the flag given the flag set and environment
func (f *Int64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valInt, err := strconv.ParseInt(val, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as int value for flag %s: %s", val, f.Name, err)
}
f.Value = valInt
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Int64Var(f.Destination, name, f.Value, f.Usage)
continue
}
set.Int64(name, f.Value, f.Usage)
}
return nil
}
// Int64 looks up the value of a local Int64Flag, returns
// 0 if not found
func (c *Context) Int64(name string) int64 {
return lookupInt64(name, c.flagSet)
}
// GlobalInt64 looks up the value of a global Int64Flag, returns
// 0 if not found
//func (c *Context) GlobalInt64(name string) int64 {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupInt64(name, fs)
// }
// return 0
//}
func lookupInt64(name string, set *flag.FlagSet) int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}

164
flag_int64_slice.go Normal file
View File

@ -0,0 +1,164 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"strconv"
"strings"
)
// Int64Slice wraps []int64 to satisfy flag.Value
type Int64Slice struct {
slice []int64
hasBeenSet bool
}
// NewInt64Slice makes an *Int64Slice with default values
func NewInt64Slice(defaults ...int64) *Int64Slice {
return &Int64Slice{slice: append([]int64{}, defaults...)}
}
// Set parses the value into an integer and appends it to the list of values
func (i *Int64Slice) Set(value string) error {
if !i.hasBeenSet {
i.slice = []int64{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
i.hasBeenSet = true
return nil
}
tmp, err := strconv.ParseInt(value, 0, 64)
if err != nil {
return err
}
i.slice = append(i.slice, tmp)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (i *Int64Slice) String() string {
return fmt.Sprintf("%#v", i.slice)
}
// Serialize allows Int64Slice to fulfill Serializer
func (i *Int64Slice) Serialize() string {
jsonBytes, _ := json.Marshal(i.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of ints set by this flag
func (i *Int64Slice) Value() []int64 {
return i.slice
}
// Get returns the slice of ints set by this flag
func (i *Int64Slice) Get() interface{} {
return *i
}
// Int64SliceFlag is a flag with type *Int64Slice
type Int64SliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value *Int64Slice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Int64SliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Int64SliceFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *Int64SliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *Int64SliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f Int64SliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Int64SliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = &Int64Slice{}
for _, s := range strings.Split(val, ",") {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", f.Value, f.Name, err)
}
}
}
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &Int64Slice{}
}
set.Var(f.Value, name, f.Usage)
}
return nil
}
// Int64Slice looks up the value of a local Int64SliceFlag, returns
// nil if not found
func (c *Context) Int64Slice(name string) []int64 {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupInt64Slice(name, fs)
}
return nil
}
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
// nil if not found
//func (c *Context) GlobalInt64Slice(name string) []int {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupInt64Slice(name, fs)
// }
// return nil
//}
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}

175
flag_int_slice.go Normal file
View File

@ -0,0 +1,175 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"strconv"
"strings"
)
// IntSlice wraps []int to satisfy flag.Value
type IntSlice struct {
slice []int
hasBeenSet bool
}
// NewIntSlice makes an *IntSlice with default values
func NewIntSlice(defaults ...int) *IntSlice {
return &IntSlice{slice: append([]int{}, defaults...)}
}
// TODO: Consistently have specific Set function for Int64 and Float64 ?
// SetInt directly adds an integer to the list of values
func (i *IntSlice) SetInt(value int) {
if !i.hasBeenSet {
i.slice = []int{}
i.hasBeenSet = true
}
i.slice = append(i.slice, value)
}
// Set parses the value into an integer and appends it to the list of values
func (i *IntSlice) Set(value string) error {
if !i.hasBeenSet {
i.slice = []int{}
i.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
i.hasBeenSet = true
return nil
}
tmp, err := strconv.ParseInt(value, 0, 64)
if err != nil {
return err
}
i.slice = append(i.slice, int(tmp))
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (i *IntSlice) String() string {
return fmt.Sprintf("%#v", i.slice)
}
// Serialize allows IntSlice to fulfill Serializer
func (i *IntSlice) Serialize() string {
jsonBytes, _ := json.Marshal(i.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of ints set by this flag
func (i *IntSlice) Value() []int {
return i.slice
}
// Get returns the slice of ints set by this flag
func (i *IntSlice) Get() interface{} {
return *i
}
// IntSliceFlag is a flag with type *IntSlice
type IntSliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value *IntSlice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *IntSliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *IntSliceFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *IntSliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *IntSliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f IntSliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *IntSliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
func (f *IntSliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = &IntSlice{}
for _, s := range strings.Split(val, ",") {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", val, f.Name, err)
}
}
}
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &IntSlice{}
}
set.Var(f.Value, name, f.Usage)
}
return nil
}
// IntSlice looks up the value of a local IntSliceFlag, returns
// nil if not found
func (c *Context) IntSlice(name string) []int {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupIntSlice(name, c.flagSet)
}
return nil
}
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
// nil if not found
//func (c *Context) GlobalIntSlice(name string) []int {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupIntSlice(name, fs)
// }
// return nil
//}
func lookupIntSlice(name string, set *flag.FlagSet) []int {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}

85
flag_path.go Normal file
View File

@ -0,0 +1,85 @@
package cli
import "flag"
type PathFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
EnvVar string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value string
DefaultText string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *PathFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *PathFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *PathFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *PathFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *PathFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *PathFlag) GetValue() string {
return f.Value
}
// Apply populates the flag given the flag set and environment
func (f *PathFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = val
}
for _, name := range f.Names() {
if f.Destination != nil {
set.StringVar(f.Destination, name, f.Value, f.Usage)
return
}
set.String(name, f.Value, f.Usage)
}
return nil
}
// String looks up the value of a local PathFlag, returns
// "" if not found
func (c *Context) Path(name string) string {
return lookupPath(name, c.flagSet)
}
func lookupPath(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}

95
flag_string.go Normal file
View File

@ -0,0 +1,95 @@
package cli
import "flag"
// StringFlag is a flag with type string
type StringFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
EnvVar string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value string
DefaultText string
Destination *string
}
// String returns a readable representation of this value
// (for usage defaults)
func (s *StringFlag) String() string {
return FlagStringer(s)
}
// Names returns the names of the flag
func (s *StringFlag) Names() []string {
return flagNames(s)
}
// IsRequired returns whether or not the flag is required
func (s *StringFlag) IsRequired() bool {
return s.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (s *StringFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (s *StringFlag) GetUsage() string {
return s.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (s *StringFlag) GetValue() string {
return s.Value
}
// Apply populates the flag given the flag set and environment
func (s *StringFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(s.EnvVars, s.FilePath); ok {
s.Value = val
}
for _, name := range s.Names() {
if s.Destination != nil {
set.StringVar(s.Destination, name, s.Value, s.Usage)
return
}
set.String(name, s.Value, s.Usage)
}
return nil
}
// String looks up the value of a local StringFlag, returns
// "" if not found
func (c *Context) String(name string) string {
return lookupString(name, c.flagSet)
}
// GlobalString looks up the value of a global StringFlag, returns
// "" if not found
//func (c *Context) GlobalString(name string) string {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupPath(name, fs)
// }
// return ""
//}
func lookupString(name string, set *flag.FlagSet) string {
f := set.Lookup(name)
if f != nil {
parsed, err := f.Value.String(), error(nil)
if err != nil {
return ""
}
return parsed
}
return ""
}

155
flag_string_slice.go Normal file
View File

@ -0,0 +1,155 @@
package cli
import (
"encoding/json"
"flag"
"fmt"
"strings"
)
// StringSlice wraps a []string to satisfy flag.Value
type StringSlice struct {
slice []string
hasBeenSet bool
}
// NewStringSlice creates a *StringSlice with default values
func NewStringSlice(defaults ...string) *StringSlice {
return &StringSlice{slice: append([]string{}, defaults...)}
}
// Set appends the string value to the list of values
func (s *StringSlice) Set(value string) error {
if !s.hasBeenSet {
s.slice = []string{}
s.hasBeenSet = true
}
if strings.HasPrefix(value, slPfx) {
// Deserializing assumes overwrite
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &s.slice)
s.hasBeenSet = true
return nil
}
s.slice = append(s.slice, value)
return nil
}
// String returns a readable representation of this value (for usage defaults)
func (s *StringSlice) String() string {
return fmt.Sprintf("%s", s.slice)
}
// Serialize allows StringSlice to fulfill Serializer
func (s *StringSlice) Serialize() string {
jsonBytes, _ := json.Marshal(s.slice)
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
}
// Value returns the slice of strings set by this flag
func (s *StringSlice) Value() []string {
return s.slice
}
// Get returns the slice of strings set by this flag
func (s *StringSlice) Get() interface{} {
return *s
}
// StringSliceFlag is a flag with type *StringSlice
type StringSliceFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
TakesFile bool
Value *StringSlice
DefaultText string
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *StringSliceFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *StringSliceFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *StringSliceFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *StringSliceFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *StringSliceFlag) GetUsage() string {
return f.Usage
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *StringSliceFlag) GetValue() string {
if f.Value != nil {
return f.Value.String()
}
return ""
}
// Apply populates the flag given the flag set and environment
func (f *StringSliceFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
f.Value = &StringSlice{}
for _, s := range strings.Split(val, ",") {
if err := f.Value.Set(strings.TrimSpace(s)); err != nil {
return fmt.Errorf("could not parse %s as string value for flag %s: %s", val, f.Name, err)
}
}
}
for _, name := range f.Names() {
if f.Value == nil {
f.Value = &StringSlice{}
}
set.Var(f.Value, name, f.Usage)
}
return nil
}
// StringSlice looks up the value of a local StringSliceFlag, returns
// nil if not found
func (c *Context) StringSlice(name string) []string {
return lookupStringSlice(name, c.flagSet)
}
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
// nil if not found
//func (c *Context) GlobalStringSlice(name string) []string {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupStringSlice(name, fs)
// }
// return nil
//}
func lookupStringSlice(name string, set *flag.FlagSet) []string {
f := set.Lookup(name)
if f != nil {
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil)
if err != nil {
return nil
}
return parsed
}
return nil
}

File diff suppressed because it is too large Load Diff

104
flag_uint.go Normal file
View File

@ -0,0 +1,104 @@
package cli
import (
"flag"
"fmt"
"strconv"
)
// UintFlag is a flag with type uint
type UintFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value uint
DefaultText string
Destination *uint
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *UintFlag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *UintFlag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *UintFlag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *UintFlag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *UintFlag) GetUsage() string {
return f.Usage
}
// Apply populates the flag given the flag set and environment
func (f *UintFlag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valInt, err := strconv.ParseUint(val, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", val, f.Name, err)
}
f.Value = uint(valInt)
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.UintVar(f.Destination, name, f.Value, f.Usage)
continue
}
set.Uint(name, f.Value, f.Usage)
}
return nil
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *UintFlag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Uint looks up the value of a local UintFlag, returns
// 0 if not found
func (c *Context) Uint(name string) uint {
return lookupUint(name, c.flagSet)
}
// GlobalUint looks up the value of a global UintFlag, returns
// 0 if not found
//func (c *Context) GlobalUint(name string) uint {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupUint(name, fs)
// }
// return 0
//}
func lookupUint(name string, set *flag.FlagSet) uint {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return uint(parsed)
}
return 0
}

104
flag_uint64.go Normal file
View File

@ -0,0 +1,104 @@
package cli
import (
"flag"
"fmt"
"strconv"
)
// Uint64Flag is a flag with type uint64
type Uint64Flag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Value uint64
DefaultText string
Destination *uint64
}
// String returns a readable representation of this value
// (for usage defaults)
func (f *Uint64Flag) String() string {
return FlagStringer(f)
}
// Names returns the names of the flag
func (f *Uint64Flag) Names() []string {
return flagNames(f)
}
// IsRequired returns whether or not the flag is required
func (f *Uint64Flag) IsRequired() bool {
return f.Required
}
// TakesValue returns true of the flag takes a value, otherwise false
func (f *Uint64Flag) TakesValue() bool {
return true
}
// GetUsage returns the usage string for the flag
func (f *Uint64Flag) GetUsage() string {
return f.Usage
}
// Apply populates the flag given the flag set and environment
func (f *Uint64Flag) Apply(set *flag.FlagSet) error {
if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if val != "" {
valInt, err := strconv.ParseUint(val, 0, 64)
if err != nil {
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", val, f.Name, err)
}
f.Value = valInt
}
}
for _, name := range f.Names() {
if f.Destination != nil {
set.Uint64Var(f.Destination, name, f.Value, f.Usage)
continue
}
set.Uint64(name, f.Value, f.Usage)
}
return nil
}
// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *Uint64Flag) GetValue() string {
return fmt.Sprintf("%d", f.Value)
}
// Uint64 looks up the value of a local Uint64Flag, returns
// 0 if not found
func (c *Context) Uint64(name string) uint64 {
return lookupUint64(name, c.flagSet)
}
// GlobalUint64 looks up the value of a global Uint64Flag, returns
// 0 if not found
//func (c *Context) GlobalUint64(name string) uint64 {
// if fs := lookupGlobalFlagSet(name, c); fs != nil {
// return lookupUint64(name, fs)
// }
// return 0
//}
func lookupUint64(name string, set *flag.FlagSet) uint64 {
f := set.Lookup(name)
if f != nil {
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64)
if err != nil {
return 0
}
return parsed
}
return 0
}

View File

@ -1,7 +1,7 @@
package cli
// ShellCompleteFunc is an action to execute when the shell completion flag is set
type ShellCompleteFunc func(*Context)
// BashCompleteFunc is an action to execute when the shell completion flag is set
type BashCompleteFunc func(*Context)
// BeforeFunc is an action to execute before any subcommands are run, but after
// the context is ready if a non-nil error is returned, no subcommands are run
@ -23,6 +23,22 @@ type CommandNotFoundFunc func(*Context, string)
// is displayed and the execution is interrupted.
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error
// ExitErrHandlerFunc is executed if provided in order to handle ExitError values
// returned by Actions and Before/After functions.
type ExitErrHandlerFunc func(context *Context, err error)
// FlagStringFunc is used by the help generation to display a flag, which is
// expected to be a single line.
type FlagStringFunc func(Flag) string
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
// text for a flag's full name.
type FlagNamePrefixFunc func(fullName []string, placeholder string) string
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
// with the environment variable details.
type FlagEnvHintFunc func(envVars []string, str string) string
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
// with the file path details.
type FlagFileHintFunc func(filePath, str string) string

View File

@ -1,255 +0,0 @@
#!/usr/bin/env python
"""
The flag types that ship with the cli library have many things in common, and
so we can take advantage of the `go generate` command to create much of the
source code from a list of definitions. These definitions attempt to cover
the parts that vary between flag types, and should evolve as needed.
An example of the minimum definition needed is:
{
"name": "SomeType",
"type": "sometype",
"context_default": "nil"
}
In this example, the code generated for the `cli` package will include a type
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`.
Fetching values by name via `*cli.Context` will default to a value of `nil`.
A more complete, albeit somewhat redundant, example showing all available
definition keys is:
{
"name": "VeryMuchType",
"type": "*VeryMuchType",
"value": true,
"dest": false,
"doctail": " which really only wraps a []float64, oh well!",
"context_type": "[]float64",
"context_default": "nil",
"parser": "parseVeryMuchType(f.Value.String())",
"parser_cast": "[]float64(parsed)"
}
The meaning of each field is as follows:
name (string) - The type "name", which will be suffixed with
`Flag` when generating the type definition
for `cli` and the wrapper type for `altsrc`
type (string) - The type that the generated `Flag` type for
`cli` is expected to "contain" as its `.Value`
member
value (bool) - Should the generated `cli` type have a `Value`
member?
dest (bool) - Should the generated `cli` type support a
destination pointer?
doctail (string) - Additional docs for the `cli` flag type comment
context_type (string) - The literal type used in the `*cli.Context`
reader func signature
context_default (string) - The literal value used as the default by the
`*cli.Context` reader funcs when no value is
present
parser (string) - Literal code used to parse the flag `f`,
expected to have a return signature of
(value, error)
parser_cast (string) - Literal code used to cast the `parsed` value
returned from the `parser` code
"""
from __future__ import print_function, unicode_literals
import argparse
import json
import os
import subprocess
import sys
import tempfile
import textwrap
_PY3 = sys.version_info.major == 3
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter,
argparse.RawDescriptionHelpFormatter):
pass
def main(sysargs=sys.argv[:]):
parser = argparse.ArgumentParser(
description='Generate flag type code!',
formatter_class=_FancyFormatter)
parser.add_argument(
'package',
type=str, default='cli', choices=_WRITEFUNCS.keys(),
help='Package for which flag types will be generated'
)
parser.add_argument(
'-i', '--in-json',
type=argparse.FileType('r'),
default=sys.stdin,
help='Input JSON file which defines each type to be generated'
)
parser.add_argument(
'-o', '--out-go',
type=argparse.FileType('w'),
default=sys.stdout,
help='Output file/stream to which generated source will be written'
)
parser.epilog = __doc__
args = parser.parse_args(sysargs[1:])
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json)
return 0
def _generate_flag_types(writefunc, output_go, input_json):
types = json.load(input_json)
tmp = _get_named_tmp_go()
writefunc(tmp, types)
tmp.close()
new_content = subprocess.check_output(
['goimports', tmp.name]
).decode('utf-8')
print(new_content, file=output_go, end='')
output_go.flush()
os.remove(tmp.name)
def _get_named_tmp_go():
tmp_args = dict(suffix='.go', mode='w', delete=False)
if _PY3:
tmp_args['encoding'] = 'utf-8'
return tempfile.NamedTemporaryFile(**tmp_args)
def _set_typedef_defaults(typedef):
typedef.setdefault('doctail', '')
typedef.setdefault('context_type', typedef['type'])
typedef.setdefault('dest', True)
typedef.setdefault('parser', 'f.Value, error(nil)')
typedef.setdefault('parser_cast', 'parsed')
def _write_cli_flag_types(outfile, types):
_fwrite(outfile, """\
package cli
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is a flag with type {type}{doctail}
type {name}Flag struct {{
Name string
Aliases []string
Usage string
EnvVars []string
Hidden bool
Value {type}
DefaultText string
""".format(**typedef))
if typedef['dest']:
_fwrite(outfile, """\
Destination *{type}
""".format(**typedef))
_fwrite(outfile, "\n}\n\n")
_fwrite(outfile, """\
// String returns a readable representation of this value
// (for usage defaults)
func (f *{name}Flag) String() string {{
return FlagStringer(f)
}}
// Names returns the names of the flag
func (f *{name}Flag) Names() []string {{
return flagNames(f)
}}
// {name} looks up the value of a local {name}Flag, returns
// {context_default} if not found
func (c *Context) {name}(name string) {context_type} {{
if fs := lookupFlagSet(name, c); fs != nil {{
return lookup{name}(name, fs)
}}
return {context_default}
}}
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{
f := set.Lookup(name)
if f != nil {{
parsed, err := {parser}
if err != nil {{
return {context_default}
}}
return {parser_cast}
}}
return {context_default}
}}
""".format(**typedef))
def _write_altsrc_flag_types(outfile, types):
_fwrite(outfile, """\
package altsrc
import "gopkg.in/urfave/cli.v2"
// WARNING: This file is generated!
""")
for typedef in types:
_set_typedef_defaults(typedef)
_fwrite(outfile, """\
// {name}Flag is the flag type that wraps cli.{name}Flag to allow
// for other values to be specified
type {name}Flag struct {{
*cli.{name}Flag
set *flag.FlagSet
}}
// New{name}Flag creates a new {name}Flag
func New{name}Flag(fl *cli.{name}Flag) *{name}Flag {{
return &{name}Flag{{{name}Flag: fl, set: nil}}
}}
// Apply saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.Apply
func (f *{name}Flag) Apply(set *flag.FlagSet) {{
f.set = set
f.{name}Flag.Apply(set)
}}
// ApplyWithError saves the flagSet for later usage calls, then calls the
// wrapped {name}Flag.ApplyWithError
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{
f.set = set
return f.{name}Flag.ApplyWithError(set)
}}
""".format(**typedef))
def _fwrite(outfile, text):
print(textwrap.dedent(text), end=None, file=outfile)
_WRITEFUNCS = {
'cli': _write_cli_flag_types,
'altsrc': _write_altsrc_flag_types
}
if __name__ == '__main__':
sys.exit(main())

9
go.mod Normal file
View File

@ -0,0 +1,9 @@
module github.com/urfave/cli/v2
go 1.11
require (
github.com/BurntSushi/toml v0.3.1
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d
gopkg.in/yaml.v2 v2.2.2
)

14
go.sum Normal file
View File

@ -0,0 +1,14 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

126
help.go
View File

@ -7,6 +7,7 @@ import (
"strings"
"text/tabwriter"
"text/template"
"unicode/utf8"
)
// AppHelpTemplate is the text template for the Default help topic.
@ -89,7 +90,7 @@ var helpCommand = &Command{
return ShowCommandHelp(c, args.First())
}
ShowAppHelp(c)
_ = ShowAppHelp(c)
return nil
},
}
@ -129,7 +130,7 @@ var VersionPrinter = printVersion
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
func ShowAppHelpAndExit(c *Context, exitCode int) {
ShowAppHelp(c)
_ = ShowAppHelp(c)
os.Exit(exitCode)
}
@ -153,19 +154,94 @@ func ShowAppHelp(c *Context) (err error) {
// DefaultAppComplete prints the list of subcommands as the default app completion method
func DefaultAppComplete(c *Context) {
for _, command := range c.App.Commands {
DefaultCompleteWithFlags(nil)(c)
}
func printCommandSuggestions(commands []Command, writer io.Writer) {
for _, command := range commands {
if command.Hidden {
continue
}
for _, name := range command.Names() {
fmt.Fprintln(c.App.Writer, name)
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" {
for _, name := range command.Names() {
_, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage)
}
} else {
for _, name := range command.Names() {
_, _ = fmt.Fprintf(writer, "%s\n", name)
}
}
}
}
func cliArgContains(flagName string) bool {
for _, name := range strings.Split(flagName, ",") {
name = strings.TrimSpace(name)
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2
}
flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
for _, a := range os.Args {
if a == flag {
return true
}
}
}
return false
}
func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
cur := strings.TrimPrefix(lastArg, "-")
cur = strings.TrimPrefix(cur, "-")
for _, flag := range flags {
if bflag, ok := flag.(BoolFlag); ok && bflag.Hidden {
continue
}
for _, name := range strings.Split(flag.GetName(), ",") {
name = strings.TrimSpace(name)
// this will get total count utf8 letters in flag name
count := utf8.RuneCountInString(name)
if count > 2 {
count = 2 // resuse this count to generate single - or -- in flag completion
}
// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
// skip flag completion for short flags example -v or -x
if strings.HasPrefix(lastArg, "--") && count == 1 {
continue
}
// match if last argument matches this flag and it is not repeated
if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(flag.GetName()) {
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
_, _ = fmt.Fprintln(writer, flagCompletion)
}
}
}
}
func DefaultCompleteWithFlags(cmd *Command) func(c *Context) {
return func(c *Context) {
if len(os.Args) > 2 {
lastArg := os.Args[len(os.Args)-2]
if strings.HasPrefix(lastArg, "-") {
printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer)
if cmd != nil {
printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer)
}
return
}
}
if cmd != nil {
printCommandSuggestions(cmd.Subcommands, c.App.Writer)
} else {
printCommandSuggestions(c.App.Commands, c.App.Writer)
}
}
}
// ShowCommandHelpAndExit - exits with code after showing help
func ShowCommandHelpAndExit(c *Context, command string, code int) {
ShowCommandHelp(c, command)
_ = ShowCommandHelp(c, command)
os.Exit(code)
}
@ -215,49 +291,57 @@ func ShowVersion(c *Context) {
}
func printVersion(c *Context) {
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
_, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version)
}
// ShowCompletions prints the lists of commands within a given context
func ShowCompletions(c *Context) {
a := c.App
if a != nil && a.ShellComplete != nil {
a.ShellComplete(c)
if a != nil && a.BashComplete != nil {
a.BashComplete(c)
}
}
// ShowCommandCompletions prints the custom completions for a given command
func ShowCommandCompletions(ctx *Context, command string) {
c := ctx.App.Command(command)
//TODO: Resolve
//<<<<<<< HEAD
if c != nil && c.ShellComplete != nil {
c.ShellComplete(ctx)
//=======
// if c != nil {
// if c.BashComplete != nil {
// c.BashComplete(ctx)
// } else {
// DefaultCompleteWithFlags(c)(ctx)
// }
//>>>>>>> master
}
}
func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) {
funcMap := template.FuncMap{
"join": strings.Join,
}
if customFunc != nil {
for key, value := range customFunc {
funcMap[key] = value
}
for key, value := range customFunc {
funcMap[key] = value
}
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
errDebug := os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != ""
err := t.Execute(w, data)
if err != nil {
if errDebug {
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
// If the writer is closed, t.Execute will fail, and there's nothing
// we can do to recover.
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
_, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
}
return
}
w.Flush()
_ = w.Flush()
}
func printHelp(out io.Writer, templ string, data interface{}) {
@ -286,7 +370,7 @@ func checkHelp(c *Context) bool {
func checkCommandHelp(c *Context, name string) bool {
if c.Bool("h") || c.Bool("help") {
ShowCommandHelp(c, name)
_ = ShowCommandHelp(c, name)
return true
}
@ -295,7 +379,7 @@ func checkCommandHelp(c *Context, name string) bool {
func checkSubcommandHelp(c *Context) bool {
if c.Bool("h") || c.Bool("help") {
ShowSubcommandHelp(c)
_ = ShowSubcommandHelp(c)
return true
}

View File

@ -15,9 +15,9 @@ func Test_ShowAppHelp_NoAuthor(t *testing.T) {
c := NewContext(app, nil, nil)
ShowAppHelp(c)
_ = ShowAppHelp(c)
if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 {
if bytes.Contains(output.Bytes(), []byte("AUTHOR(S):")) {
t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):")
}
}
@ -30,9 +30,9 @@ func Test_ShowAppHelp_NoVersion(t *testing.T) {
c := NewContext(app, nil, nil)
ShowAppHelp(c)
_ = ShowAppHelp(c)
if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
if bytes.Contains(output.Bytes(), []byte("VERSION:")) {
t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
}
}
@ -45,9 +45,9 @@ func Test_ShowAppHelp_HideVersion(t *testing.T) {
c := NewContext(app, nil, nil)
ShowAppHelp(c)
_ = ShowAppHelp(c)
if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 {
if bytes.Contains(output.Bytes(), []byte("VERSION:")) {
t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:")
}
}
@ -77,7 +77,7 @@ func Test_Help_Custom_Flags(t *testing.T) {
}
output := new(bytes.Buffer)
app.Writer = output
app.Run([]string{"test", "-h"})
_ = app.Run([]string{"test", "-h"})
if output.Len() > 0 {
t.Errorf("unexpected output: %s", output.String())
}
@ -108,7 +108,7 @@ func Test_Version_Custom_Flags(t *testing.T) {
}
output := new(bytes.Buffer)
app.Writer = output
app.Run([]string{"test", "-v"})
_ = app.Run([]string{"test", "-v"})
if output.Len() > 0 {
t.Errorf("unexpected output: %s", output.String())
}
@ -118,7 +118,7 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
app := &App{}
set := flag.NewFlagSet("test", 0)
set.Parse([]string{"foo"})
_ = set.Parse([]string{"foo"})
c := NewContext(app, set, nil)
@ -128,9 +128,9 @@ func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
}
exitErr, ok := err.(*exitError)
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
t.Fatalf("expected *ExitError from helpCommand.Action(), but instead got: %v", err.Error())
}
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
@ -146,7 +146,7 @@ func Test_helpCommand_InHelpOutput(t *testing.T) {
app := &App{}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"test", "--help"})
_ = app.Run([]string{"test", "--help"})
s := output.String()
@ -163,7 +163,7 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
app := &App{}
set := flag.NewFlagSet("test", 0)
set.Parse([]string{"foo"})
_ = set.Parse([]string{"foo"})
c := NewContext(app, set, nil)
@ -173,9 +173,9 @@ func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) {
t.Fatalf("expected error from helpCommand.Action(), but got nil")
}
exitErr, ok := err.(*exitError)
exitErr, ok := err.(*ExitError)
if !ok {
t.Fatalf("expected *exitError from helpCommand.Action(), but instead got: %v", err.Error())
t.Fatalf("expected *ExitError from helpCommand.Action(), but instead got: %v", err.Error())
}
if !strings.HasPrefix(exitErr.Error(), "No help topic for") {
@ -202,7 +202,7 @@ func TestShowAppHelp_CommandAliases(t *testing.T) {
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "--help"})
_ = app.Run([]string{"foo", "--help"})
if !strings.Contains(output.String(), "frobbly, fr, frob") {
t.Errorf("expected output to include all command aliases; got: %q", output.String())
@ -224,7 +224,7 @@ func TestShowCommandHelp_CommandAliases(t *testing.T) {
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "help", "fr"})
_ = app.Run([]string{"foo", "help", "fr"})
if !strings.Contains(output.String(), "frobbly") {
t.Errorf("expected output to include command name; got: %q", output.String())
@ -250,7 +250,7 @@ func TestShowSubcommandHelp_CommandAliases(t *testing.T) {
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "help"})
_ = app.Run([]string{"foo", "help"})
if !strings.Contains(output.String(), "frobbly, fr, frob, bork") {
t.Errorf("expected output to include all command aliases; got: %q", output.String())
@ -284,7 +284,7 @@ EXAMPLES:
}
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "help", "frobbly"})
_ = app.Run([]string{"foo", "help", "frobbly"})
if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") {
t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String())
@ -312,7 +312,7 @@ func TestShowSubcommandHelp_CommandUsageText(t *testing.T) {
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "frobbly", "--help"})
_ = app.Run([]string{"foo", "frobbly", "--help"})
if !strings.Contains(output.String(), "this is usage text") {
t.Errorf("expected output to include usage text; got: %q", output.String())
@ -336,7 +336,7 @@ func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) {
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
_ = app.Run([]string{"foo", "frobbly", "bobbly", "--help"})
if !strings.Contains(output.String(), "this is usage text") {
t.Errorf("expected output to include usage text; got: %q", output.String())
@ -364,7 +364,7 @@ func TestShowAppHelp_HiddenCommand(t *testing.T) {
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"app", "--help"})
_ = app.Run([]string{"app", "--help"})
if strings.Contains(output.String(), "secretfrob") {
t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())
@ -422,7 +422,7 @@ VERSION:
output := &bytes.Buffer{}
app.Writer = output
app.Run([]string{"app", "--help"})
_ = app.Run([]string{"app", "--help"})
if strings.Contains(output.String(), "secretfrob") {
t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String())

View File

@ -1,9 +0,0 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package cli
import "os"
func clearenv() {
os.Clearenv()
}

View File

@ -1,20 +0,0 @@
package cli
import (
"os"
"syscall"
)
// os.Clearenv() doesn't actually unset variables on Windows
// See: https://github.com/golang/go/issues/17902
func clearenv() {
for _, s := range os.Environ() {
for j := 1; j < len(s); j++ {
if s[j] == '=' {
keyp, _ := syscall.UTF16PtrFromString(s[0:j])
syscall.SetEnvironmentVariable(keyp, nil)
break
}
}
}
}

80
parse.go Normal file
View File

@ -0,0 +1,80 @@
package cli
import (
"flag"
"strings"
)
type iterativeParser interface {
newFlagSet() (*flag.FlagSet, error)
useShortOptionHandling() bool
}
// To enable short-option handling (e.g., "-it" vs "-i -t") we have to
// iteratively catch parsing errors. This way we achieve LR parsing without
// transforming any arguments. Otherwise, there is no way we can discriminate
// combined short options from common arguments that should be left untouched.
func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) {
for {
set, err := ip.newFlagSet()
if err != nil {
return nil, err
}
err = set.Parse(args)
if !ip.useShortOptionHandling() || err == nil {
return set, err
}
errStr := err.Error()
trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ")
if errStr == trimmed {
return nil, err
}
// regenerate the initial args with the split short opts
newArgs := []string{}
for i, arg := range args {
if arg != trimmed {
newArgs = append(newArgs, arg)
continue
}
shortOpts := splitShortOptions(set, trimmed)
if len(shortOpts) == 1 {
return nil, err
}
// add each short option and all remaining arguments
newArgs = append(newArgs, shortOpts...)
newArgs = append(newArgs, args[i+1:]...)
args = newArgs
}
}
}
func splitShortOptions(set *flag.FlagSet, arg string) []string {
shortFlagsExist := func(s string) bool {
for _, c := range s[1:] {
if f := set.Lookup(string(c)); f == nil {
return false
}
}
return true
}
if !isSplittable(arg) || !shortFlagsExist(arg) {
return []string{arg}
}
separated := make([]string, 0, len(arg)-1)
for _, flagChar := range arg[1:] {
separated = append(separated, "-"+string(flagChar))
}
return separated
}
func isSplittable(flagArg string) bool {
return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
}

172
runtests
View File

@ -1,172 +0,0 @@
#!/usr/bin/env python
from __future__ import print_function, unicode_literals
import argparse
import codecs
import glob
import os
import platform
import shutil
import sys
import tempfile
from subprocess import check_call, check_output
_PY3 = sys.version_info.major == 3
_WINDOWS = platform.system().lower() == 'windows'
_PACKAGE_NAME = os.environ.get(
'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
)
_TARGETS = {}
def main(sysargs=sys.argv[:]):
parser = argparse.ArgumentParser()
parser.add_argument(
'target', nargs='?', choices=tuple(_TARGETS.keys()), default='test'
)
args = parser.parse_args(sysargs[1:])
_TARGETS[args.target]()
return 0
def _target(func):
_TARGETS[func.__name__.strip('_')] = func
return func
@_target
def _test():
if _go_version() < 'go1.2':
_run('go test -v .')
return
coverprofiles = []
for subpackage in ['', 'altsrc']:
coverprofile = 'cli.coverprofile'
if subpackage != '':
coverprofile = '{}.coverprofile'.format(subpackage)
coverprofiles.append(coverprofile)
_run('go test -v'.split() + [
'-coverprofile={}'.format(coverprofile),
('{}/{}'.format(_PACKAGE_NAME, subpackage)).rstrip('/')
])
combined_name = _combine_coverprofiles(coverprofiles)
_run('go tool cover -func={}'.format(combined_name))
os.remove(combined_name)
@_target
def _gfmrun():
go_version = _go_version()
if go_version < 'go1.3':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
@_target
def _vet():
_run('go vet ./...')
@_target
def _migrations():
go_version = _go_version()
if go_version < 'go1.3':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
migration_script = os.path.abspath(
os.environ.get('V1TOV2', './cli-v1-to-v2')
)
v1_readme_url = os.environ.get(
'V1README',
'https://raw.githubusercontent.com/urfave/cli/v1/README.md'
)
tmpdir = tempfile.mkdtemp()
try:
os.chdir(tmpdir)
_run('curl -sSL -o README.md {}'.format(v1_readme_url).split())
_run('gfmrun extract -o .'.split())
for gofile in glob.glob('*.go'):
for i in (0, 1):
_run(['python', migration_script, '-w', gofile])
_run('go build -o tmp.out {}'.format(gofile).split())
finally:
if os.environ.get('NOCLEAN', '') == '':
shutil.rmtree(tmpdir, ignore_errors=True)
@_target
def _toc():
exe = ['bash'] if _WINDOWS else []
_run(exe + [
os.path.join('node_modules', '.bin', 'markdown-toc'),
'-i', 'README.md'
])
_run('git diff --exit-code')
@_target
def _gen():
go_version = _go_version()
if go_version < 'go1.5':
print('runtests: skip on {}'.format(go_version), file=sys.stderr)
return
_run('go generate ./...')
_run('git diff --exit-code')
def _run(command):
if hasattr(command, 'split'):
command = command.split()
print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
sys.stderr.flush()
check_call(command)
def _gfmrun_count():
with codecs.open('README.md', 'r', 'utf-8') as infile:
lines = infile.read().splitlines()
return len(list(filter(_is_go_runnable, lines)))
def _is_go_runnable(line):
return line.startswith('package main')
def _go_version():
return check_output('go version'.split()).decode('utf-8').split()[2]
def _combine_coverprofiles(coverprofiles):
tmp_args = dict(suffix='.coverprofile', mode='w', delete=False)
if _PY3:
tmp_args['encoding'] = 'utf-8'
combined = tempfile.NamedTemporaryFile(**tmp_args)
combined.write('mode: set\n')
for coverprofile in coverprofiles:
with codecs.open(coverprofile, 'r', 'utf-8') as infile:
for line in infile.readlines():
if not line.startswith('mode: '):
combined.write(line)
combined.flush()
name = combined.name
combined.close()
return name
if __name__ == '__main__':
sys.exit(main())

29
sort.go Normal file
View File

@ -0,0 +1,29 @@
package cli
import "unicode"
// lexicographicLess compares strings alphabetically considering case.
func lexicographicLess(i, j string) bool {
iRunes := []rune(i)
jRunes := []rune(j)
lenShared := len(iRunes)
if lenShared > len(jRunes) {
lenShared = len(jRunes)
}
for index := 0; index < lenShared; index++ {
ir := iRunes[index]
jr := jRunes[index]
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
return lir < ljr
}
if ir != jr {
return ir < jr
}
}
return i < j
}

30
sort_test.go Normal file
View File

@ -0,0 +1,30 @@
package cli
import "testing"
var lexicographicLessTests = []struct {
i string
j string
expected bool
}{
{"", "a", true},
{"a", "", false},
{"a", "a", false},
{"a", "A", false},
{"A", "a", true},
{"aa", "a", false},
{"a", "aa", true},
{"a", "b", true},
{"a", "B", true},
{"A", "b", true},
{"A", "B", true},
}
func TestLexicographicLess(t *testing.T) {
for _, test := range lexicographicLessTests {
actual := lexicographicLess(test.i, test.j)
if test.expected != actual {
t.Errorf(`expected string "%s" to come before "%s"`, test.i, test.j)
}
}
}

121
template.go Normal file
View File

@ -0,0 +1,121 @@
package cli
// AppHelpTemplate is the text template for the Default help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var AppHelpTemplate = `NAME:
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
VERSION:
{{.Version}}{{end}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if len .Authors}}
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
GLOBAL OPTIONS:
{{range $index, $option := .VisibleFlags}}{{if $index}}
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}}
COPYRIGHT:
{{.Copyright}}{{end}}
`
// CommandHelpTemplate is the text template for the command help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var CommandHelpTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}}
CATEGORY:
{{.Category}}{{end}}{{if .Description}}
DESCRIPTION:
{{.Description}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
// SubcommandHelpTemplate is the text template for the subcommand help topic.
// cli.go uses text/template to render templates. You can
// render custom help text by setting this variable.
var SubcommandHelpTemplate = `NAME:
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}}
USAGE:
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}
COMMANDS:{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}}
OPTIONS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
`
var MarkdownDocTemplate = `% {{ .App.Name }}(8) {{ .App.Description }}
% {{ .App.Author }}
# NAME
{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }}
# SYNOPSIS
{{ .App.Name }}
{{ if .SynopsisArgs }}
` + "```" + `
{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + `
{{ end }}{{ if .App.UsageText }}
# DESCRIPTION
{{ .App.UsageText }}
{{ end }}
**Usage**:
` + "```" + `
{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
` + "```" + `
{{ if .GlobalArgs }}
# GLOBAL OPTIONS
{{ range $v := .GlobalArgs }}
{{ $v }}{{ end }}
{{ end }}{{ if .Commands }}
# COMMANDS
{{ range $v := .Commands }}
{{ $v }}{{ end }}{{ end }}`
var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion
function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet'
for i in (commandline -opc)
if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }}
return 1
end
end
return 0
end
{{ range $v := .Completions }}{{ $v }}
{{ end }}`

80
testdata/expected-doc-full.man vendored Normal file
View File

@ -0,0 +1,80 @@
.nh
.TH greet(8)
.SH Harrison
.SH NAME
.PP
greet \- Some app
.SH SYNOPSIS
.PP
greet
.PP
.RS
.nf
[\-\-another\-flag|\-b]
[\-\-flag|\-\-fl|\-f]=[value]
[\-\-socket|\-s]=[value]
.fi
.RE
.SH DESCRIPTION
.PP
app [first\_arg] [second\_arg]
.PP
\fBUsage\fP:
.PP
.RS
.nf
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
.fi
.RE
.SH GLOBAL OPTIONS
.PP
\fB\-\-another\-flag, \-b\fP: another usage text
.PP
\fB\-\-flag, \-\-fl, \-f\fP="":
.PP
\fB\-\-socket, \-s\fP="": some 'usage' text (default: value)
.SH COMMANDS
.SH config, c
.PP
another usage test
.PP
\fB\-\-another\-flag, \-b\fP: another usage text
.PP
\fB\-\-flag, \-\-fl, \-f\fP="":
.SS sub\-config, s, ss
.PP
another usage test
.PP
\fB\-\-sub\-command\-flag, \-s\fP: some usage text
.PP
\fB\-\-sub\-flag, \-\-sub\-fl, \-s\fP="":
.SH info, i, in
.PP
retrieve generic information
.SH some\-command

62
testdata/expected-doc-full.md vendored Normal file
View File

@ -0,0 +1,62 @@
% greet(8)
% Harrison
# NAME
greet - Some app
# SYNOPSIS
greet
```
[--another-flag|-b]
[--flag|--fl|-f]=[value]
[--socket|-s]=[value]
```
# DESCRIPTION
app [first_arg] [second_arg]
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# GLOBAL OPTIONS
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
**--socket, -s**="": some 'usage' text (default: value)
# COMMANDS
## config, c
another usage test
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-config, s, ss
another usage test
**--sub-command-flag, -s**: some usage text
**--sub-flag, --sub-fl, -s**="":
## info, i, in
retrieve generic information
## some-command

36
testdata/expected-doc-no-commands.md vendored Normal file
View File

@ -0,0 +1,36 @@
% greet(8)
% Harrison
# NAME
greet - Some app
# SYNOPSIS
greet
```
[--another-flag|-b]
[--flag|--fl|-f]=[value]
[--socket|-s]=[value]
```
# DESCRIPTION
app [first_arg] [second_arg]
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# GLOBAL OPTIONS
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
**--socket, -s**="": some 'usage' text (default: value)

47
testdata/expected-doc-no-flags.md vendored Normal file
View File

@ -0,0 +1,47 @@
% greet(8)
% Harrison
# NAME
greet - Some app
# SYNOPSIS
greet
# DESCRIPTION
app [first_arg] [second_arg]
**Usage**:
```
greet [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
```
# COMMANDS
## config, c
another usage test
**--another-flag, -b**: another usage text
**--flag, --fl, -f**="":
### sub-config, s, ss
another usage test
**--sub-command-flag, -s**: some usage text
**--sub-flag, --sub-fl, -s**="":
## info, i, in
retrieve generic information
## some-command

28
testdata/expected-fish-full.fish vendored Normal file
View File

@ -0,0 +1,28 @@
# greet fish shell completion
function __fish_greet_no_subcommand --description 'Test if there has been any subcommand yet'
for i in (commandline -opc)
if contains -- $i config c sub-config s ss info i in some-command
return 1
end
end
return 0
end
complete -c greet -n '__fish_greet_no_subcommand' -l socket -s s -r -d 'some \'usage\' text'
complete -c greet -n '__fish_greet_no_subcommand' -f -l flag -s fl -s f -r
complete -c greet -n '__fish_greet_no_subcommand' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_greet_no_subcommand' -f -l help -s h -d 'show help'
complete -c greet -n '__fish_greet_no_subcommand' -f -l version -s v -d 'print the version'
complete -c greet -n '__fish_seen_subcommand_from config c' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'config c' -d 'another usage test'
complete -c greet -n '__fish_seen_subcommand_from config c' -l flag -s fl -s f -r
complete -c greet -n '__fish_seen_subcommand_from config c' -f -l another-flag -s b -d 'another usage text'
complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_seen_subcommand_from config c' -a 'sub-config s ss' -d 'another usage test'
complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l sub-flag -s sub-fl -s s -r
complete -c greet -n '__fish_seen_subcommand_from sub-config s ss' -f -l sub-command-flag -s s -d 'some usage text'
complete -c greet -n '__fish_seen_subcommand_from info i in' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'info i in' -d 'retrieve generic information'
complete -c greet -n '__fish_seen_subcommand_from some-command' -f -l help -s h -d 'show help'
complete -r -c greet -n '__fish_greet_no_subcommand' -a 'some-command'