From f2122e3c51dc343f2046a9446ab16f199849e457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 12 Aug 2022 16:52:43 +0300 Subject: [PATCH 01/34] upgrade urfave, enable urfave's auto-completion --- auto | 20 + command/app.go | 18 +- go.mod | 8 +- go.sum | 22 +- main_test.Go | 12 + .../cpuguy83/go-md2man/v2/md2man/roff.go | 105 +- .../russross/blackfriday/v2/README.md | 90 +- .../russross/blackfriday/v2/block.go | 30 +- .../github.com/russross/blackfriday/v2/doc.go | 28 + .../russross/blackfriday/v2/entities.go | 2236 ++++++++++++++++ .../github.com/russross/blackfriday/v2/esc.go | 42 +- .../russross/blackfriday/v2/html.go | 9 +- .../russross/blackfriday/v2/inline.go | 2 +- .../russross/blackfriday/v2/node.go | 12 +- .../sanitized_anchor_name/.travis.yml | 16 - .../shurcooL/sanitized_anchor_name/LICENSE | 21 - .../shurcooL/sanitized_anchor_name/README.md | 36 - .../shurcooL/sanitized_anchor_name/main.go | 29 - vendor/github.com/urfave/cli/v2/.gitignore | 5 +- .../urfave/cli/v2/CODE_OF_CONDUCT.md | 11 +- vendor/github.com/urfave/cli/v2/LICENSE | 2 +- vendor/github.com/urfave/cli/v2/Makefile | 40 + vendor/github.com/urfave/cli/v2/README.md | 63 +- vendor/github.com/urfave/cli/v2/app.go | 283 +- vendor/github.com/urfave/cli/v2/category.go | 94 +- vendor/github.com/urfave/cli/v2/cli.go | 2 +- vendor/github.com/urfave/cli/v2/command.go | 58 +- vendor/github.com/urfave/cli/v2/context.go | 180 +- vendor/github.com/urfave/cli/v2/docs.go | 74 +- vendor/github.com/urfave/cli/v2/errors.go | 50 +- vendor/github.com/urfave/cli/v2/fish.go | 6 +- .../github.com/urfave/cli/v2/flag-spec.yaml | 51 + vendor/github.com/urfave/cli/v2/flag.go | 171 +- vendor/github.com/urfave/cli/v2/flag_bool.go | 67 +- .../github.com/urfave/cli/v2/flag_duration.go | 67 +- .../github.com/urfave/cli/v2/flag_float64.go | 72 +- .../urfave/cli/v2/flag_float64_slice.go | 124 +- .../github.com/urfave/cli/v2/flag_generic.go | 67 +- vendor/github.com/urfave/cli/v2/flag_int.go | 67 +- vendor/github.com/urfave/cli/v2/flag_int64.go | 67 +- .../urfave/cli/v2/flag_int64_slice.go | 127 +- .../urfave/cli/v2/flag_int_slice.go | 126 +- vendor/github.com/urfave/cli/v2/flag_path.go | 73 +- .../github.com/urfave/cli/v2/flag_string.go | 79 +- .../urfave/cli/v2/flag_string_slice.go | 118 +- .../urfave/cli/v2/flag_timestamp.go | 100 +- vendor/github.com/urfave/cli/v2/flag_uint.go | 67 +- .../github.com/urfave/cli/v2/flag_uint64.go | 67 +- vendor/github.com/urfave/cli/v2/funcs.go | 6 +- .../urfave/cli/v2/godoc-current.txt | 2318 +++++++++++++++++ vendor/github.com/urfave/cli/v2/help.go | 222 +- .../urfave/cli/v2/mkdocs-requirements.txt | 5 + vendor/github.com/urfave/cli/v2/mkdocs.yml | 62 + vendor/github.com/urfave/cli/v2/parse.go | 18 +- vendor/github.com/urfave/cli/v2/sliceflag.go | 293 +++ .../urfave/cli/v2/sliceflag_pre18.go | 10 + .../github.com/urfave/cli/v2/suggestions.go | 60 + vendor/github.com/urfave/cli/v2/template.go | 48 +- .../urfave/cli/v2/zz_generated.flags.go | 674 +++++ vendor/github.com/xrash/smetrics/.travis.yml | 9 + vendor/github.com/xrash/smetrics/LICENSE | 21 + vendor/github.com/xrash/smetrics/README.md | 49 + vendor/github.com/xrash/smetrics/doc.go | 19 + vendor/github.com/xrash/smetrics/hamming.go | 25 + .../github.com/xrash/smetrics/jaro-winkler.go | 28 + vendor/github.com/xrash/smetrics/jaro.go | 86 + vendor/github.com/xrash/smetrics/soundex.go | 41 + vendor/github.com/xrash/smetrics/ukkonen.go | 94 + .../xrash/smetrics/wagner-fischer.go | 48 + vendor/modules.txt | 16 +- 70 files changed, 7944 insertions(+), 1322 deletions(-) create mode 100644 auto create mode 100644 main_test.Go create mode 100644 vendor/github.com/russross/blackfriday/v2/entities.go delete mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml delete mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE delete mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/README.md delete mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/main.go create mode 100644 vendor/github.com/urfave/cli/v2/Makefile create mode 100644 vendor/github.com/urfave/cli/v2/flag-spec.yaml create mode 100644 vendor/github.com/urfave/cli/v2/godoc-current.txt create mode 100644 vendor/github.com/urfave/cli/v2/mkdocs-requirements.txt create mode 100644 vendor/github.com/urfave/cli/v2/mkdocs.yml create mode 100644 vendor/github.com/urfave/cli/v2/sliceflag.go create mode 100644 vendor/github.com/urfave/cli/v2/sliceflag_pre18.go create mode 100644 vendor/github.com/urfave/cli/v2/suggestions.go create mode 100644 vendor/github.com/urfave/cli/v2/zz_generated.flags.go create mode 100644 vendor/github.com/xrash/smetrics/.travis.yml create mode 100644 vendor/github.com/xrash/smetrics/LICENSE create mode 100644 vendor/github.com/xrash/smetrics/README.md create mode 100644 vendor/github.com/xrash/smetrics/doc.go create mode 100644 vendor/github.com/xrash/smetrics/hamming.go create mode 100644 vendor/github.com/xrash/smetrics/jaro-winkler.go create mode 100644 vendor/github.com/xrash/smetrics/jaro.go create mode 100644 vendor/github.com/xrash/smetrics/soundex.go create mode 100644 vendor/github.com/xrash/smetrics/ukkonen.go create mode 100644 vendor/github.com/xrash/smetrics/wagner-fischer.go diff --git a/auto b/auto new file mode 100644 index 000000000..b519666f8 --- /dev/null +++ b/auto @@ -0,0 +1,20 @@ +#compdef $PROG + +_cli_zsh_autocomplete() { + local -a opts + local cur + cur=${words[-1]} + if [[ "$cur" == "-"* ]]; then + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + else + opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") + fi + + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi +} + +compdef _cli_zsh_autocomplete $PROG diff --git a/command/app.go b/command/app.go index 6f324b842..c0a927e5f 100644 --- a/command/app.go +++ b/command/app.go @@ -22,8 +22,9 @@ const ( ) var app = &cli.App{ - Name: appName, - Usage: "Blazing fast S3 and local filesystem execution tool", + Name: appName, + Usage: "Blazing fast S3 and local filesystem execution tool", + EnableBashCompletion: true, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", @@ -59,7 +60,11 @@ var app = &cli.App{ }, &cli.BoolFlag{ Name: "install-completion", - Usage: "install completion for your shell", + Usage: "install completion for your shell (only avialble for bash, zsh, and fish)", + }, + &cli.BoolFlag{ + Name: "uninstall-completion", + Usage: "uninstall completion from your shell", }, &cli.BoolFlag{ Name: "dry-run", @@ -150,6 +155,13 @@ var app = &cli.App{ return cmpinstall.Install(appName) } + if c.Bool("unsinstall-completion") { + if !cmpinstall.IsInstalled(appName) { + return nil + } + + return cmpinstall.Uninstall(appName) + } args := c.Args() if args.Present() { diff --git a/go.mod b/go.mod index b06f77cd8..c5da25cc4 100644 --- a/go.mod +++ b/go.mod @@ -13,23 +13,23 @@ require ( github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.4.0 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae - github.com/urfave/cli/v2 v2.2.0 + github.com/urfave/cli/v2 v2.11.1 gotest.tools/v3 v3.0.2 ) require ( - github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/pretty v0.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/stretchr/objx v0.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.etcd.io/bbolt v1.3.6 // indirect golang.org/x/sys v0.0.0-20220405210540-1e041c57c461 // indirect golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 // indirect diff --git a/go.sum b/go.sum index 1066238b5..0857e477c 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,9 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/aws/aws-sdk-go v1.17.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.40.25 h1:Depnx7O86HWgOCLD5nMto6F9Ju85Q1QuFDnbpZYQWno= github.com/aws/aws-sdk-go v1.40.25/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -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/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -39,14 +39,12 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 h1:J6qvD6rbmOil46orKqJaRPG+zTpoGlBTUdyv8ki63L0= github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63/go.mod h1:n+VKSARF5y/tS9XFSP7vWDfS+GUC5vs/YT7M5XDTUEM= -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= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= @@ -56,8 +54,10 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1aZOlG32uyE6xHpLdKhZzcTEktz5wM= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= +github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -75,8 +75,9 @@ golang.org/x/sys v0.0.0-20220405210540-1e041c57c461 h1:kHVeDEnfKn3T238CvrUcz6KeE golang.org/x/sys v0.0.0-20220405210540-1e041c57c461/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190308174544-00c44ba9c14f/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4 h1:1mMox4TgefDwqluYCv677yNXwlfTkija4owZve/jr78= @@ -91,5 +92,6 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/main_test.Go b/main_test.Go new file mode 100644 index 000000000..66ebe50b3 --- /dev/null +++ b/main_test.Go @@ -0,0 +1,12 @@ +package main + +func TestPlay(t *testing.T) { + a := make([]extsort.SortType, 0, 100) + fmt.Println(unsafe.Sizeof(a)) + a = make([]extsort.SortType, 0, 1000) + fmt.Println(unsafe.Sizeof(a)) + a = make([]extsort.SortType, 0, 1000) + fmt.Println(unsafe.Sizeof(a)) + + t.Fatal("It's intentional, don't worry") +} \ No newline at end of file diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go index 0668a66cf..be2b34360 100644 --- a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go +++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go @@ -15,7 +15,7 @@ type roffRenderer struct { extensions blackfriday.Extensions listCounters []int firstHeader bool - defineTerm bool + firstDD bool listDepth int } @@ -42,7 +42,8 @@ const ( quoteCloseTag = "\n.RE\n" listTag = "\n.RS\n" listCloseTag = "\n.RE\n" - arglistTag = "\n.TP\n" + dtTag = "\n.TP\n" + dd2Tag = "\n" tableStart = "\n.TS\nallbox;\n" tableEnd = ".TE\n" tableCellStart = "T{\n" @@ -90,7 +91,7 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering switch node.Type { case blackfriday.Text: - r.handleText(w, node, entering) + escapeSpecialChars(w, node.Literal) case blackfriday.Softbreak: out(w, crTag) case blackfriday.Hardbreak: @@ -150,40 +151,21 @@ func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering out(w, codeCloseTag) case blackfriday.Table: r.handleTable(w, node, entering) - case blackfriday.TableCell: - r.handleTableCell(w, node, entering) case blackfriday.TableHead: case blackfriday.TableBody: case blackfriday.TableRow: // no action as cell entries do all the nroff formatting return blackfriday.GoToNext + case blackfriday.TableCell: + r.handleTableCell(w, node, entering) + case blackfriday.HTMLSpan: + // ignore other HTML tags default: fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) } return walkAction } -func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) { - var ( - start, end string - ) - // handle special roff table cell text encapsulation - if node.Parent.Type == blackfriday.TableCell { - if len(node.Literal) > 30 { - start = tableCellStart - end = tableCellEnd - } else { - // end rows that aren't terminated by "tableCellEnd" with a cr if end of row - if node.Parent.Next == nil && !node.Parent.IsHeader { - end = crTag - } - } - } - out(w, start) - escapeSpecialChars(w, node.Literal) - out(w, end) -} - func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) { if entering { switch node.Level { @@ -230,15 +212,20 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering if node.ListFlags&blackfriday.ListTypeOrdered != 0 { out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1])) r.listCounters[len(r.listCounters)-1]++ + } else if node.ListFlags&blackfriday.ListTypeTerm != 0 { + // DT (definition term): line just before DD (see below). + out(w, dtTag) + r.firstDD = true } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { - // state machine for handling terms and following definitions - // since blackfriday does not distinguish them properly, nor - // does it seperate them into separate lists as it should - if !r.defineTerm { - out(w, arglistTag) - r.defineTerm = true + // DD (definition description): line that starts with ": ". + // + // We have to distinguish between the first DD and the + // subsequent ones, as there should be no vertical + // whitespace between the DT and the first DD. + if r.firstDD { + r.firstDD = false } else { - r.defineTerm = false + out(w, dd2Tag) } } else { out(w, ".IP \\(bu 2\n") @@ -251,7 +238,7 @@ func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) { if entering { out(w, tableStart) - //call walker to count cells (and rows?) so format section can be produced + // call walker to count cells (and rows?) so format section can be produced columns := countColumns(node) out(w, strings.Repeat("l ", columns)+"\n") out(w, strings.Repeat("l ", columns)+".\n") @@ -261,28 +248,41 @@ func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering } func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) { - var ( - start, end string - ) - if node.IsHeader { - start = codespanTag - end = codespanCloseTag - } if entering { + var start string if node.Prev != nil && node.Prev.Type == blackfriday.TableCell { - out(w, "\t"+start) - } else { - out(w, start) + start = "\t" + } + if node.IsHeader { + start += codespanTag + } else if nodeLiteralSize(node) > 30 { + start += tableCellStart } + out(w, start) } else { - // need to carriage return if we are at the end of the header row - if node.IsHeader && node.Next == nil { - end = end + crTag + var end string + if node.IsHeader { + end = codespanCloseTag + } else if nodeLiteralSize(node) > 30 { + end = tableCellEnd + } + if node.Next == nil && end != tableCellEnd { + // Last cell: need to carriage return if we are at the end of the + // header row and content isn't wrapped in a "tablecell" + end += crTag } out(w, end) } } +func nodeLiteralSize(node *blackfriday.Node) int { + total := 0 + for n := node.FirstChild; n != nil; n = n.FirstChild { + total += len(n.Literal) + } + return total +} + // because roff format requires knowing the column count before outputting any table // data we need to walk a table tree and count the columns func countColumns(node *blackfriday.Node) int { @@ -309,15 +309,6 @@ func out(w io.Writer, output string) { io.WriteString(w, output) // nolint: errcheck } -func needsBackslash(c byte) bool { - for _, r := range []byte("-_&\\~") { - if c == r { - return true - } - } - return false -} - func escapeSpecialChars(w io.Writer, text []byte) { for i := 0; i < len(text); i++ { // escape initial apostrophe or period @@ -328,7 +319,7 @@ func escapeSpecialChars(w io.Writer, text []byte) { // directly copy normal characters org := i - for i < len(text) && !needsBackslash(text[i]) { + for i < len(text) && text[i] != '\\' { i++ } if i > org { diff --git a/vendor/github.com/russross/blackfriday/v2/README.md b/vendor/github.com/russross/blackfriday/v2/README.md index d5a8649bd..d9c08a22f 100644 --- a/vendor/github.com/russross/blackfriday/v2/README.md +++ b/vendor/github.com/russross/blackfriday/v2/README.md @@ -1,4 +1,6 @@ -Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) +Blackfriday +[![Build Status][BuildV2SVG]][BuildV2URL] +[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL] =========== Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It @@ -16,19 +18,21 @@ It started as a translation from C of [Sundown][3]. Installation ------------ -Blackfriday is compatible with any modern Go release. With Go 1.7 and git -installed: +Blackfriday is compatible with modern Go releases in module mode. +With Go installed: - go get gopkg.in/russross/blackfriday.v2 + go get github.com/russross/blackfriday/v2 -will download, compile, and install the package into your `$GOPATH` -directory hierarchy. Alternatively, you can achieve the same if you -import it into a project: +will resolve and add the package to the current development module, +then build and install it. Alternatively, you can achieve the same +if you import it in a package: - import "gopkg.in/russross/blackfriday.v2" + import "github.com/russross/blackfriday/v2" and `go get` without parameters. +Legacy GOPATH mode is unsupported. + Versions -------- @@ -36,13 +40,9 @@ Versions Currently maintained and recommended version of Blackfriday is `v2`. It's being developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the documentation is available at -https://godoc.org/gopkg.in/russross/blackfriday.v2. +https://pkg.go.dev/github.com/russross/blackfriday/v2. -It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`, -but we highly recommend using package management tool like [dep][7] or -[Glide][8] and make use of semantic versioning. With package management you -should import `github.com/russross/blackfriday` and specify that you're using -version 2.0.0. +It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`. Version 2 offers a number of improvements over v1: @@ -62,6 +62,11 @@ Potential drawbacks: v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for tracking. +If you are still interested in the legacy `v1`, you can import it from +`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found +here: https://pkg.go.dev/github.com/russross/blackfriday. + + Usage ----- @@ -91,7 +96,7 @@ Here's an example of simple usage of Blackfriday together with Bluemonday: ```go import ( "github.com/microcosm-cc/bluemonday" - "github.com/russross/blackfriday" + "github.com/russross/blackfriday/v2" ) // ... @@ -104,6 +109,8 @@ html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) If you want to customize the set of options, use `blackfriday.WithExtensions`, `blackfriday.WithRenderer` and `blackfriday.WithRefOverride`. +### `blackfriday-tool` + You can also check out `blackfriday-tool` for a more complete example of how to use it. Download and install it using: @@ -114,7 +121,7 @@ markdown file using a standalone program. You can also browse the source directly on github if you are just looking for some example code: -* +* Note that if you have not already done so, installing `blackfriday-tool` will be sufficient to download and install @@ -123,6 +130,22 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that can be copied to wherever you need it without worrying about dependencies and library versions. +### Sanitized anchor names + +Blackfriday includes an algorithm for creating sanitized anchor names +corresponding to a given input text. This algorithm is used to create +anchors for headings when `AutoHeadingIDs` extension is enabled. The +algorithm has a specification, so that other packages can create +compatible anchor names and links to those anchors. + +The specification is located at https://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names. + +[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#SanitizedAnchorName) exposes this functionality, and can be used to +create compatible links to the anchor names generated by blackfriday. +This algorithm is also implemented in a small standalone package at +[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients +that want a small package and don't need full functionality of blackfriday. + Features -------- @@ -199,6 +222,15 @@ implements the following extensions: You can use 3 or more backticks to mark the beginning of the block, and the same number to mark the end of the block. + To preserve classes of fenced code blocks while using the bluemonday + HTML sanitizer, use the following policy: + + ```go + p := bluemonday.UGCPolicy() + p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code") + html := p.SanitizeBytes(unsafe) + ``` + * **Definition lists**. A simple definition list is made of a single-line term followed by a colon and the definition for that term. @@ -250,7 +282,7 @@ Other renderers Blackfriday is structured to allow alternative rendering engines. Here are a few of note: -* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown): +* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown): provides a GitHub Flavored Markdown renderer with fenced code block highlighting, clickable heading anchor links. @@ -261,20 +293,28 @@ are a few of note: * [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt, but for markdown. -* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX): +* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex): renders output as LaTeX. +* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience + integration with the [Chroma](https://github.com/alecthomas/chroma) code + highlighting library. bfchroma is only compatible with v2 of Blackfriday and + provides a drop-in renderer ready to use with Blackfriday, as well as + options and means for further customization. + * [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer. +* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style + -Todo +TODO ---- * More unit testing -* Improve unicode support. It does not understand all unicode +* Improve Unicode support. It does not understand all Unicode rules (about what constitutes a letter, a punctuation symbol, etc.), so it may fail to detect word boundaries correctly in - some instances. It is safe on all utf-8 input. + some instances. It is safe on all UTF-8 input. License @@ -286,6 +326,10 @@ License [1]: https://daringfireball.net/projects/markdown/ "Markdown" [2]: https://golang.org/ "Go Language" [3]: https://github.com/vmg/sundown "Sundown" - [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" + [4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func" [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" - [6]: https://labix.org/gopkg.in "gopkg.in" + + [BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2 + [BuildV2URL]: https://travis-ci.org/russross/blackfriday + [PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2 + [PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2 diff --git a/vendor/github.com/russross/blackfriday/v2/block.go b/vendor/github.com/russross/blackfriday/v2/block.go index b8607474e..dcd61e6e3 100644 --- a/vendor/github.com/russross/blackfriday/v2/block.go +++ b/vendor/github.com/russross/blackfriday/v2/block.go @@ -18,8 +18,7 @@ import ( "html" "regexp" "strings" - - "github.com/shurcooL/sanitized_anchor_name" + "unicode" ) const ( @@ -259,7 +258,7 @@ func (p *Markdown) prefixHeading(data []byte) int { } if end > i { if id == "" && p.extensions&AutoHeadingIDs != 0 { - id = sanitized_anchor_name.Create(string(data[i:end])) + id = SanitizedAnchorName(string(data[i:end])) } block := p.addBlock(Heading, data[i:end]) block.HeadingID = id @@ -673,6 +672,7 @@ func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { if beg == 0 || beg >= len(data) { return 0 } + fenceLength := beg - 1 var work bytes.Buffer work.Write([]byte(info)) @@ -706,6 +706,7 @@ func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { if doRender { block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer block.IsFenced = true + block.FenceLength = fenceLength finalizeCodeBlock(block) } @@ -1503,7 +1504,7 @@ func (p *Markdown) paragraph(data []byte) int { id := "" if p.extensions&AutoHeadingIDs != 0 { - id = sanitized_anchor_name.Create(string(data[prev:eol])) + id = SanitizedAnchorName(string(data[prev:eol])) } block := p.addBlock(Heading, data[prev:eol]) @@ -1588,3 +1589,24 @@ func skipUntilChar(text []byte, start int, char byte) int { } return i } + +// SanitizedAnchorName returns a sanitized anchor name for the given text. +// +// It implements the algorithm specified in the package comment. +func SanitizedAnchorName(text string) string { + var anchorName []rune + futureDash := false + for _, r := range text { + switch { + case unicode.IsLetter(r) || unicode.IsNumber(r): + if futureDash && len(anchorName) > 0 { + anchorName = append(anchorName, '-') + } + futureDash = false + anchorName = append(anchorName, unicode.ToLower(r)) + default: + futureDash = true + } + } + return string(anchorName) +} diff --git a/vendor/github.com/russross/blackfriday/v2/doc.go b/vendor/github.com/russross/blackfriday/v2/doc.go index 5b3fa9876..57ff152a0 100644 --- a/vendor/github.com/russross/blackfriday/v2/doc.go +++ b/vendor/github.com/russross/blackfriday/v2/doc.go @@ -15,4 +15,32 @@ // // If you're interested in calling Blackfriday from command line, see // https://github.com/russross/blackfriday-tool. +// +// Sanitized Anchor Names +// +// Blackfriday includes an algorithm for creating sanitized anchor names +// corresponding to a given input text. This algorithm is used to create +// anchors for headings when AutoHeadingIDs extension is enabled. The +// algorithm is specified below, so that other packages can create +// compatible anchor names and links to those anchors. +// +// The algorithm iterates over the input text, interpreted as UTF-8, +// one Unicode code point (rune) at a time. All runes that are letters (category L) +// or numbers (category N) are considered valid characters. They are mapped to +// lower case, and included in the output. All other runes are considered +// invalid characters. Invalid characters that precede the first valid character, +// as well as invalid character that follow the last valid character +// are dropped completely. All other sequences of invalid characters +// between two valid characters are replaced with a single dash character '-'. +// +// SanitizedAnchorName exposes this functionality, and can be used to +// create compatible links to the anchor names generated by blackfriday. +// This algorithm is also implemented in a small standalone package at +// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients +// that want a small package and don't need full functionality of blackfriday. package blackfriday + +// NOTE: Keep Sanitized Anchor Name algorithm in sync with package +// github.com/shurcooL/sanitized_anchor_name. +// Otherwise, users of sanitized_anchor_name will get anchor names +// that are incompatible with those generated by blackfriday. diff --git a/vendor/github.com/russross/blackfriday/v2/entities.go b/vendor/github.com/russross/blackfriday/v2/entities.go new file mode 100644 index 000000000..a2c3edb69 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/entities.go @@ -0,0 +1,2236 @@ +package blackfriday + +// Extracted from https://html.spec.whatwg.org/multipage/entities.json +var entities = map[string]bool{ + "Æ": true, + "Æ": true, + "&": true, + "&": true, + "Á": true, + "Á": true, + "Ă": true, + "Â": true, + "Â": true, + "А": true, + "𝔄": true, + "À": true, + "À": true, + "Α": true, + "Ā": true, + "⩓": true, + "Ą": true, + "𝔸": true, + "⁡": true, + "Å": true, + "Å": true, + "𝒜": true, + "≔": true, + "Ã": true, + "Ã": true, + "Ä": true, + "Ä": true, + "∖": true, + "⫧": true, + "⌆": true, + "Б": true, + "∵": true, + "ℬ": true, + "Β": true, + "𝔅": true, + "𝔹": true, + "˘": true, + "ℬ": true, + "≎": true, + "Ч": true, + "©": true, + "©": true, + "Ć": true, + "⋒": true, + "ⅅ": true, + "ℭ": true, + "Č": true, + "Ç": true, + "Ç": true, + "Ĉ": true, + "∰": true, + "Ċ": true, + "¸": true, + "·": true, + "ℭ": true, + "Χ": true, + "⊙": true, + "⊖": true, + "⊕": true, + "⊗": true, + "∲": true, + "”": true, + "’": true, + "∷": true, + "⩴": true, + "≡": true, + "∯": true, + "∮": true, + "ℂ": true, + "∐": true, + "∳": true, + "⨯": true, + "𝒞": true, + "⋓": true, + "≍": true, + "ⅅ": true, + "⤑": true, + "Ђ": true, + "Ѕ": true, + "Џ": true, + "‡": true, + "↡": true, + "⫤": true, + "Ď": true, + "Д": true, + "∇": true, + "Δ": true, + "𝔇": true, + "´": true, + "˙": true, + "˝": true, + "`": true, + "˜": true, + "⋄": true, + "ⅆ": true, + "𝔻": true, + "¨": true, + "⃜": true, + "≐": true, + "∯": true, + "¨": true, + "⇓": true, + "⇐": true, + "⇔": true, + "⫤": true, + "⟸": true, + "⟺": true, + "⟹": true, + "⇒": true, + "⊨": true, + "⇑": true, + "⇕": true, + "∥": true, + "↓": true, + "⤓": true, + "⇵": true, + "̑": true, + "⥐": true, + "⥞": true, + "↽": true, + "⥖": true, + "⥟": true, + "⇁": true, + "⥗": true, + "⊤": true, + "↧": true, + "⇓": true, + "𝒟": true, + "Đ": true, + "Ŋ": true, + "Ð": true, + "Ð": true, + "É": true, + "É": true, + "Ě": true, + "Ê": true, + "Ê": true, + "Э": true, + "Ė": true, + "𝔈": true, + "È": true, + "È": true, + "∈": true, + "Ē": true, + "◻": true, + "▫": true, + "Ę": true, + "𝔼": true, + "Ε": true, + "⩵": true, + "≂": true, + "⇌": true, + "ℰ": true, + "⩳": true, + "Η": true, + "Ë": true, + "Ë": true, + "∃": true, + "ⅇ": true, + "Ф": true, + "𝔉": true, + "◼": true, + "▪": true, + "𝔽": true, + "∀": true, + "ℱ": true, + "ℱ": true, + "Ѓ": true, + ">": true, + ">": true, + "Γ": true, + "Ϝ": true, + "Ğ": true, + "Ģ": true, + "Ĝ": true, + "Г": true, + "Ġ": true, + "𝔊": true, + "⋙": true, + "𝔾": true, + "≥": true, + "⋛": true, + "≧": true, + "⪢": true, + "≷": true, + "⩾": true, + "≳": true, + "𝒢": true, + "≫": true, + "Ъ": true, + "ˇ": true, + "^": true, + "Ĥ": true, + "ℌ": true, + "ℋ": true, + "ℍ": true, + "─": true, + "ℋ": true, + "Ħ": true, + "≎": true, + "≏": true, + "Е": true, + "IJ": true, + "Ё": true, + "Í": true, + "Í": true, + "Î": true, + "Î": true, + "И": true, + "İ": true, + "ℑ": true, + "Ì": true, + "Ì": true, + "ℑ": true, + "Ī": true, + "ⅈ": true, + "⇒": true, + "∬": true, + "∫": true, + "⋂": true, + "⁣": true, + "⁢": true, + "Į": true, + "𝕀": true, + "Ι": true, + "ℐ": true, + "Ĩ": true, + "І": true, + "Ï": true, + "Ï": true, + "Ĵ": true, + "Й": true, + "𝔍": true, + "𝕁": true, + "𝒥": true, + "Ј": true, + "Є": true, + "Х": true, + "Ќ": true, + "Κ": true, + "Ķ": true, + "К": true, + "𝔎": true, + "𝕂": true, + "𝒦": true, + "Љ": true, + "<": true, + "<": true, + "Ĺ": true, + "Λ": true, + "⟪": true, + "ℒ": true, + "↞": true, + "Ľ": true, + "Ļ": true, + "Л": true, + "⟨": true, + "←": true, + "⇤": true, + "⇆": true, + "⌈": true, + "⟦": true, + "⥡": true, + "⇃": true, + "⥙": true, + "⌊": true, + "↔": true, + "⥎": true, + "⊣": true, + "↤": true, + "⥚": true, + "⊲": true, + "⧏": true, + "⊴": true, + "⥑": true, + "⥠": true, + "↿": true, + "⥘": true, + "↼": true, + "⥒": true, + "⇐": true, + "⇔": true, + "⋚": true, + "≦": true, + "≶": true, + "⪡": true, + "⩽": true, + "≲": true, + "𝔏": true, + "⋘": true, + "⇚": true, + "Ŀ": true, + "⟵": true, + "⟷": true, + "⟶": true, + "⟸": true, + "⟺": true, + "⟹": true, + "𝕃": true, + "↙": true, + "↘": true, + "ℒ": true, + "↰": true, + "Ł": true, + "≪": true, + "⤅": true, + "М": true, + " ": true, + "ℳ": true, + "𝔐": true, + "∓": true, + "𝕄": true, + "ℳ": true, + "Μ": true, + "Њ": true, + "Ń": true, + "Ň": true, + "Ņ": true, + "Н": true, + "​": true, + "​": true, + "​": true, + "​": true, + "≫": true, + "≪": true, + " ": true, + "𝔑": true, + "⁠": true, + " ": true, + "ℕ": true, + "⫬": true, + "≢": true, + "≭": true, + "∦": true, + "∉": true, + "≠": true, + "≂̸": true, + "∄": true, + "≯": true, + "≱": true, + "≧̸": true, + "≫̸": true, + "≹": true, + "⩾̸": true, + "≵": true, + "≎̸": true, + "≏̸": true, + "⋪": true, + "⧏̸": true, + "⋬": true, + "≮": true, + "≰": true, + "≸": true, + "≪̸": true, + "⩽̸": true, + "≴": true, + "⪢̸": true, + "⪡̸": true, + "⊀": true, + "⪯̸": true, + "⋠": true, + "∌": true, + "⋫": true, + "⧐̸": true, + "⋭": true, + "⊏̸": true, + "⋢": true, + "⊐̸": true, + "⋣": true, + "⊂⃒": true, + "⊈": true, + "⊁": true, + "⪰̸": true, + "⋡": true, + "≿̸": true, + "⊃⃒": true, + "⊉": true, + "≁": true, + "≄": true, + "≇": true, + "≉": true, + "∤": true, + "𝒩": true, + "Ñ": true, + "Ñ": true, + "Ν": true, + "Œ": true, + "Ó": true, + "Ó": true, + "Ô": true, + "Ô": true, + "О": true, + "Ő": true, + "𝔒": true, + "Ò": true, + "Ò": true, + "Ō": true, + "Ω": true, + "Ο": true, + "𝕆": true, + "“": true, + "‘": true, + "⩔": true, + "𝒪": true, + "Ø": true, + "Ø": true, + "Õ": true, + "Õ": true, + "⨷": true, + "Ö": true, + "Ö": true, + "‾": true, + "⏞": true, + "⎴": true, + "⏜": true, + "∂": true, + "П": true, + "𝔓": true, + "Φ": true, + "Π": true, + "±": true, + "ℌ": true, + "ℙ": true, + "⪻": true, + "≺": true, + "⪯": true, + "≼": true, + "≾": true, + "″": true, + "∏": true, + "∷": true, + "∝": true, + "𝒫": true, + "Ψ": true, + """: true, + """: true, + "𝔔": true, + "ℚ": true, + "𝒬": true, + "⤐": true, + "®": true, + "®": true, + "Ŕ": true, + "⟫": true, + "↠": true, + "⤖": true, + "Ř": true, + "Ŗ": true, + "Р": true, + "ℜ": true, + "∋": true, + "⇋": true, + "⥯": true, + "ℜ": true, + "Ρ": true, + "⟩": true, + "→": true, + "⇥": true, + "⇄": true, + "⌉": true, + "⟧": true, + "⥝": true, + "⇂": true, + "⥕": true, + "⌋": true, + "⊢": true, + "↦": true, + "⥛": true, + "⊳": true, + "⧐": true, + "⊵": true, + "⥏": true, + "⥜": true, + "↾": true, + "⥔": true, + "⇀": true, + "⥓": true, + "⇒": true, + "ℝ": true, + "⥰": true, + "⇛": true, + "ℛ": true, + "↱": true, + "⧴": true, + "Щ": true, + "Ш": true, + "Ь": true, + "Ś": true, + "⪼": true, + "Š": true, + "Ş": true, + "Ŝ": true, + "С": true, + "𝔖": true, + "↓": true, + "←": true, + "→": true, + "↑": true, + "Σ": true, + "∘": true, + "𝕊": true, + "√": true, + "□": true, + "⊓": true, + "⊏": true, + "⊑": true, + "⊐": true, + "⊒": true, + "⊔": true, + "𝒮": true, + "⋆": true, + "⋐": true, + "⋐": true, + "⊆": true, + "≻": true, + "⪰": true, + "≽": true, + "≿": true, + "∋": true, + "∑": true, + "⋑": true, + "⊃": true, + "⊇": true, + "⋑": true, + "Þ": true, + "Þ": true, + "™": true, + "Ћ": true, + "Ц": true, + " ": true, + "Τ": true, + "Ť": true, + "Ţ": true, + "Т": true, + "𝔗": true, + "∴": true, + "Θ": true, + "  ": true, + " ": true, + "∼": true, + "≃": true, + "≅": true, + "≈": true, + "𝕋": true, + "⃛": true, + "𝒯": true, + "Ŧ": true, + "Ú": true, + "Ú": true, + "↟": true, + "⥉": true, + "Ў": true, + "Ŭ": true, + "Û": true, + "Û": true, + "У": true, + "Ű": true, + "𝔘": true, + "Ù": true, + "Ù": true, + "Ū": true, + "_": true, + "⏟": true, + "⎵": true, + "⏝": true, + "⋃": true, + "⊎": true, + "Ų": true, + "𝕌": true, + "↑": true, + "⤒": true, + "⇅": true, + "↕": true, + "⥮": true, + "⊥": true, + "↥": true, + "⇑": true, + "⇕": true, + "↖": true, + "↗": true, + "ϒ": true, + "Υ": true, + "Ů": true, + "𝒰": true, + "Ũ": true, + "Ü": true, + "Ü": true, + "⊫": true, + "⫫": true, + "В": true, + "⊩": true, + "⫦": true, + "⋁": true, + "‖": true, + "‖": true, + "∣": true, + "|": true, + "❘": true, + "≀": true, + " ": true, + "𝔙": true, + "𝕍": true, + "𝒱": true, + "⊪": true, + "Ŵ": true, + "⋀": true, + "𝔚": true, + "𝕎": true, + "𝒲": true, + "𝔛": true, + "Ξ": true, + "𝕏": true, + "𝒳": true, + "Я": true, + "Ї": true, + "Ю": true, + "Ý": true, + "Ý": true, + "Ŷ": true, + "Ы": true, + "𝔜": true, + "𝕐": true, + "𝒴": true, + "Ÿ": true, + "Ж": true, + "Ź": true, + "Ž": true, + "З": true, + "Ż": true, + "​": true, + "Ζ": true, + "ℨ": true, + "ℤ": true, + "𝒵": true, + "á": true, + "á": true, + "ă": true, + "∾": true, + "∾̳": true, + "∿": true, + "â": true, + "â": true, + "´": true, + "´": true, + "а": true, + "æ": true, + "æ": true, + "⁡": true, + "𝔞": true, + "à": true, + "à": true, + "ℵ": true, + "ℵ": true, + "α": true, + "ā": true, + "⨿": true, + "&": true, + "&": true, + "∧": true, + "⩕": true, + "⩜": true, + "⩘": true, + "⩚": true, + "∠": true, + "⦤": true, + "∠": true, + "∡": true, + "⦨": true, + "⦩": true, + "⦪": true, + "⦫": true, + "⦬": true, + "⦭": true, + "⦮": true, + "⦯": true, + "∟": true, + "⊾": true, + "⦝": true, + "∢": true, + "Å": true, + "⍼": true, + "ą": true, + "𝕒": true, + "≈": true, + "⩰": true, + "⩯": true, + "≊": true, + "≋": true, + "'": true, + "≈": true, + "≊": true, + "å": true, + "å": true, + "𝒶": true, + "*": true, + "≈": true, + "≍": true, + "ã": true, + "ã": true, + "ä": true, + "ä": true, + "∳": true, + "⨑": true, + "⫭": true, + "≌": true, + "϶": true, + "‵": true, + "∽": true, + "⋍": true, + "⊽": true, + "⌅": true, + "⌅": true, + "⎵": true, + "⎶": true, + "≌": true, + "б": true, + "„": true, + "∵": true, + "∵": true, + "⦰": true, + "϶": true, + "ℬ": true, + "β": true, + "ℶ": true, + "≬": true, + "𝔟": true, + "⋂": true, + "◯": true, + "⋃": true, + "⨀": true, + "⨁": true, + "⨂": true, + "⨆": true, + "★": true, + "▽": true, + "△": true, + "⨄": true, + "⋁": true, + "⋀": true, + "⤍": true, + "⧫": true, + "▪": true, + "▴": true, + "▾": true, + "◂": true, + "▸": true, + "␣": true, + "▒": true, + "░": true, + "▓": true, + "█": true, + "=⃥": true, + "≡⃥": true, + "⌐": true, + "𝕓": true, + "⊥": true, + "⊥": true, + "⋈": true, + "╗": true, + "╔": true, + "╖": true, + "╓": true, + "═": true, + "╦": true, + "╩": true, + "╤": true, + "╧": true, + "╝": true, + "╚": true, + "╜": true, + "╙": true, + "║": true, + "╬": true, + "╣": true, + "╠": true, + "╫": true, + "╢": true, + "╟": true, + "⧉": true, + "╕": true, + "╒": true, + "┐": true, + "┌": true, + "─": true, + "╥": true, + "╨": true, + "┬": true, + "┴": true, + "⊟": true, + "⊞": true, + "⊠": true, + "╛": true, + "╘": true, + "┘": true, + "└": true, + "│": true, + "╪": true, + "╡": true, + "╞": true, + "┼": true, + "┤": true, + "├": true, + "‵": true, + "˘": true, + "¦": true, + "¦": true, + "𝒷": true, + "⁏": true, + "∽": true, + "⋍": true, + "\": true, + "⧅": true, + "⟈": true, + "•": true, + "•": true, + "≎": true, + "⪮": true, + "≏": true, + "≏": true, + "ć": true, + "∩": true, + "⩄": true, + "⩉": true, + "⩋": true, + "⩇": true, + "⩀": true, + "∩︀": true, + "⁁": true, + "ˇ": true, + "⩍": true, + "č": true, + "ç": true, + "ç": true, + "ĉ": true, + "⩌": true, + "⩐": true, + "ċ": true, + "¸": true, + "¸": true, + "⦲": true, + "¢": true, + "¢": true, + "·": true, + "𝔠": true, + "ч": true, + "✓": true, + "✓": true, + "χ": true, + "○": true, + "⧃": true, + "ˆ": true, + "≗": true, + "↺": true, + "↻": true, + "®": true, + "Ⓢ": true, + "⊛": true, + "⊚": true, + "⊝": true, + "≗": true, + "⨐": true, + "⫯": true, + "⧂": true, + "♣": true, + "♣": true, + ":": true, + "≔": true, + "≔": true, + ",": true, + "@": true, + "∁": true, + "∘": true, + "∁": true, + "ℂ": true, + "≅": true, + "⩭": true, + "∮": true, + "𝕔": true, + "∐": true, + "©": true, + "©": true, + "℗": true, + "↵": true, + "✗": true, + "𝒸": true, + "⫏": true, + "⫑": true, + "⫐": true, + "⫒": true, + "⋯": true, + "⤸": true, + "⤵": true, + "⋞": true, + "⋟": true, + "↶": true, + "⤽": true, + "∪": true, + "⩈": true, + "⩆": true, + "⩊": true, + "⊍": true, + "⩅": true, + "∪︀": true, + "↷": true, + "⤼": true, + "⋞": true, + "⋟": true, + "⋎": true, + "⋏": true, + "¤": true, + "¤": true, + "↶": true, + "↷": true, + "⋎": true, + "⋏": true, + "∲": true, + "∱": true, + "⌭": true, + "⇓": true, + "⥥": true, + "†": true, + "ℸ": true, + "↓": true, + "‐": true, + "⊣": true, + "⤏": true, + "˝": true, + "ď": true, + "д": true, + "ⅆ": true, + "‡": true, + "⇊": true, + "⩷": true, + "°": true, + "°": true, + "δ": true, + "⦱": true, + "⥿": true, + "𝔡": true, + "⇃": true, + "⇂": true, + "⋄": true, + "⋄": true, + "♦": true, + "♦": true, + "¨": true, + "ϝ": true, + "⋲": true, + "÷": true, + "÷": true, + "÷": true, + "⋇": true, + "⋇": true, + "ђ": true, + "⌞": true, + "⌍": true, + "$": true, + "𝕕": true, + "˙": true, + "≐": true, + "≑": true, + "∸": true, + "∔": true, + "⊡": true, + "⌆": true, + "↓": true, + "⇊": true, + "⇃": true, + "⇂": true, + "⤐": true, + "⌟": true, + "⌌": true, + "𝒹": true, + "ѕ": true, + "⧶": true, + "đ": true, + "⋱": true, + "▿": true, + "▾": true, + "⇵": true, + "⥯": true, + "⦦": true, + "џ": true, + "⟿": true, + "⩷": true, + "≑": true, + "é": true, + "é": true, + "⩮": true, + "ě": true, + "≖": true, + "ê": true, + "ê": true, + "≕": true, + "э": true, + "ė": true, + "ⅇ": true, + "≒": true, + "𝔢": true, + "⪚": true, + "è": true, + "è": true, + "⪖": true, + "⪘": true, + "⪙": true, + "⏧": true, + "ℓ": true, + "⪕": true, + "⪗": true, + "ē": true, + "∅": true, + "∅": true, + "∅": true, + " ": true, + " ": true, + " ": true, + "ŋ": true, + " ": true, + "ę": true, + "𝕖": true, + "⋕": true, + "⧣": true, + "⩱": true, + "ε": true, + "ε": true, + "ϵ": true, + "≖": true, + "≕": true, + "≂": true, + "⪖": true, + "⪕": true, + "=": true, + "≟": true, + "≡": true, + "⩸": true, + "⧥": true, + "≓": true, + "⥱": true, + "ℯ": true, + "≐": true, + "≂": true, + "η": true, + "ð": true, + "ð": true, + "ë": true, + "ë": true, + "€": true, + "!": true, + "∃": true, + "ℰ": true, + "ⅇ": true, + "≒": true, + "ф": true, + "♀": true, + "ffi": true, + "ff": true, + "ffl": true, + "𝔣": true, + "fi": true, + "fj": true, + "♭": true, + "fl": true, + "▱": true, + "ƒ": true, + "𝕗": true, + "∀": true, + "⋔": true, + "⫙": true, + "⨍": true, + "½": true, + "½": true, + "⅓": true, + "¼": true, + "¼": true, + "⅕": true, + "⅙": true, + "⅛": true, + "⅔": true, + "⅖": true, + "¾": true, + "¾": true, + "⅗": true, + "⅜": true, + "⅘": true, + "⅚": true, + "⅝": true, + "⅞": true, + "⁄": true, + "⌢": true, + "𝒻": true, + "≧": true, + "⪌": true, + "ǵ": true, + "γ": true, + "ϝ": true, + "⪆": true, + "ğ": true, + "ĝ": true, + "г": true, + "ġ": true, + "≥": true, + "⋛": true, + "≥": true, + "≧": true, + "⩾": true, + "⩾": true, + "⪩": true, + "⪀": true, + "⪂": true, + "⪄": true, + "⋛︀": true, + "⪔": true, + "𝔤": true, + "≫": true, + "⋙": true, + "ℷ": true, + "ѓ": true, + "≷": true, + "⪒": true, + "⪥": true, + "⪤": true, + "≩": true, + "⪊": true, + "⪊": true, + "⪈": true, + "⪈": true, + "≩": true, + "⋧": true, + "𝕘": true, + "`": true, + "ℊ": true, + "≳": true, + "⪎": true, + "⪐": true, + ">": true, + ">": true, + "⪧": true, + "⩺": true, + "⋗": true, + "⦕": true, + "⩼": true, + "⪆": true, + "⥸": true, + "⋗": true, + "⋛": true, + "⪌": true, + "≷": true, + "≳": true, + "≩︀": true, + "≩︀": true, + "⇔": true, + " ": true, + "½": true, + "ℋ": true, + "ъ": true, + "↔": true, + "⥈": true, + "↭": true, + "ℏ": true, + "ĥ": true, + "♥": true, + "♥": true, + "…": true, + "⊹": true, + "𝔥": true, + "⤥": true, + "⤦": true, + "⇿": true, + "∻": true, + "↩": true, + "↪": true, + "𝕙": true, + "―": true, + "𝒽": true, + "ℏ": true, + "ħ": true, + "⁃": true, + "‐": true, + "í": true, + "í": true, + "⁣": true, + "î": true, + "î": true, + "и": true, + "е": true, + "¡": true, + "¡": true, + "⇔": true, + "𝔦": true, + "ì": true, + "ì": true, + "ⅈ": true, + "⨌": true, + "∭": true, + "⧜": true, + "℩": true, + "ij": true, + "ī": true, + "ℑ": true, + "ℐ": true, + "ℑ": true, + "ı": true, + "⊷": true, + "Ƶ": true, + "∈": true, + "℅": true, + "∞": true, + "⧝": true, + "ı": true, + "∫": true, + "⊺": true, + "ℤ": true, + "⊺": true, + "⨗": true, + "⨼": true, + "ё": true, + "į": true, + "𝕚": true, + "ι": true, + "⨼": true, + "¿": true, + "¿": true, + "𝒾": true, + "∈": true, + "⋹": true, + "⋵": true, + "⋴": true, + "⋳": true, + "∈": true, + "⁢": true, + "ĩ": true, + "і": true, + "ï": true, + "ï": true, + "ĵ": true, + "й": true, + "𝔧": true, + "ȷ": true, + "𝕛": true, + "𝒿": true, + "ј": true, + "є": true, + "κ": true, + "ϰ": true, + "ķ": true, + "к": true, + "𝔨": true, + "ĸ": true, + "х": true, + "ќ": true, + "𝕜": true, + "𝓀": true, + "⇚": true, + "⇐": true, + "⤛": true, + "⤎": true, + "≦": true, + "⪋": true, + "⥢": true, + "ĺ": true, + "⦴": true, + "ℒ": true, + "λ": true, + "⟨": true, + "⦑": true, + "⟨": true, + "⪅": true, + "«": true, + "«": true, + "←": true, + "⇤": true, + "⤟": true, + "⤝": true, + "↩": true, + "↫": true, + "⤹": true, + "⥳": true, + "↢": true, + "⪫": true, + "⤙": true, + "⪭": true, + "⪭︀": true, + "⤌": true, + "❲": true, + "{": true, + "[": true, + "⦋": true, + "⦏": true, + "⦍": true, + "ľ": true, + "ļ": true, + "⌈": true, + "{": true, + "л": true, + "⤶": true, + "“": true, + "„": true, + "⥧": true, + "⥋": true, + "↲": true, + "≤": true, + "←": true, + "↢": true, + "↽": true, + "↼": true, + "⇇": true, + "↔": true, + "⇆": true, + "⇋": true, + "↭": true, + "⋋": true, + "⋚": true, + "≤": true, + "≦": true, + "⩽": true, + "⩽": true, + "⪨": true, + "⩿": true, + "⪁": true, + "⪃": true, + "⋚︀": true, + "⪓": true, + "⪅": true, + "⋖": true, + "⋚": true, + "⪋": true, + "≶": true, + "≲": true, + "⥼": true, + "⌊": true, + "𝔩": true, + "≶": true, + "⪑": true, + "↽": true, + "↼": true, + "⥪": true, + "▄": true, + "љ": true, + "≪": true, + "⇇": true, + "⌞": true, + "⥫": true, + "◺": true, + "ŀ": true, + "⎰": true, + "⎰": true, + "≨": true, + "⪉": true, + "⪉": true, + "⪇": true, + "⪇": true, + "≨": true, + "⋦": true, + "⟬": true, + "⇽": true, + "⟦": true, + "⟵": true, + "⟷": true, + "⟼": true, + "⟶": true, + "↫": true, + "↬": true, + "⦅": true, + "𝕝": true, + "⨭": true, + "⨴": true, + "∗": true, + "_": true, + "◊": true, + "◊": true, + "⧫": true, + "(": true, + "⦓": true, + "⇆": true, + "⌟": true, + "⇋": true, + "⥭": true, + "‎": true, + "⊿": true, + "‹": true, + "𝓁": true, + "↰": true, + "≲": true, + "⪍": true, + "⪏": true, + "[": true, + "‘": true, + "‚": true, + "ł": true, + "<": true, + "<": true, + "⪦": true, + "⩹": true, + "⋖": true, + "⋋": true, + "⋉": true, + "⥶": true, + "⩻": true, + "⦖": true, + "◃": true, + "⊴": true, + "◂": true, + "⥊": true, + "⥦": true, + "≨︀": true, + "≨︀": true, + "∺": true, + "¯": true, + "¯": true, + "♂": true, + "✠": true, + "✠": true, + "↦": true, + "↦": true, + "↧": true, + "↤": true, + "↥": true, + "▮": true, + "⨩": true, + "м": true, + "—": true, + "∡": true, + "𝔪": true, + "℧": true, + "µ": true, + "µ": true, + "∣": true, + "*": true, + "⫰": true, + "·": true, + "·": true, + "−": true, + "⊟": true, + "∸": true, + "⨪": true, + "⫛": true, + "…": true, + "∓": true, + "⊧": true, + "𝕞": true, + "∓": true, + "𝓂": true, + "∾": true, + "μ": true, + "⊸": true, + "⊸": true, + "⋙̸": true, + "≫⃒": true, + "≫̸": true, + "⇍": true, + "⇎": true, + "⋘̸": true, + "≪⃒": true, + "≪̸": true, + "⇏": true, + "⊯": true, + "⊮": true, + "∇": true, + "ń": true, + "∠⃒": true, + "≉": true, + "⩰̸": true, + "≋̸": true, + "ʼn": true, + "≉": true, + "♮": true, + "♮": true, + "ℕ": true, + " ": true, + " ": true, + "≎̸": true, + "≏̸": true, + "⩃": true, + "ň": true, + "ņ": true, + "≇": true, + "⩭̸": true, + "⩂": true, + "н": true, + "–": true, + "≠": true, + "⇗": true, + "⤤": true, + "↗": true, + "↗": true, + "≐̸": true, + "≢": true, + "⤨": true, + "≂̸": true, + "∄": true, + "∄": true, + "𝔫": true, + "≧̸": true, + "≱": true, + "≱": true, + "≧̸": true, + "⩾̸": true, + "⩾̸": true, + "≵": true, + "≯": true, + "≯": true, + "⇎": true, + "↮": true, + "⫲": true, + "∋": true, + "⋼": true, + "⋺": true, + "∋": true, + "њ": true, + "⇍": true, + "≦̸": true, + "↚": true, + "‥": true, + "≰": true, + "↚": true, + "↮": true, + "≰": true, + "≦̸": true, + "⩽̸": true, + "⩽̸": true, + "≮": true, + "≴": true, + "≮": true, + "⋪": true, + "⋬": true, + "∤": true, + "𝕟": true, + "¬": true, + "¬": true, + "∉": true, + "⋹̸": true, + "⋵̸": true, + "∉": true, + "⋷": true, + "⋶": true, + "∌": true, + "∌": true, + "⋾": true, + "⋽": true, + "∦": true, + "∦": true, + "⫽⃥": true, + "∂̸": true, + "⨔": true, + "⊀": true, + "⋠": true, + "⪯̸": true, + "⊀": true, + "⪯̸": true, + "⇏": true, + "↛": true, + "⤳̸": true, + "↝̸": true, + "↛": true, + "⋫": true, + "⋭": true, + "⊁": true, + "⋡": true, + "⪰̸": true, + "𝓃": true, + "∤": true, + "∦": true, + "≁": true, + "≄": true, + "≄": true, + "∤": true, + "∦": true, + "⋢": true, + "⋣": true, + "⊄": true, + "⫅̸": true, + "⊈": true, + "⊂⃒": true, + "⊈": true, + "⫅̸": true, + "⊁": true, + "⪰̸": true, + "⊅": true, + "⫆̸": true, + "⊉": true, + "⊃⃒": true, + "⊉": true, + "⫆̸": true, + "≹": true, + "ñ": true, + "ñ": true, + "≸": true, + "⋪": true, + "⋬": true, + "⋫": true, + "⋭": true, + "ν": true, + "#": true, + "№": true, + " ": true, + "⊭": true, + "⤄": true, + "≍⃒": true, + "⊬": true, + "≥⃒": true, + ">⃒": true, + "⧞": true, + "⤂": true, + "≤⃒": true, + "<⃒": true, + "⊴⃒": true, + "⤃": true, + "⊵⃒": true, + "∼⃒": true, + "⇖": true, + "⤣": true, + "↖": true, + "↖": true, + "⤧": true, + "Ⓢ": true, + "ó": true, + "ó": true, + "⊛": true, + "⊚": true, + "ô": true, + "ô": true, + "о": true, + "⊝": true, + "ő": true, + "⨸": true, + "⊙": true, + "⦼": true, + "œ": true, + "⦿": true, + "𝔬": true, + "˛": true, + "ò": true, + "ò": true, + "⧁": true, + "⦵": true, + "Ω": true, + "∮": true, + "↺": true, + "⦾": true, + "⦻": true, + "‾": true, + "⧀": true, + "ō": true, + "ω": true, + "ο": true, + "⦶": true, + "⊖": true, + "𝕠": true, + "⦷": true, + "⦹": true, + "⊕": true, + "∨": true, + "↻": true, + "⩝": true, + "ℴ": true, + "ℴ": true, + "ª": true, + "ª": true, + "º": true, + "º": true, + "⊶": true, + "⩖": true, + "⩗": true, + "⩛": true, + "ℴ": true, + "ø": true, + "ø": true, + "⊘": true, + "õ": true, + "õ": true, + "⊗": true, + "⨶": true, + "ö": true, + "ö": true, + "⌽": true, + "∥": true, + "¶": true, + "¶": true, + "∥": true, + "⫳": true, + "⫽": true, + "∂": true, + "п": true, + "%": true, + ".": true, + "‰": true, + "⊥": true, + "‱": true, + "𝔭": true, + "φ": true, + "ϕ": true, + "ℳ": true, + "☎": true, + "π": true, + "⋔": true, + "ϖ": true, + "ℏ": true, + "ℎ": true, + "ℏ": true, + "+": true, + "⨣": true, + "⊞": true, + "⨢": true, + "∔": true, + "⨥": true, + "⩲": true, + "±": true, + "±": true, + "⨦": true, + "⨧": true, + "±": true, + "⨕": true, + "𝕡": true, + "£": true, + "£": true, + "≺": true, + "⪳": true, + "⪷": true, + "≼": true, + "⪯": true, + "≺": true, + "⪷": true, + "≼": true, + "⪯": true, + "⪹": true, + "⪵": true, + "⋨": true, + "≾": true, + "′": true, + "ℙ": true, + "⪵": true, + "⪹": true, + "⋨": true, + "∏": true, + "⌮": true, + "⌒": true, + "⌓": true, + "∝": true, + "∝": true, + "≾": true, + "⊰": true, + "𝓅": true, + "ψ": true, + " ": true, + "𝔮": true, + "⨌": true, + "𝕢": true, + "⁗": true, + "𝓆": true, + "ℍ": true, + "⨖": true, + "?": true, + "≟": true, + """: true, + """: true, + "⇛": true, + "⇒": true, + "⤜": true, + "⤏": true, + "⥤": true, + "∽̱": true, + "ŕ": true, + "√": true, + "⦳": true, + "⟩": true, + "⦒": true, + "⦥": true, + "⟩": true, + "»": true, + "»": true, + "→": true, + "⥵": true, + "⇥": true, + "⤠": true, + "⤳": true, + "⤞": true, + "↪": true, + "↬": true, + "⥅": true, + "⥴": true, + "↣": true, + "↝": true, + "⤚": true, + "∶": true, + "ℚ": true, + "⤍": true, + "❳": true, + "}": true, + "]": true, + "⦌": true, + "⦎": true, + "⦐": true, + "ř": true, + "ŗ": true, + "⌉": true, + "}": true, + "р": true, + "⤷": true, + "⥩": true, + "”": true, + "”": true, + "↳": true, + "ℜ": true, + "ℛ": true, + "ℜ": true, + "ℝ": true, + "▭": true, + "®": true, + "®": true, + "⥽": true, + "⌋": true, + "𝔯": true, + "⇁": true, + "⇀": true, + "⥬": true, + "ρ": true, + "ϱ": true, + "→": true, + "↣": true, + "⇁": true, + "⇀": true, + "⇄": true, + "⇌": true, + "⇉": true, + "↝": true, + "⋌": true, + "˚": true, + "≓": true, + "⇄": true, + "⇌": true, + "‏": true, + "⎱": true, + "⎱": true, + "⫮": true, + "⟭": true, + "⇾": true, + "⟧": true, + "⦆": true, + "𝕣": true, + "⨮": true, + "⨵": true, + ")": true, + "⦔": true, + "⨒": true, + "⇉": true, + "›": true, + "𝓇": true, + "↱": true, + "]": true, + "’": true, + "’": true, + "⋌": true, + "⋊": true, + "▹": true, + "⊵": true, + "▸": true, + "⧎": true, + "⥨": true, + "℞": true, + "ś": true, + "‚": true, + "≻": true, + "⪴": true, + "⪸": true, + "š": true, + "≽": true, + "⪰": true, + "ş": true, + "ŝ": true, + "⪶": true, + "⪺": true, + "⋩": true, + "⨓": true, + "≿": true, + "с": true, + "⋅": true, + "⊡": true, + "⩦": true, + "⇘": true, + "⤥": true, + "↘": true, + "↘": true, + "§": true, + "§": true, + ";": true, + "⤩": true, + "∖": true, + "∖": true, + "✶": true, + "𝔰": true, + "⌢": true, + "♯": true, + "щ": true, + "ш": true, + "∣": true, + "∥": true, + "­": true, + "­": true, + "σ": true, + "ς": true, + "ς": true, + "∼": true, + "⩪": true, + "≃": true, + "≃": true, + "⪞": true, + "⪠": true, + "⪝": true, + "⪟": true, + "≆": true, + "⨤": true, + "⥲": true, + "←": true, + "∖": true, + "⨳": true, + "⧤": true, + "∣": true, + "⌣": true, + "⪪": true, + "⪬": true, + "⪬︀": true, + "ь": true, + "/": true, + "⧄": true, + "⌿": true, + "𝕤": true, + "♠": true, + "♠": true, + "∥": true, + "⊓": true, + "⊓︀": true, + "⊔": true, + "⊔︀": true, + "⊏": true, + "⊑": true, + "⊏": true, + "⊑": true, + "⊐": true, + "⊒": true, + "⊐": true, + "⊒": true, + "□": true, + "□": true, + "▪": true, + "▪": true, + "→": true, + "𝓈": true, + "∖": true, + "⌣": true, + "⋆": true, + "☆": true, + "★": true, + "ϵ": true, + "ϕ": true, + "¯": true, + "⊂": true, + "⫅": true, + "⪽": true, + "⊆": true, + "⫃": true, + "⫁": true, + "⫋": true, + "⊊": true, + "⪿": true, + "⥹": true, + "⊂": true, + "⊆": true, + "⫅": true, + "⊊": true, + "⫋": true, + "⫇": true, + "⫕": true, + "⫓": true, + "≻": true, + "⪸": true, + "≽": true, + "⪰": true, + "⪺": true, + "⪶": true, + "⋩": true, + "≿": true, + "∑": true, + "♪": true, + "¹": true, + "¹": true, + "²": true, + "²": true, + "³": true, + "³": true, + "⊃": true, + "⫆": true, + "⪾": true, + "⫘": true, + "⊇": true, + "⫄": true, + "⟉": true, + "⫗": true, + "⥻": true, + "⫂": true, + "⫌": true, + "⊋": true, + "⫀": true, + "⊃": true, + "⊇": true, + "⫆": true, + "⊋": true, + "⫌": true, + "⫈": true, + "⫔": true, + "⫖": true, + "⇙": true, + "⤦": true, + "↙": true, + "↙": true, + "⤪": true, + "ß": true, + "ß": true, + "⌖": true, + "τ": true, + "⎴": true, + "ť": true, + "ţ": true, + "т": true, + "⃛": true, + "⌕": true, + "𝔱": true, + "∴": true, + "∴": true, + "θ": true, + "ϑ": true, + "ϑ": true, + "≈": true, + "∼": true, + " ": true, + "≈": true, + "∼": true, + "þ": true, + "þ": true, + "˜": true, + "×": true, + "×": true, + "⊠": true, + "⨱": true, + "⨰": true, + "∭": true, + "⤨": true, + "⊤": true, + "⌶": true, + "⫱": true, + "𝕥": true, + "⫚": true, + "⤩": true, + "‴": true, + "™": true, + "▵": true, + "▿": true, + "◃": true, + "⊴": true, + "≜": true, + "▹": true, + "⊵": true, + "◬": true, + "≜": true, + "⨺": true, + "⨹": true, + "⧍": true, + "⨻": true, + "⏢": true, + "𝓉": true, + "ц": true, + "ћ": true, + "ŧ": true, + "≬": true, + "↞": true, + "↠": true, + "⇑": true, + "⥣": true, + "ú": true, + "ú": true, + "↑": true, + "ў": true, + "ŭ": true, + "û": true, + "û": true, + "у": true, + "⇅": true, + "ű": true, + "⥮": true, + "⥾": true, + "𝔲": true, + "ù": true, + "ù": true, + "↿": true, + "↾": true, + "▀": true, + "⌜": true, + "⌜": true, + "⌏": true, + "◸": true, + "ū": true, + "¨": true, + "¨": true, + "ų": true, + "𝕦": true, + "↑": true, + "↕": true, + "↿": true, + "↾": true, + "⊎": true, + "υ": true, + "ϒ": true, + "υ": true, + "⇈": true, + "⌝": true, + "⌝": true, + "⌎": true, + "ů": true, + "◹": true, + "𝓊": true, + "⋰": true, + "ũ": true, + "▵": true, + "▴": true, + "⇈": true, + "ü": true, + "ü": true, + "⦧": true, + "⇕": true, + "⫨": true, + "⫩": true, + "⊨": true, + "⦜": true, + "ϵ": true, + "ϰ": true, + "∅": true, + "ϕ": true, + "ϖ": true, + "∝": true, + "↕": true, + "ϱ": true, + "ς": true, + "⊊︀": true, + "⫋︀": true, + "⊋︀": true, + "⫌︀": true, + "ϑ": true, + "⊲": true, + "⊳": true, + "в": true, + "⊢": true, + "∨": true, + "⊻": true, + "≚": true, + "⋮": true, + "|": true, + "|": true, + "𝔳": true, + "⊲": true, + "⊂⃒": true, + "⊃⃒": true, + "𝕧": true, + "∝": true, + "⊳": true, + "𝓋": true, + "⫋︀": true, + "⊊︀": true, + "⫌︀": true, + "⊋︀": true, + "⦚": true, + "ŵ": true, + "⩟": true, + "∧": true, + "≙": true, + "℘": true, + "𝔴": true, + "𝕨": true, + "℘": true, + "≀": true, + "≀": true, + "𝓌": true, + "⋂": true, + "◯": true, + "⋃": true, + "▽": true, + "𝔵": true, + "⟺": true, + "⟷": true, + "ξ": true, + "⟸": true, + "⟵": true, + "⟼": true, + "⋻": true, + "⨀": true, + "𝕩": true, + "⨁": true, + "⨂": true, + "⟹": true, + "⟶": true, + "𝓍": true, + "⨆": true, + "⨄": true, + "△": true, + "⋁": true, + "⋀": true, + "ý": true, + "ý": true, + "я": true, + "ŷ": true, + "ы": true, + "¥": true, + "¥": true, + "𝔶": true, + "ї": true, + "𝕪": true, + "𝓎": true, + "ю": true, + "ÿ": true, + "ÿ": true, + "ź": true, + "ž": true, + "з": true, + "ż": true, + "ℨ": true, + "ζ": true, + "𝔷": true, + "ж": true, + "⇝": true, + "𝕫": true, + "𝓏": true, + "‍": true, + "‌": true, +} diff --git a/vendor/github.com/russross/blackfriday/v2/esc.go b/vendor/github.com/russross/blackfriday/v2/esc.go index 6385f27cb..6ab60102c 100644 --- a/vendor/github.com/russross/blackfriday/v2/esc.go +++ b/vendor/github.com/russross/blackfriday/v2/esc.go @@ -13,13 +13,27 @@ var htmlEscaper = [256][]byte{ } func escapeHTML(w io.Writer, s []byte) { + escapeEntities(w, s, false) +} + +func escapeAllHTML(w io.Writer, s []byte) { + escapeEntities(w, s, true) +} + +func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) { var start, end int for end < len(s) { escSeq := htmlEscaper[s[end]] if escSeq != nil { - w.Write(s[start:end]) - w.Write(escSeq) - start = end + 1 + isEntity, entityEnd := nodeIsEntity(s, end) + if isEntity && !escapeValidEntities { + w.Write(s[start : entityEnd+1]) + start = entityEnd + 1 + } else { + w.Write(s[start:end]) + w.Write(escSeq) + start = end + 1 + } } end++ } @@ -28,6 +42,28 @@ func escapeHTML(w io.Writer, s []byte) { } } +func nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) { + isEntity = false + endEntityPos = end + 1 + + if s[end] == '&' { + for endEntityPos < len(s) { + if s[endEntityPos] == ';' { + if entities[string(s[end:endEntityPos+1])] { + isEntity = true + break + } + } + if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' { + break + } + endEntityPos++ + } + } + + return isEntity, endEntityPos +} + func escLink(w io.Writer, text []byte) { unesc := html.UnescapeString(string(text)) escapeHTML(w, []byte(unesc)) diff --git a/vendor/github.com/russross/blackfriday/v2/html.go b/vendor/github.com/russross/blackfriday/v2/html.go index 284c87184..cb4f26e30 100644 --- a/vendor/github.com/russross/blackfriday/v2/html.go +++ b/vendor/github.com/russross/blackfriday/v2/html.go @@ -132,7 +132,10 @@ func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer { } if params.FootnoteReturnLinkContents == "" { - params.FootnoteReturnLinkContents = `[return]` + // U+FE0E is VARIATION SELECTOR-15. + // It suppresses automatic emoji presentation of the preceding + // U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS. + params.FootnoteReturnLinkContents = "↩\ufe0e" } return &HTMLRenderer{ @@ -616,7 +619,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt } case Code: r.out(w, codeTag) - escapeHTML(w, node.Literal) + escapeAllHTML(w, node.Literal) r.out(w, codeCloseTag) case Document: break @@ -762,7 +765,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt r.cr(w) r.out(w, preTag) r.tag(w, codeTag[:len(codeTag)-1], attrs) - escapeHTML(w, node.Literal) + escapeAllHTML(w, node.Literal) r.out(w, codeCloseTag) r.out(w, preCloseTag) if node.Parent.Type != Item { diff --git a/vendor/github.com/russross/blackfriday/v2/inline.go b/vendor/github.com/russross/blackfriday/v2/inline.go index 4ed290792..d45bd9417 100644 --- a/vendor/github.com/russross/blackfriday/v2/inline.go +++ b/vendor/github.com/russross/blackfriday/v2/inline.go @@ -278,7 +278,7 @@ func link(p *Markdown, data []byte, offset int) (int, *Node) { case data[i] == '\n': textHasNl = true - case data[i-1] == '\\': + case isBackslashEscaped(data, i): continue case data[i] == '[': diff --git a/vendor/github.com/russross/blackfriday/v2/node.go b/vendor/github.com/russross/blackfriday/v2/node.go index 51b9e8c1b..04e6050ce 100644 --- a/vendor/github.com/russross/blackfriday/v2/node.go +++ b/vendor/github.com/russross/blackfriday/v2/node.go @@ -199,7 +199,8 @@ func (n *Node) InsertBefore(sibling *Node) { } } -func (n *Node) isContainer() bool { +// IsContainer returns true if 'n' can contain children. +func (n *Node) IsContainer() bool { switch n.Type { case Document: fallthrough @@ -238,6 +239,11 @@ func (n *Node) isContainer() bool { } } +// IsLeaf returns true if 'n' is a leaf node. +func (n *Node) IsLeaf() bool { + return !n.IsContainer() +} + func (n *Node) canContain(t NodeType) bool { if n.Type == List { return t == Item @@ -309,11 +315,11 @@ func newNodeWalker(root *Node) *nodeWalker { } func (nw *nodeWalker) next() { - if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root { + if (!nw.current.IsContainer() || !nw.entering) && nw.current == nw.root { nw.current = nil return } - if nw.entering && nw.current.isContainer() { + if nw.entering && nw.current.IsContainer() { if nw.current.FirstChild != nil { nw.current = nw.current.FirstChild nw.entering = true diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml b/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml deleted file mode 100644 index 93b1fcdb3..000000000 --- a/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -sudo: false -language: go -go: - - 1.x - - master -matrix: - allow_failures: - - go: master - fast_finish: true -install: - - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d -s .) - - go tool vet . - - go test -v -race ./... diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE b/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE deleted file mode 100644 index c35c17af9..000000000 --- a/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2015 Dmitri Shuralyov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/README.md b/vendor/github.com/shurcooL/sanitized_anchor_name/README.md deleted file mode 100644 index 670bf0fe6..000000000 --- a/vendor/github.com/shurcooL/sanitized_anchor_name/README.md +++ /dev/null @@ -1,36 +0,0 @@ -sanitized_anchor_name -===================== - -[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name) - -Package sanitized_anchor_name provides a func to create sanitized anchor names. - -Its logic can be reused by multiple packages to create interoperable anchor names -and links to those anchors. - -At this time, it does not try to ensure that generated anchor names -are unique, that responsibility falls on the caller. - -Installation ------------- - -```bash -go get -u github.com/shurcooL/sanitized_anchor_name -``` - -Example -------- - -```Go -anchorName := sanitized_anchor_name.Create("This is a header") - -fmt.Println(anchorName) - -// Output: -// this-is-a-header -``` - -License -------- - -- [MIT License](LICENSE) diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/main.go b/vendor/github.com/shurcooL/sanitized_anchor_name/main.go deleted file mode 100644 index 6a77d1243..000000000 --- a/vendor/github.com/shurcooL/sanitized_anchor_name/main.go +++ /dev/null @@ -1,29 +0,0 @@ -// Package sanitized_anchor_name provides a func to create sanitized anchor names. -// -// Its logic can be reused by multiple packages to create interoperable anchor names -// and links to those anchors. -// -// At this time, it does not try to ensure that generated anchor names -// are unique, that responsibility falls on the caller. -package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name" - -import "unicode" - -// Create returns a sanitized anchor name for the given text. -func Create(text string) string { - var anchorName []rune - var futureDash = false - for _, r := range text { - switch { - case unicode.IsLetter(r) || unicode.IsNumber(r): - if futureDash && len(anchorName) > 0 { - anchorName = append(anchorName, '-') - } - futureDash = false - anchorName = append(anchorName, unicode.ToLower(r)) - default: - futureDash = true - } - } - return string(anchorName) -} diff --git a/vendor/github.com/urfave/cli/v2/.gitignore b/vendor/github.com/urfave/cli/v2/.gitignore index 2d5e149b4..c04fcc538 100644 --- a/vendor/github.com/urfave/cli/v2/.gitignore +++ b/vendor/github.com/urfave/cli/v2/.gitignore @@ -1,7 +1,10 @@ *.coverprofile *.orig -node_modules/ vendor .idea internal/*/built-example coverage.txt +/.local/ +/site/ + +*.exe diff --git a/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md b/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md index 41ba294f6..9fee14807 100644 --- a/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md +++ b/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md @@ -55,11 +55,12 @@ 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. +reported by contacting urfave-governance@googlegroups.com, a members-only group +that is world-postable. 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 diff --git a/vendor/github.com/urfave/cli/v2/LICENSE b/vendor/github.com/urfave/cli/v2/LICENSE index 42a597e29..2c84c78a1 100644 --- a/vendor/github.com/urfave/cli/v2/LICENSE +++ b/vendor/github.com/urfave/cli/v2/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2016 Jeremy Saenz & Contributors +Copyright (c) 2022 urfave/cli maintainers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/urfave/cli/v2/Makefile b/vendor/github.com/urfave/cli/v2/Makefile new file mode 100644 index 000000000..3b0e5e0bb --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/Makefile @@ -0,0 +1,40 @@ +# NOTE: this Makefile is meant to provide a simplified entry point for humans to +# run all of the critical steps to verify one's changes are harmonious in +# nature. Keeping target bodies to one line each and abstaining from make magic +# are very important so that maintainers and contributors can focus their +# attention on files that are primarily Go. + +.PHONY: all +all: generate vet tag-test test check-binary-size tag-check-binary-size gfmrun v2diff + +# NOTE: this is a special catch-all rule to run any of the commands +# defined in internal/build/build.go with optional arguments passed +# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.: +# +# $ make test GFLAGS='--packages cli' +%: + go run internal/build/build.go $(GFLAGS) $* $(FLAGS) + +.PHONY: tag-test +tag-test: + go run internal/build/build.go -tags urfave_cli_no_docs test + +.PHONY: tag-check-binary-size +tag-check-binary-size: + go run internal/build/build.go -tags urfave_cli_no_docs check-binary-size + +.PHONY: gfmrun +gfmrun: + go run internal/build/build.go gfmrun docs/v2/manual.md + +.PHONY: docs +docs: + mkdocs build + +.PHONY: docs-deps +docs-deps: + pip install -r mkdocs-requirements.txt + +.PHONY: serve-docs +serve-docs: + mkdocs serve diff --git a/vendor/github.com/urfave/cli/v2/README.md b/vendor/github.com/urfave/cli/v2/README.md index c9237fbc6..eaed35630 100644 --- a/vendor/github.com/urfave/cli/v2/README.md +++ b/vendor/github.com/urfave/cli/v2/README.md @@ -1,66 +1,19 @@ -cli -=== +# cli -[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://pkg.go.dev/github.com/urfave/cli/v2) [![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) -[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli) +[![codecov](https://codecov.io/gh/urfave/cli/branch/main/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 applications in an expressive way. -## Usage Documentation +## Documentation -Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `master` branch, which is currently `v2`. +More documentation is available in [`./docs`](./docs) or the hosted +documentation site at . -- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md) -- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md) +## License -## Installation - -Make sure you have a working Go environment. Go version 1.11+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html). - -Go Modules are strongly recommended when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). - -### Using `v2` releases - -``` -$ GO111MODULE=on go get github.com/urfave/cli/v2 -``` - -```go -... -import ( - "github.com/urfave/cli/v2" // imports as package "cli" -) -... -``` - -### Using `v1` releases - -``` -$ GO111MODULE=on go get github.com/urfave/cli -``` - -```go -... -import ( - "github.com/urfave/cli" -) -... -``` - -### GOPATH - -Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can -be easily used: -``` -export PATH=$PATH:$GOPATH/bin -``` - -### Supported platforms - -cli is tested against multiple versions of Go on Linux, and against the latest -released version of Go on OS X and Windows. This project uses Github Actions for -builds. For more build info, please look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml). +See [`LICENSE`](./LICENSE) diff --git a/vendor/github.com/urfave/cli/v2/app.go b/vendor/github.com/urfave/cli/v2/app.go index d0c8f84e2..7e64c2d9f 100644 --- a/vendor/github.com/urfave/cli/v2/app.go +++ b/vendor/github.com/urfave/cli/v2/app.go @@ -7,17 +7,24 @@ import ( "io" "os" "path/filepath" + "reflect" "sort" "time" ) +const suggestDidYouMeanTemplate = "Did you mean %q?" + var ( - changeLogURL = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md" + changeLogURL = "https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md" appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", 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) + + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand + SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate ) // App is the main structure of a cli application. It is recommended that @@ -37,6 +44,9 @@ type App struct { Version string // Description of the program Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string // List of commands to execute Commands []*Command // List of flags to parse @@ -52,6 +62,8 @@ type App struct { HideVersion bool // categories contains the categorized commands and is populated on app startup categories CommandCategories + // flagCategories contains the categorized flags and is populated on app startup + flagCategories FlagCategories // An action to execute when the shell completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready @@ -64,7 +76,7 @@ type App struct { Action ActionFunc // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc - // Execute this function if an usage error occurs + // Execute this function if a usage error occurs OnUsageError OnUsageErrorFunc // Compilation date Compiled time.Time @@ -72,12 +84,15 @@ type App struct { Authors []*Author // Copyright of the binary if any Copyright string + // Reader reader to write input to (useful for tests) + Reader io.Reader // Writer writer to write output to 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 processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} @@ -91,10 +106,16 @@ type App struct { // single-character bool arguments into one // i.e. foobar -o -v -> foobar -ov UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool didSetup bool } +type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string + +type SuggestCommandFunc func(commands []*Command, provided string) string + // Tries to find out when this binary was compiled. // Returns the current time if it fails to find it. func compileTime() time.Time { @@ -116,7 +137,9 @@ func NewApp() *App { BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), + Reader: os.Stdin, Writer: os.Stdout, + ErrWriter: os.Stderr, } } @@ -135,7 +158,7 @@ func (a *App) Setup() { } if a.HelpName == "" { - a.HelpName = filepath.Base(os.Args[0]) + a.HelpName = a.Name } if a.Usage == "" { @@ -158,16 +181,26 @@ func (a *App) Setup() { a.Compiled = compileTime() } + if a.Reader == nil { + a.Reader = os.Stdin + } + if a.Writer == nil { a.Writer = os.Stdout } + if a.ErrWriter == nil { + a.ErrWriter = os.Stderr + } + var newCommands []*Command for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } + + c.flagCategories = newFlagCategoriesFromFlags(c.Flags) newCommands = append(newCommands, c) } a.Commands = newCommands @@ -192,12 +225,15 @@ func (a *App) Setup() { } sort.Sort(a.categories.(*commandCategories)) - if a.Metadata == nil { - a.Metadata = make(map[string]interface{}) + a.flagCategories = newFlagCategories() + for _, fl := range a.Flags { + if cf, ok := fl.(CategorizableFlag); ok { + a.flagCategories.AddFlag(cf.GetCategory(), cf) + } } - if a.Writer == nil { - a.Writer = os.Stdout + if a.Metadata == nil { + a.Metadata = make(map[string]interface{}) } } @@ -236,48 +272,53 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { err = parseIter(set, a, arguments[1:], shellComplete) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, &Context{Context: ctx}) + cCtx := NewContext(a, set, &Context{Context: ctx}) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) - _ = ShowAppHelp(context) + _ = ShowAppHelp(cCtx) return nerr } - context.shellComplete = shellComplete + cCtx.shellComplete = shellComplete - if checkCompletions(context) { + if checkCompletions(cCtx) { return nil } if err != nil { if a.OnUsageError != nil { - err := a.OnUsageError(context, err, false) - a.handleExitCoder(context, err) + err := a.OnUsageError(cCtx, err, false) + a.handleExitCoder(cCtx, err) return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - _ = ShowAppHelp(context) + if a.Suggest { + if suggestion, err := a.suggestFlagFromError(err, ""); err == nil { + fmt.Fprintf(a.Writer, suggestion) + } + } + _ = ShowAppHelp(cCtx) return err } - if !a.HideHelp && checkHelp(context) { - _ = ShowAppHelp(context) + if !a.HideHelp && checkHelp(cCtx) { + _ = ShowAppHelp(cCtx) return nil } - if !a.HideVersion && checkVersion(context) { - ShowVersion(context) + if !a.HideVersion && checkVersion(cCtx) { + ShowVersion(cCtx) return nil } - cerr := checkRequiredFlags(a.Flags, context) + cerr := cCtx.checkRequiredFlags(a.Flags) if cerr != nil { - _ = ShowAppHelp(context) + _ = ShowAppHelp(cCtx) return cerr } if a.After != nil { defer func() { - if afterErr := a.After(context); afterErr != nil { + if afterErr := a.After(cCtx); afterErr != nil { if err != nil { err = newMultiError(err, afterErr) } else { @@ -288,23 +329,53 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } if a.Before != nil { - beforeErr := a.Before(context) + beforeErr := a.Before(cCtx) if beforeErr != nil { - _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) - _ = ShowAppHelp(context) - a.handleExitCoder(context, beforeErr) + a.handleExitCoder(cCtx, beforeErr) err = beforeErr return err } } - args := context.Args() + var c *Command + args := cCtx.Args() if args.Present() { name := args.First() - c := a.Command(name) - if c != nil { - return c.Run(context) + if a.validCommandName(name) { + c = a.Command(name) + } else { + hasDefault := a.DefaultCommand != "" + isFlagName := checkStringSliceIncludes(name, cCtx.FlagNames()) + + var ( + isDefaultSubcommand = false + defaultHasSubcommands = false + ) + + if hasDefault { + dc := a.Command(a.DefaultCommand) + defaultHasSubcommands = len(dc.Subcommands) > 0 + for _, dcSub := range dc.Subcommands { + if checkStringSliceIncludes(name, dcSub.Names()) { + isDefaultSubcommand = true + break + } + } + } + + if isFlagName || (hasDefault && (defaultHasSubcommands && isDefaultSubcommand)) { + argsWithDefault := a.argsWithDefaultCommand(args) + if !reflect.DeepEqual(args, argsWithDefault) { + c = a.Command(argsWithDefault.First()) + } + } } + } else if a.DefaultCommand != "" { + c = a.Command(a.DefaultCommand) + } + + if c != nil { + return c.Run(cCtx) } if a.Action == nil { @@ -312,20 +383,43 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { } // Run default Action - err = a.Action(context) + err = a.Action(cCtx) - a.handleExitCoder(context, err) + a.handleExitCoder(cCtx, err) return err } +func (a *App) suggestFlagFromError(err error, command string) (string, error) { + flag, parseErr := flagFromError(err) + if parseErr != nil { + return "", err + } + + flags := a.Flags + if command != "" { + cmd := a.Command(command) + if cmd == nil { + return "", err + } + flags = cmd.Flags + } + + suggestion := SuggestFlag(flags, flag, a.HideHelp) + if len(suggestion) == 0 { + return "", err + } + + return fmt.Sprintf(SuggestDidYouMeanTemplate+"\n\n", suggestion), nil +} + // 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 +// to cli.App.Run. This will cause the application to exit with the given error // code in the cli.ExitCoder func (a *App) RunAndExitOnError() { if err := a.Run(os.Args); err != nil { - _, _ = fmt.Fprintln(a.errWriter(), err) + _, _ = fmt.Fprintln(a.ErrWriter, err) OsExiter(1) } } @@ -352,55 +446,60 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, ctx) + cCtx := NewContext(a, set, ctx) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) _, _ = fmt.Fprintln(a.Writer) if len(a.Commands) > 0 { - _ = ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(cCtx) } else { - _ = ShowCommandHelp(ctx, context.Args().First()) + _ = ShowCommandHelp(ctx, cCtx.Args().First()) } return nerr } - if checkCompletions(context) { + if checkCompletions(cCtx) { return nil } if err != nil { if a.OnUsageError != nil { - err = a.OnUsageError(context, err, true) - a.handleExitCoder(context, err) + err = a.OnUsageError(cCtx, err, true) + a.handleExitCoder(cCtx, err) return err } _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - _ = ShowSubcommandHelp(context) + if a.Suggest { + if suggestion, err := a.suggestFlagFromError(err, cCtx.Command.Name); err == nil { + fmt.Fprintf(a.Writer, suggestion) + } + } + _ = ShowSubcommandHelp(cCtx) return err } if len(a.Commands) > 0 { - if checkSubcommandHelp(context) { + if checkSubcommandHelp(cCtx) { return nil } } else { - if checkCommandHelp(ctx, context.Args().First()) { + if checkCommandHelp(ctx, cCtx.Args().First()) { return nil } } - cerr := checkRequiredFlags(a.Flags, context) + cerr := cCtx.checkRequiredFlags(a.Flags) if cerr != nil { - _ = ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(cCtx) return cerr } if a.After != nil { defer func() { - afterErr := a.After(context) + afterErr := a.After(cCtx) if afterErr != nil { - a.handleExitCoder(context, err) + a.handleExitCoder(cCtx, err) if err != nil { err = newMultiError(err, afterErr) } else { @@ -411,27 +510,27 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } if a.Before != nil { - beforeErr := a.Before(context) + beforeErr := a.Before(cCtx) if beforeErr != nil { - a.handleExitCoder(context, beforeErr) + a.handleExitCoder(cCtx, beforeErr) err = beforeErr return err } } - args := context.Args() + args := cCtx.Args() if args.Present() { name := args.First() c := a.Command(name) if c != nil { - return c.Run(context) + return c.Run(cCtx) } } // Run default Action - err = a.Action(context) + err = a.Action(cCtx) - a.handleExitCoder(context, err) + a.handleExitCoder(cCtx, err) return err } @@ -474,20 +573,19 @@ func (a *App) VisibleCommands() []*Command { return ret } +// VisibleFlagCategories returns a slice containing all the categories with the flags they contain +func (a *App) VisibleFlagCategories() []VisibleFlagCategory { + if a.flagCategories == nil { + return []VisibleFlagCategory{} + } + return a.flagCategories.VisibleCategories() +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (a *App) VisibleFlags() []Flag { return visibleFlags(a.Flags) } -func (a *App) errWriter() io.Writer { - // When the app ErrWriter is nil use the package level one. - if a.ErrWriter == nil { - return ErrWriter - } - - return a.ErrWriter -} - func (a *App) appendFlag(fl Flag) { if !hasFlag(a.Flags, fl) { a.Flags = append(a.Flags, fl) @@ -500,14 +598,49 @@ func (a *App) appendCommand(c *Command) { } } -func (a *App) handleExitCoder(context *Context, err error) { +func (a *App) handleExitCoder(cCtx *Context, err error) { if a.ExitErrHandler != nil { - a.ExitErrHandler(context, err) + a.ExitErrHandler(cCtx, err) } else { HandleExitCoder(err) } } +func (a *App) commandNames() []string { + var cmdNames []string + + for _, cmd := range a.Commands { + cmdNames = append(cmdNames, cmd.Names()...) + } + + return cmdNames +} + +func (a *App) validCommandName(checkCmdName string) bool { + valid := false + allCommandNames := a.commandNames() + + for _, cmdName := range allCommandNames { + if checkCmdName == cmdName { + valid = true + break + } + } + + return valid +} + +func (a *App) argsWithDefaultCommand(oldArgs Args) Args { + if a.DefaultCommand != "" { + rawArgs := append([]string{a.DefaultCommand}, oldArgs.Slice()...) + newArgs := args(rawArgs) + + return &newArgs + } + + return oldArgs +} + // Author represents someone who has contributed to a cli project. type Author struct { Name string // The Authors name @@ -527,16 +660,28 @@ func (a *Author) String() string { // 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) { +func HandleAction(action interface{}, cCtx *Context) (err error) { switch a := action.(type) { case ActionFunc: - return a(context) + return a(cCtx) case func(*Context) error: - return a(context) + return a(cCtx) case func(*Context): // deprecated function signature - a(context) + a(cCtx) return nil } return errInvalidActionType } + +func checkStringSliceIncludes(want string, sSlice []string) bool { + found := false + for _, s := range sSlice { + if want == s { + found = true + break + } + } + + return found +} diff --git a/vendor/github.com/urfave/cli/v2/category.go b/vendor/github.com/urfave/cli/v2/category.go index 867e3908c..8bf325e20 100644 --- a/vendor/github.com/urfave/cli/v2/category.go +++ b/vendor/github.com/urfave/cli/v2/category.go @@ -1,10 +1,12 @@ package cli +import "sort" + // CommandCategories interface allows for category manipulation type CommandCategories interface { // AddCommand adds a command to a category, creating a new category if necessary. AddCommand(category string, command *Command) - // categories returns a copy of the category slice + // Categories returns a slice of categories sorted by name Categories() []CommandCategory } @@ -77,3 +79,93 @@ func (c *commandCategory) VisibleCommands() []*Command { } return ret } + +// FlagCategories interface allows for category manipulation +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory +} + +type defaultFlagCategories struct { + m map[string]*defaultVisibleFlagCategory +} + +func newFlagCategories() FlagCategories { + return &defaultFlagCategories{ + m: map[string]*defaultVisibleFlagCategory{}, + } +} + +func newFlagCategoriesFromFlags(fs []Flag) FlagCategories { + fc := newFlagCategories() + for _, fl := range fs { + if cf, ok := fl.(CategorizableFlag); ok { + fc.AddFlag(cf.GetCategory(), cf) + } + } + + return fc +} + +func (f *defaultFlagCategories) AddFlag(category string, fl Flag) { + if _, ok := f.m[category]; !ok { + f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}} + } + + f.m[category].m[fl.String()] = fl +} + +func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory { + catNames := []string{} + for name := range f.m { + catNames = append(catNames, name) + } + + sort.Strings(catNames) + + ret := make([]VisibleFlagCategory, len(catNames)) + for i, name := range catNames { + ret[i] = f.m[name] + } + + return ret +} + +// VisibleFlagCategory is a category containing flags. +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + +type defaultVisibleFlagCategory struct { + name string + m map[string]Flag +} + +func (fc *defaultVisibleFlagCategory) Name() string { + return fc.name +} + +func (fc *defaultVisibleFlagCategory) Flags() []VisibleFlag { + vfNames := []string{} + for flName, fl := range fc.m { + if vf, ok := fl.(VisibleFlag); ok { + if vf.IsVisible() { + vfNames = append(vfNames, flName) + } + } + } + + sort.Strings(vfNames) + + ret := make([]VisibleFlag, len(vfNames)) + for i, flName := range vfNames { + ret[i] = fc.m[flName].(VisibleFlag) + } + + return ret +} diff --git a/vendor/github.com/urfave/cli/v2/cli.go b/vendor/github.com/urfave/cli/v2/cli.go index 62a5bc22d..2a11c5ad4 100644 --- a/vendor/github.com/urfave/cli/v2/cli.go +++ b/vendor/github.com/urfave/cli/v2/cli.go @@ -20,4 +20,4 @@ // } package cli -//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go +//go:generate go run internal/genflags/cmd/genflags/main.go diff --git a/vendor/github.com/urfave/cli/v2/command.go b/vendor/github.com/urfave/cli/v2/command.go index 95840f32e..2cafd8e0e 100644 --- a/vendor/github.com/urfave/cli/v2/command.go +++ b/vendor/github.com/urfave/cli/v2/command.go @@ -38,7 +38,8 @@ type Command struct { // List of child commands Subcommands []*Command // List of flags to parse - Flags []Flag + Flags []Flag + flagCategories FlagCategories // Treat all flags as normal arguments if true SkipFlagParsing bool // Boolean to hide built-in help command and help flag @@ -105,39 +106,44 @@ func (c *Command) Run(ctx *Context) (err error) { set, err := c.parseFlags(ctx.Args(), ctx.shellComplete) - context := NewContext(ctx.App, set, ctx) - context.Command = c - if checkCommandCompletions(context, c.Name) { + cCtx := NewContext(ctx.App, set, ctx) + cCtx.Command = c + if checkCommandCompletions(cCtx, c.Name) { return nil } if err != nil { if c.OnUsageError != nil { - err = c.OnUsageError(context, err, false) - context.App.handleExitCoder(context, err) + err = c.OnUsageError(cCtx, err, false) + cCtx.App.handleExitCoder(cCtx, err) return err } - _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) - _, _ = fmt.Fprintln(context.App.Writer) - _ = ShowCommandHelp(context, c.Name) + _, _ = fmt.Fprintln(cCtx.App.Writer, "Incorrect Usage:", err.Error()) + _, _ = fmt.Fprintln(cCtx.App.Writer) + if ctx.App.Suggest { + if suggestion, err := ctx.App.suggestFlagFromError(err, c.Name); err == nil { + fmt.Fprintf(cCtx.App.Writer, suggestion) + } + } + _ = ShowCommandHelp(cCtx, c.Name) return err } - if checkCommandHelp(context, c.Name) { + if checkCommandHelp(cCtx, c.Name) { return nil } - cerr := checkRequiredFlags(c.Flags, context) + cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { - _ = ShowCommandHelp(context, c.Name) + _ = ShowCommandHelp(cCtx, c.Name) return cerr } if c.After != nil { defer func() { - afterErr := c.After(context) + afterErr := c.After(cCtx) if afterErr != nil { - context.App.handleExitCoder(context, err) + cCtx.App.handleExitCoder(cCtx, err) if err != nil { err = newMultiError(err, afterErr) } else { @@ -148,10 +154,9 @@ func (c *Command) Run(ctx *Context) (err error) { } if c.Before != nil { - err = c.Before(context) + err = c.Before(cCtx) if err != nil { - _ = ShowCommandHelp(context, c.Name) - context.App.handleExitCoder(context, err) + cCtx.App.handleExitCoder(cCtx, err) return err } } @@ -160,11 +165,11 @@ func (c *Command) Run(ctx *Context) (err error) { c.Action = helpSubcommand.Action } - context.Command = c - err = c.Action(context) + cCtx.Command = c + err = c.Action(cCtx) if err != nil { - context.App.handleExitCoder(context, err) + cCtx.App.handleExitCoder(cCtx, err) } return err } @@ -228,6 +233,7 @@ func (c *Command) startApp(ctx *Context) error { } app.Usage = c.Usage + app.UsageText = c.UsageText app.Description = c.Description app.ArgsUsage = c.ArgsUsage @@ -242,12 +248,14 @@ func (c *Command) startApp(ctx *Context) error { app.HideHelpCommand = c.HideHelpCommand app.Version = ctx.App.Version - app.HideVersion = ctx.App.HideVersion + app.HideVersion = true app.Compiled = ctx.App.Compiled + app.Reader = ctx.App.Reader app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter app.ExitErrHandler = ctx.App.ExitErrHandler app.UseShortOptionHandling = ctx.App.UseShortOptionHandling + app.Suggest = ctx.App.Suggest app.categories = newCommandCategories() for _, command := range c.Subcommands { @@ -279,6 +287,14 @@ func (c *Command) startApp(ctx *Context) error { return app.RunAsSubcommand(ctx) } +// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain +func (c *Command) VisibleFlagCategories() []VisibleFlagCategory { + if c.flagCategories == nil { + return []VisibleFlagCategory{} + } + return c.flagCategories.VisibleCategories() +} + // VisibleFlags returns a slice of the Flags with Hidden=false func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) diff --git a/vendor/github.com/urfave/cli/v2/context.go b/vendor/github.com/urfave/cli/v2/context.go index 74ed51912..6b497ed20 100644 --- a/vendor/github.com/urfave/cli/v2/context.go +++ b/vendor/github.com/urfave/cli/v2/context.go @@ -2,9 +2,7 @@ package cli import ( "context" - "errors" "flag" - "fmt" "strings" ) @@ -42,31 +40,29 @@ func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { } // NumFlags returns the number of flags set -func (c *Context) NumFlags() int { - return c.flagSet.NFlag() +func (cCtx *Context) NumFlags() int { + return cCtx.flagSet.NFlag() } // Set sets a context flag to a value. -func (c *Context) Set(name, value string) error { - return c.flagSet.Set(name, value) +func (cCtx *Context) Set(name, value string) error { + return cCtx.flagSet.Set(name, value) } // IsSet determines if the flag was actually set -func (c *Context) IsSet(name string) bool { - if fs := lookupFlagSet(name, c); fs != nil { - if fs := lookupFlagSet(name, c); fs != nil { - isSet := false - fs.Visit(func(f *flag.Flag) { - if f.Name == name { - isSet = true - } - }) - if isSet { - return true +func (cCtx *Context) IsSet(name string) bool { + if fs := cCtx.lookupFlagSet(name); fs != nil { + isSet := false + fs.Visit(func(f *flag.Flag) { + if f.Name == name { + isSet = true } + }) + if isSet { + return true } - f := lookupFlag(name, c) + f := cCtx.lookupFlag(name) if f == nil { return false } @@ -78,28 +74,28 @@ func (c *Context) IsSet(name string) bool { } // LocalFlagNames returns a slice of flag names used in this context. -func (c *Context) LocalFlagNames() []string { +func (cCtx *Context) LocalFlagNames() []string { var names []string - c.flagSet.Visit(makeFlagNameVisitor(&names)) + cCtx.flagSet.Visit(makeFlagNameVisitor(&names)) return names } // FlagNames returns a slice of flag names used by the this context and all of // its parent contexts. -func (c *Context) FlagNames() []string { +func (cCtx *Context) FlagNames() []string { var names []string - for _, ctx := range c.Lineage() { - ctx.flagSet.Visit(makeFlagNameVisitor(&names)) + for _, pCtx := range cCtx.Lineage() { + pCtx.flagSet.Visit(makeFlagNameVisitor(&names)) } return names } // Lineage returns *this* context and all of its ancestor contexts in order from // child to parent -func (c *Context) Lineage() []*Context { +func (cCtx *Context) Lineage() []*Context { var lineage []*Context - for cur := c; cur != nil; cur = cur.parentContext { + for cur := cCtx; cur != nil; cur = cur.parentContext { lineage = append(lineage, cur) } @@ -107,23 +103,26 @@ func (c *Context) Lineage() []*Context { } // Value returns the value of the flag corresponding to `name` -func (c *Context) Value(name string) interface{} { - return c.flagSet.Lookup(name).Value.(flag.Getter).Get() +func (cCtx *Context) Value(name string) interface{} { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return fs.Lookup(name).Value.(flag.Getter).Get() + } + return nil } // Args returns the command line arguments associated with the context. -func (c *Context) Args() Args { - ret := args(c.flagSet.Args()) +func (cCtx *Context) Args() Args { + ret := args(cCtx.flagSet.Args()) return &ret } // NArg returns the number of the command line arguments. -func (c *Context) NArg() int { - return c.Args().Len() +func (cCtx *Context) NArg() int { + return cCtx.Args().Len() } -func lookupFlag(name string, ctx *Context) Flag { - for _, c := range ctx.Lineage() { +func (cCtx *Context) lookupFlag(name string) Flag { + for _, c := range cCtx.Lineage() { if c.Command == nil { continue } @@ -137,8 +136,8 @@ func lookupFlag(name string, ctx *Context) Flag { } } - if ctx.App != nil { - for _, f := range ctx.App.Flags { + if cCtx.App != nil { + for _, f := range cCtx.App.Flags { for _, n := range f.Names() { if n == name { return f @@ -150,8 +149,11 @@ func lookupFlag(name string, ctx *Context) Flag { return nil } -func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { - for _, c := range ctx.Lineage() { +func (cCtx *Context) lookupFlagSet(name string) *flag.FlagSet { + for _, c := range cCtx.Lineage() { + if c.flagSet == nil { + continue + } if f := c.flagSet.Lookup(name); f != nil { return c.flagSet } @@ -160,89 +162,7 @@ func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { return nil } -func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { - switch ff.Value.(type) { - case Serializer: - _ = set.Set(name, ff.Value.(Serializer).Serialize()) - default: - _ = set.Set(name, ff.Value.String()) - } -} - -func normalizeFlags(flags []Flag, set *flag.FlagSet) error { - visited := make(map[string]bool) - set.Visit(func(f *flag.Flag) { - visited[f.Name] = true - }) - for _, f := range flags { - parts := f.Names() - if len(parts) == 1 { - continue - } - var ff *flag.Flag - for _, name := range parts { - name = strings.Trim(name, " ") - if visited[name] { - if ff != nil { - return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) - } - ff = set.Lookup(name) - } - } - if ff == nil { - continue - } - for _, name := range parts { - name = strings.Trim(name, " ") - if !visited[name] { - copyFlag(name, ff, set) - } - } - } - return nil -} - -func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { - return func(f *flag.Flag) { - nameParts := strings.Split(f.Name, ",") - name := strings.TrimSpace(nameParts[0]) - - for _, part := range nameParts { - part = strings.TrimSpace(part) - if len(part) > len(name) { - name = part - } - } - - if 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 { +func (cCtx *Context) checkRequiredFlags(flags []Flag) requiredFlagsErr { var missingFlags []string for _, f := range flags { if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { @@ -254,7 +174,7 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { flagName = key } - if context.IsSet(strings.TrimSpace(key)) { + if cCtx.IsSet(strings.TrimSpace(key)) { flagPresent = true } } @@ -271,3 +191,21 @@ func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { return nil } + +func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { + return func(f *flag.Flag) { + nameParts := strings.Split(f.Name, ",") + name := strings.TrimSpace(nameParts[0]) + + for _, part := range nameParts { + part = strings.TrimSpace(part) + if len(part) > len(name) { + name = part + } + } + + if name != "" { + *names = append(*names, name) + } + } +} diff --git a/vendor/github.com/urfave/cli/v2/docs.go b/vendor/github.com/urfave/cli/v2/docs.go index dc16fc82d..8b1c9c8a2 100644 --- a/vendor/github.com/urfave/cli/v2/docs.go +++ b/vendor/github.com/urfave/cli/v2/docs.go @@ -1,3 +1,6 @@ +//go:build !urfave_cli_no_docs +// +build !urfave_cli_no_docs + package cli import ( @@ -15,31 +18,39 @@ import ( // 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 { + if err := a.writeDocTemplate(&w, 0); err != nil { return "", err } return w.String(), nil } -// ToMan creates a man page string for the `*App` +// ToMan creates a man page string with section number for the `*App` // The function errors if either parsing or writing of the string fails. -func (a *App) ToMan() (string, error) { +func (a *App) ToManWithSection(sectionNumber int) (string, error) { var w bytes.Buffer - if err := a.writeDocTemplate(&w); err != nil { + if err := a.writeDocTemplate(&w, sectionNumber); err != nil { return "", err } man := md2man.Render(w.Bytes()) return string(man), 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) { + man, err := a.ToManWithSection(8) + return man, err +} + type cliTemplate struct { App *App + SectionNum int Commands []string GlobalArgs []string SynopsisArgs []string } -func (a *App) writeDocTemplate(w io.Writer) error { +func (a *App) writeDocTemplate(w io.Writer, sectionNum int) error { const name = "cli" t, err := template.New(name).Parse(MarkdownDocTemplate) if err != nil { @@ -47,6 +58,7 @@ func (a *App) writeDocTemplate(w io.Writer) error { } return t.ExecuteTemplate(w, name, &cliTemplate{ App: a, + SectionNum: sectionNum, Commands: prepareCommands(a.Commands, 0), GlobalArgs: prepareArgsWithValues(a.VisibleFlags()), SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()), @@ -59,25 +71,26 @@ func prepareCommands(commands []*Command, level int) []string { if command.Hidden { continue } - usage := "" - if command.Usage != "" { - usage = command.Usage - } - prepared := fmt.Sprintf("%s %s\n\n%s\n", + usageText := prepareUsageText(command) + + usage := prepareUsage(command, usageText) + + prepared := fmt.Sprintf("%s %s\n\n%s%s", strings.Repeat("#", level+2), strings.Join(command.Names(), ", "), usage, + usageText, ) - flags := prepareArgsWithValues(command.Flags) + flags := prepareArgsWithValues(command.VisibleFlags()) if len(flags) > 0 { prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) } coms = append(coms, prepared) - // recursevly iterate subcommands + // recursively iterate subcommands if len(command.Subcommands) > 0 { coms = append( coms, @@ -146,3 +159,40 @@ func flagDetails(flag DocGenerationFlag) string { } return ": " + description } + +func prepareUsageText(command *Command) string { + if command.UsageText == "" { + return "" + } + + // Remove leading and trailing newlines + preparedUsageText := strings.Trim(command.UsageText, "\n") + + var usageText string + if strings.Contains(preparedUsageText, "\n") { + // Format multi-line string as a code block using the 4 space schema to allow for embedded markdown such + // that it will not break the continuous code block. + for _, ln := range strings.Split(preparedUsageText, "\n") { + usageText += fmt.Sprintf(" %s\n", ln) + } + } else { + // Style a single line as a note + usageText = fmt.Sprintf(">%s\n", preparedUsageText) + } + + return usageText +} + +func prepareUsage(command *Command, usageText string) string { + if command.Usage == "" { + return "" + } + + usage := command.Usage + "\n" + // Add a newline to the Usage IFF there is a UsageText + if usageText != "" { + usage += "\n" + } + + return usage +} diff --git a/vendor/github.com/urfave/cli/v2/errors.go b/vendor/github.com/urfave/cli/v2/errors.go index 344b4361e..8f641fb64 100644 --- a/vendor/github.com/urfave/cli/v2/errors.go +++ b/vendor/github.com/urfave/cli/v2/errors.go @@ -17,11 +17,10 @@ var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. type MultiError interface { error - // Errors returns a copy of the errors slice Errors() []error } -// NewMultiError creates a new MultiError. Pass in one or more errors. +// newMultiError creates a new MultiError. Pass in one or more errors. func newMultiError(err ...error) MultiError { ret := multiError(err) return &ret @@ -48,6 +47,28 @@ func (m *multiError) Errors() []error { return errs } +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 +} + // ErrorFormatter is the interface that will suitably format the error output type ErrorFormatter interface { Format(s fmt.State, verb rune) @@ -65,13 +86,20 @@ type exitError struct { message interface{} } -// NewExitError makes a new *exitError +// NewExitError calls Exit to create a new ExitCoder. +// +// Deprecated: This function is a duplicate of Exit and will eventually be removed. func NewExitError(message interface{}, exitCode int) ExitCoder { return Exit(message, exitCode) } -// Exit wraps a message and exit code into an ExitCoder suitable for handling by -// HandleExitCoder +// Exit wraps a message and exit code into an error, which by default is +// handled with a call to os.Exit during default error handling. +// +// This is the simplest way to trigger a non-zero exit code for an App without +// having to call os.Exit manually. During testing, this behavior can be avoided +// by overiding the ExitErrHandler function on an App or the package-global +// OsExiter function. func Exit(message interface{}, exitCode int) ExitCoder { return &exitError{ message: message, @@ -87,10 +115,14 @@ func (ee *exitError) ExitCode() int { return ee.exitCode } -// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if -// so prints the error to stderr (if it is non-empty) and calls OsExiter with the -// given exit code. If the given error is a MultiError, then this func is -// called on all members of the Errors slice and calls OsExiter with the last exit code. +// HandleExitCoder handles errors implementing ExitCoder by printing their +// message and calling OsExiter with the given exit code. +// +// If the given error instead implements MultiError, each error will be checked +// for the ExitCoder interface, and OsExiter will be called with the last exit +// code found, or exit code 1 if no ExitCoder is found. +// +// This function is the default error-handling behavior for an App. func HandleExitCoder(err error) { if err == nil { return diff --git a/vendor/github.com/urfave/cli/v2/fish.go b/vendor/github.com/urfave/cli/v2/fish.go index 67122c9fe..eec3253cd 100644 --- a/vendor/github.com/urfave/cli/v2/fish.go +++ b/vendor/github.com/urfave/cli/v2/fish.go @@ -95,7 +95,7 @@ func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, pr completions = append(completions, completion.String()) completions = append( completions, - a.prepareFishFlags(command.Flags, command.Names())..., + a.prepareFishFlags(command.VisibleFlags(), command.Names())..., ) // recursevly iterate subcommands @@ -171,6 +171,10 @@ func fishAddFileFlag(flag Flag, completion *strings.Builder) { if f.TakesFile { return } + case *PathFlag: + if f.TakesFile { + return + } } completion.WriteString(" -f") } diff --git a/vendor/github.com/urfave/cli/v2/flag-spec.yaml b/vendor/github.com/urfave/cli/v2/flag-spec.yaml new file mode 100644 index 000000000..45f054d6e --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag-spec.yaml @@ -0,0 +1,51 @@ +# NOTE: this file is used by the tool defined in +# ./internal/genflags/cmd/genflags/main.go which uses the +# `genflags.Spec` type that maps to this file structure. + +flag_types: + bool: {} + float64: {} + int64: {} + int: {} + time.Duration: {} + uint64: {} + uint: {} + + string: + struct_fields: + - { name: TakesFile, type: bool } + Generic: + struct_fields: + - { name: TakesFile, type: bool } + Path: + struct_fields: + - { name: TakesFile, type: bool } + + Float64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + Int64Slice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + IntSlice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + StringSlice: + value_pointer: true + skip_interfaces: + - fmt.Stringer + struct_fields: + - { name: TakesFile, type: bool } + Timestamp: + value_pointer: true + struct_fields: + - { name: Layout, type: string } + - { name: Timezone, type: "*time.Location" } + + # TODO: enable UintSlice + # UintSlice: {} + # TODO: enable Uint64Slice once #1334 lands + # Uint64Slice: {} diff --git a/vendor/github.com/urfave/cli/v2/flag.go b/vendor/github.com/urfave/cli/v2/flag.go index ad97c2d05..dbed577cd 100644 --- a/vendor/github.com/urfave/cli/v2/flag.go +++ b/vendor/github.com/urfave/cli/v2/flag.go @@ -1,10 +1,10 @@ package cli import ( + "errors" "flag" "fmt" "io/ioutil" - "reflect" "regexp" "runtime" "strconv" @@ -116,6 +116,28 @@ type DocGenerationFlag interface { // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. GetValue() string + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + +// VisibleFlag is an interface that allows to check if a flag is visible +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + +// CategorizableFlag is an interface that allows us to potentially +// use a flag in a categorized representation. +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string } func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { @@ -130,11 +152,52 @@ func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { return set, nil } +func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { + switch ff.Value.(type) { + case Serializer: + _ = set.Set(name, ff.Value.(Serializer).Serialize()) + default: + _ = set.Set(name, ff.Value.String()) + } +} + +func normalizeFlags(flags []Flag, set *flag.FlagSet) error { + visited := make(map[string]bool) + set.Visit(func(f *flag.Flag) { + visited[f.Name] = true + }) + for _, f := range flags { + parts := f.Names() + if len(parts) == 1 { + continue + } + var ff *flag.Flag + for _, name := range parts { + name = strings.Trim(name, " ") + if visited[name] { + if ff != nil { + return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) + } + ff = set.Lookup(name) + } + } + if ff == nil { + continue + } + for _, name := range parts { + name = strings.Trim(name, " ") + if !visited[name] { + copyFlag(name, ff, set) + } + } + } + return nil +} + func visibleFlags(fl []Flag) []Flag { var visible []Flag for _, f := range fl { - field := flagValue(f).FieldByName("Hidden") - if !field.IsValid() || !field.Bool() { + if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() { visible = append(visible, f) } } @@ -188,7 +251,7 @@ func prefixedNames(names []string, placeholder string) string { func withEnvHint(envVars []string, str string) string { envText := "" - if envVars != nil && len(envVars) > 0 { + if len(envVars) > 0 { prefix := "$" suffix := "" sep := ", $" @@ -203,7 +266,7 @@ func withEnvHint(envVars []string, str string) string { return str + envText } -func flagNames(name string, aliases []string) []string { +func FlagNames(name string, aliases []string) []string { var ret []string for _, part := range append([]string{name}, aliases...) { @@ -217,17 +280,6 @@ func flagNames(name string, aliases []string) []string { return ret } -func flagStringSliceField(f Flag, name string) []string { - fv := flagValue(f) - field := fv.FieldByName(name) - - if field.IsValid() { - return field.Interface().([]string) - } - - return []string{} -} - func withFileHint(filePath, str string) string { fileText := "" if filePath != "" { @@ -236,68 +288,34 @@ func withFileHint(filePath, str string) string { return str + fileText } -func flagValue(f Flag) reflect.Value { - fv := reflect.ValueOf(f) - for fv.Kind() == reflect.Ptr { - fv = reflect.Indirect(fv) - } - return fv -} - func formatDefault(format string) string { return " (default: " + format + ")" } func stringifyFlag(f Flag) string { - fv := flagValue(f) - - switch f := f.(type) { - case *IntSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyIntSliceFlag(f)) - case *Int64SliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyInt64SliceFlag(f)) - case *Float64SliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyFloat64SliceFlag(f)) - case *StringSliceFlag: - return withEnvHint(flagStringSliceField(f, "EnvVars"), - stringifyStringSliceFlag(f)) + // enforce DocGeneration interface on flags to avoid reflection + df, ok := f.(DocGenerationFlag) + if !ok { + return "" } - placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) - - needsPlaceholder := false - defaultValueString := "" - val := fv.FieldByName("Value") - if val.IsValid() { - needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(formatDefault("%v"), val.Interface()) - - if val.Kind() == reflect.String && val.String() != "" { - defaultValueString = fmt.Sprintf(formatDefault("%q"), val.String()) - } - } + placeholder, usage := unquoteUsage(df.GetUsage()) + needsPlaceholder := df.TakesValue() - helpText := fv.FieldByName("DefaultText") - if helpText.IsValid() && helpText.String() != "" { - needsPlaceholder = val.Kind() != reflect.Bool - defaultValueString = fmt.Sprintf(formatDefault("%s"), helpText.String()) + if needsPlaceholder && placeholder == "" { + placeholder = defaultPlaceholder } - if defaultValueString == formatDefault("") { - defaultValueString = "" - } + defaultValueString := "" - if needsPlaceholder && placeholder == "" { - placeholder = defaultPlaceholder + if s := df.GetDefaultText(); s != "" { + defaultValueString = fmt.Sprintf(formatDefault("%s"), s) } usageWithDefault := strings.TrimSpace(usage + defaultValueString) - return withEnvHint(flagStringSliceField(f, "EnvVars"), - fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) + return withEnvHint(df.GetEnvVars(), + fmt.Sprintf("%s\t%s", prefixedNames(df.Names(), placeholder), usageWithDefault)) } func stringifyIntSliceFlag(f *IntSliceFlag) string { @@ -359,7 +377,11 @@ func stringifySliceFlag(usage string, names, defaultVals []string) string { } usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) + multiInputString := "(accepts multiple inputs)" + if usageWithDefault != "" { + multiInputString = "\t" + multiInputString + } + return fmt.Sprintf("%s\t%s%s", prefixedNames(names, placeholder), usageWithDefault, multiInputString) } func hasFlag(flags []Flag, fl Flag) bool { @@ -372,17 +394,26 @@ func hasFlag(flags []Flag, fl Flag) bool { return false } -func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { +// Return the first value from a list of environment variables and files +// (which may or may not exist), a description of where the value was found, +// and a boolean which is true if a value was found. +func flagFromEnvOrFile(envVars []string, filePath string) (value string, fromWhere string, found bool) { for _, envVar := range envVars { envVar = strings.TrimSpace(envVar) - if val, ok := syscall.Getenv(envVar); ok { - return val, true + if value, found := syscall.Getenv(envVar); found { + return value, fmt.Sprintf("environment variable %q", envVar), true } } for _, fileVar := range strings.Split(filePath, ",") { - if data, err := ioutil.ReadFile(fileVar); err == nil { - return string(data), true + if fileVar != "" { + if data, err := ioutil.ReadFile(fileVar); err == nil { + return string(data), fmt.Sprintf("file %q", filePath), true + } } } - return "", false + return "", "", false +} + +func flagSplitMultiValues(val string) []string { + return strings.Split(val, ",") } diff --git a/vendor/github.com/urfave/cli/v2/flag_bool.go b/vendor/github.com/urfave/cli/v2/flag_bool.go index bc9ea35d0..b21d5163c 100644 --- a/vendor/github.com/urfave/cli/v2/flag_bool.go +++ b/vendor/github.com/urfave/cli/v2/flag_bool.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *BoolFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,20 +16,38 @@ func (f *BoolFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *BoolFlag) GetCategory() string { + return f.Category +} + // 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 "" } +// GetDefaultText returns the default text for this flag +func (f *BoolFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return fmt.Sprintf("%v", f.Value) +} + +// GetEnvVars returns the env vars for this flag +func (f *BoolFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valBool, err := strconv.ParseBool(val) if err != nil { - return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as bool value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valBool @@ -84,10 +66,15 @@ func (f *BoolFlag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *BoolFlag) Get(ctx *Context) bool { + return ctx.Bool(f.Name) +} + // 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 { +func (cCtx *Context) Bool(name string) bool { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupBool(name, fs) } return false diff --git a/vendor/github.com/urfave/cli/v2/flag_duration.go b/vendor/github.com/urfave/cli/v2/flag_duration.go index 22a2e6720..5178c6ae1 100644 --- a/vendor/github.com/urfave/cli/v2/flag_duration.go +++ b/vendor/github.com/urfave/cli/v2/flag_duration.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *DurationFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,20 +16,38 @@ func (f *DurationFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *DurationFlag) GetCategory() string { + return f.Category +} + // 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() } +// GetDefaultText returns the default text for this flag +func (f *DurationFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *DurationFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valDuration, err := time.ParseDuration(val) if err != nil { - return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as duration value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valDuration @@ -83,10 +65,15 @@ func (f *DurationFlag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *DurationFlag) Get(ctx *Context) time.Duration { + return ctx.Duration(f.Name) +} + // 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 { +func (cCtx *Context) Duration(name string) time.Duration { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupDuration(name, fs) } return 0 diff --git a/vendor/github.com/urfave/cli/v2/flag_float64.go b/vendor/github.com/urfave/cli/v2/flag_float64.go index 91c778c87..2d31739bc 100644 --- a/vendor/github.com/urfave/cli/v2/flag_float64.go +++ b/vendor/github.com/urfave/cli/v2/flag_float64.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Float64Flag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,20 +16,37 @@ func (f *Float64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Float64Flag) GetCategory() string { + return f.Category +} + // 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) + return fmt.Sprintf("%v", f.Value) +} + +// GetDefaultText returns the default text for this flag +func (f *Float64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Float64Flag) GetEnvVars() []string { + return f.EnvVars } // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { - valFloat, err := strconv.ParseFloat(val, 10) - + valFloat, err := strconv.ParseFloat(val, 64) if err != nil { - return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as float64 value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valFloat @@ -84,10 +65,15 @@ func (f *Float64Flag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *Float64Flag) Get(ctx *Context) float64 { + return ctx.Float64(f.Name) +} + // 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 { +func (cCtx *Context) Float64(name string) float64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupFloat64(name, fs) } return 0 diff --git a/vendor/github.com/urfave/cli/v2/flag_float64_slice.go b/vendor/github.com/urfave/cli/v2/flag_float64_slice.go index 706ee6cd4..031ec1d1a 100644 --- a/vendor/github.com/urfave/cli/v2/flag_float64_slice.go +++ b/vendor/github.com/urfave/cli/v2/flag_float64_slice.go @@ -19,6 +19,16 @@ func NewFloat64Slice(defaults ...float64) *Float64Slice { return &Float64Slice{slice: append([]float64{}, defaults...)} } +// clone allocate a copy of self object +func (f *Float64Slice) clone() *Float64Slice { + n := &Float64Slice{ + slice: make([]float64, len(f.slice)), + hasBeenSet: f.hasBeenSet, + } + copy(n.slice, f.slice) + return n +} + // 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 { @@ -33,18 +43,25 @@ func (f *Float64Slice) Set(value string) error { return nil } - tmp, err := strconv.ParseFloat(value, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseFloat(strings.TrimSpace(s), 64) + if err != nil { + return err + } - f.slice = append(f.slice, tmp) + 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) + v := f.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]float64, 0) + } + return fmt.Sprintf("%#v", v) } // Serialize allows Float64Slice to fulfill Serializer @@ -63,39 +80,10 @@ 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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Float64SliceFlag) IsSet() bool { - return f.HasBeenSet -} - // 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.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *Float64SliceFlag) IsRequired() bool { - return f.Required + return withEnvHint(f.GetEnvVars(), stringifyFloat64SliceFlag(f)) } // TakesValue returns true if the flag takes a value, otherwise false @@ -108,6 +96,11 @@ func (f *Float64SliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Float64SliceFlag) GetCategory() string { + return f.Category +} + // 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 { @@ -117,36 +110,69 @@ func (f *Float64SliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *Float64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Float64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { - if val != "" { - f.Value = &Float64Slice{} + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]float64, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *Float64Slice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(Float64Slice) + } - for _, s := range strings.Split(val, ",") { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { + if val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as float64 slice value from %s for flag %s: %s", val, source, f.Name, err) } } + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + setValue.hasBeenSet = false f.HasBeenSet = true } } for _, name := range f.Names() { - if f.Value == nil { - f.Value = &Float64Slice{} - } - set.Var(f.Value, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +// Get returns the flag’s value in the given Context. +func (f *Float64SliceFlag) Get(ctx *Context) []float64 { + return ctx.Float64Slice(f.Name) +} + // 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 { +func (cCtx *Context) Float64Slice(name string) []float64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupFloat64Slice(name, fs) } return nil @@ -155,7 +181,7 @@ func (c *Context) Float64Slice(name string) []float64 { func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*Float64Slice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*Float64Slice); ok { return slice.Value() } } diff --git a/vendor/github.com/urfave/cli/v2/flag_generic.go b/vendor/github.com/urfave/cli/v2/flag_generic.go index b0c8ff44d..680eeb9d7 100644 --- a/vendor/github.com/urfave/cli/v2/flag_generic.go +++ b/vendor/github.com/urfave/cli/v2/flag_generic.go @@ -11,42 +11,6 @@ type Generic interface { 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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *GenericFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -57,6 +21,11 @@ func (f *GenericFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *GenericFlag) GetCategory() string { + return f.Category +} + // 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 { @@ -66,13 +35,26 @@ func (f *GenericFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *GenericFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *GenericFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q from %s as value for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true @@ -86,10 +68,15 @@ func (f GenericFlag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *GenericFlag) Get(ctx *Context) interface{} { + return ctx.Generic(f.Name) +} + // 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 { +func (cCtx *Context) Generic(name string) interface{} { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupGeneric(name, fs) } return nil diff --git a/vendor/github.com/urfave/cli/v2/flag_int.go b/vendor/github.com/urfave/cli/v2/flag_int.go index ac39d4a9e..c70b88985 100644 --- a/vendor/github.com/urfave/cli/v2/flag_int.go +++ b/vendor/github.com/urfave/cli/v2/flag_int.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *IntFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,20 +16,38 @@ func (f *IntFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *IntFlag) GetCategory() string { + return f.Category +} + // 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) } +// GetDefaultText returns the default text for this flag +func (f *IntFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *IntFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = int(valInt) @@ -84,10 +66,15 @@ func (f *IntFlag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *IntFlag) Get(ctx *Context) int { + return ctx.Int(f.Name) +} + // 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 { +func (cCtx *Context) Int(name string) int { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupInt(name, fs) } return 0 diff --git a/vendor/github.com/urfave/cli/v2/flag_int64.go b/vendor/github.com/urfave/cli/v2/flag_int64.go index e09991269..5e7038cfb 100644 --- a/vendor/github.com/urfave/cli/v2/flag_int64.go +++ b/vendor/github.com/urfave/cli/v2/flag_int64.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Int64Flag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,20 +16,38 @@ func (f *Int64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Int64Flag) GetCategory() string { + return f.Category +} + // 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) } +// GetDefaultText returns the default text for this flag +func (f *Int64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Int64Flag) GetEnvVars() []string { + return f.EnvVars +} + // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseInt(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as int value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt @@ -83,10 +65,15 @@ func (f *Int64Flag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *Int64Flag) Get(ctx *Context) int64 { + return ctx.Int64(f.Name) +} + // 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 { +func (cCtx *Context) Int64(name string) int64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupInt64(name, fs) } return 0 diff --git a/vendor/github.com/urfave/cli/v2/flag_int64_slice.go b/vendor/github.com/urfave/cli/v2/flag_int64_slice.go index 6c7fd9376..657aaaaf3 100644 --- a/vendor/github.com/urfave/cli/v2/flag_int64_slice.go +++ b/vendor/github.com/urfave/cli/v2/flag_int64_slice.go @@ -19,6 +19,16 @@ func NewInt64Slice(defaults ...int64) *Int64Slice { return &Int64Slice{slice: append([]int64{}, defaults...)} } +// clone allocate a copy of self object +func (i *Int64Slice) clone() *Int64Slice { + n := &Int64Slice{ + slice: make([]int64, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + // 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 { @@ -33,19 +43,26 @@ func (i *Int64Slice) Set(value string) error { return nil } - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } - i.slice = append(i.slice, tmp) + 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) + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]int64, 0) + } + return fmt.Sprintf("%#v", v) } // Serialize allows Int64Slice to fulfill Serializer @@ -64,39 +81,10 @@ 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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Int64SliceFlag) IsSet() bool { - return f.HasBeenSet -} - // 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.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *Int64SliceFlag) IsRequired() bool { - return f.Required + return withEnvHint(f.GetEnvVars(), stringifyInt64SliceFlag(f)) } // TakesValue returns true of the flag takes a value, otherwise false @@ -105,10 +93,15 @@ func (f *Int64SliceFlag) TakesValue() bool { } // GetUsage returns the usage string for the flag -func (f Int64SliceFlag) GetUsage() string { +func (f *Int64SliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Int64SliceFlag) GetCategory() string { + return f.Category +} + // 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 { @@ -118,40 +111,76 @@ func (f *Int64SliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *Int64SliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Int64SliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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{} + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]int64, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } - for _, s := range strings.Split(val, ",") { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) + // resolve setValue (what we will assign to the set) + var setValue *Int64Slice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(Int64Slice) + } + + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as int64 slice value from %s for flag %s: %s", val, source, f.Name, err) } } + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + setValue.hasBeenSet = false f.HasBeenSet = true } for _, name := range f.Names() { - if f.Value == nil { - f.Value = &Int64Slice{} - } - set.Var(f.Value, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +// Get returns the flag’s value in the given Context. +func (f *Int64SliceFlag) Get(ctx *Context) []int64 { + return ctx.Int64Slice(f.Name) +} + // Int64Slice looks up the value of a local Int64SliceFlag, returns // nil if not found -func (c *Context) Int64Slice(name string) []int64 { - return lookupInt64Slice(name, c.flagSet) +func (cCtx *Context) Int64Slice(name string) []int64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil } func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*Int64Slice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*Int64Slice); ok { return slice.Value() } } diff --git a/vendor/github.com/urfave/cli/v2/flag_int_slice.go b/vendor/github.com/urfave/cli/v2/flag_int_slice.go index 4e0afc021..7c383935a 100644 --- a/vendor/github.com/urfave/cli/v2/flag_int_slice.go +++ b/vendor/github.com/urfave/cli/v2/flag_int_slice.go @@ -19,6 +19,16 @@ func NewIntSlice(defaults ...int) *IntSlice { return &IntSlice{slice: append([]int{}, defaults...)} } +// clone allocate a copy of self object +func (i *IntSlice) clone() *IntSlice { + n := &IntSlice{ + slice: make([]int, len(i.slice)), + hasBeenSet: i.hasBeenSet, + } + copy(n.slice, i.slice) + return n +} + // 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) { @@ -44,19 +54,26 @@ func (i *IntSlice) Set(value string) error { return nil } - tmp, err := strconv.ParseInt(value, 0, 64) - if err != nil { - return err - } + for _, s := range flagSplitMultiValues(value) { + tmp, err := strconv.ParseInt(strings.TrimSpace(s), 0, 64) + if err != nil { + return err + } - i.slice = append(i.slice, int(tmp)) + 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) + v := i.slice + if v == nil { + // treat nil the same as zero length non-nil + v = make([]int, 0) + } + return fmt.Sprintf("%#v", v) } // Serialize allows IntSlice to fulfill Serializer @@ -75,39 +92,10 @@ 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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *IntSliceFlag) IsSet() bool { - return f.HasBeenSet -} - // 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.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *IntSliceFlag) IsRequired() bool { - return f.Required + return withEnvHint(f.GetEnvVars(), stringifyIntSliceFlag(f)) } // TakesValue returns true of the flag takes a value, otherwise false @@ -116,10 +104,15 @@ func (f *IntSliceFlag) TakesValue() bool { } // GetUsage returns the usage string for the flag -func (f IntSliceFlag) GetUsage() string { +func (f *IntSliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *IntSliceFlag) GetCategory() string { + return f.Category +} + // 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 { @@ -129,35 +122,68 @@ func (f *IntSliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *IntSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *IntSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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{} + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]int, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *IntSlice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(IntSlice) + } - for _, s := range strings.Split(val, ",") { - if err := f.Value.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) + if val, source, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok && val != "" { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as int slice value from %s for flag %s: %s", val, source, f.Name, err) } } + // Set this to false so that we reset the slice if we then set values from + // flags that have already been set by the environment. + setValue.hasBeenSet = false f.HasBeenSet = true } for _, name := range f.Names() { - if f.Value == nil { - f.Value = &IntSlice{} - } - set.Var(f.Value, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +// Get returns the flag’s value in the given Context. +func (f *IntSliceFlag) Get(ctx *Context) []int { + return ctx.IntSlice(f.Name) +} + // 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) +func (cCtx *Context) IntSlice(name string) []int { + if fs := cCtx.lookupFlagSet(name); fs != nil { + return lookupIntSlice(name, fs) } return nil } @@ -165,7 +191,7 @@ func (c *Context) IntSlice(name string) []int { func lookupIntSlice(name string, set *flag.FlagSet) []int { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*IntSlice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*IntSlice); ok { return slice.Value() } } diff --git a/vendor/github.com/urfave/cli/v2/flag_path.go b/vendor/github.com/urfave/cli/v2/flag_path.go index 8070dc4b0..7c87a8900 100644 --- a/vendor/github.com/urfave/cli/v2/flag_path.go +++ b/vendor/github.com/urfave/cli/v2/flag_path.go @@ -1,42 +1,11 @@ package cli -import "flag" +import ( + "flag" + "fmt" +) -type PathFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value string - DefaultText string - Destination *string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *PathFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *PathFlag) IsRequired() bool { - return f.Required -} +type Path = string // TakesValue returns true of the flag takes a value, otherwise false func (f *PathFlag) TakesValue() bool { @@ -48,15 +17,36 @@ func (f *PathFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *PathFlag) GetCategory() string { + return f.Category +} + // 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 } +// GetDefaultText returns the default text for this flag +func (f *PathFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + if f.Value == "" { + return f.Value + } + return fmt.Sprintf("%q", f.Value) +} + +// GetEnvVars returns the env vars for this flag +func (f *PathFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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 { + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true } @@ -72,10 +62,15 @@ func (f *PathFlag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *PathFlag) Get(ctx *Context) string { + return ctx.Path(f.Name) +} + // 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 { +func (cCtx *Context) Path(name string) string { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupPath(name, fs) } diff --git a/vendor/github.com/urfave/cli/v2/flag_string.go b/vendor/github.com/urfave/cli/v2/flag_string.go index 400bb532e..c8da38f92 100644 --- a/vendor/github.com/urfave/cli/v2/flag_string.go +++ b/vendor/github.com/urfave/cli/v2/flag_string.go @@ -1,43 +1,9 @@ package cli -import "flag" - -// StringFlag is a flag with type string -type StringFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - TakesFile bool - Value string - DefaultText string - Destination *string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *StringFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *StringFlag) IsRequired() bool { - return f.Required -} +import ( + "flag" + "fmt" +) // TakesValue returns true of the flag takes a value, otherwise false func (f *StringFlag) TakesValue() bool { @@ -49,15 +15,36 @@ func (f *StringFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *StringFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *StringFlag) GetValue() string { return f.Value } +// GetDefaultText returns the default text for this flag +func (f *StringFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + if f.Value == "" { + return f.Value + } + return fmt.Sprintf("%q", f.Value) +} + +// GetEnvVars returns the env vars for this flag +func (f *StringFlag) GetEnvVars() []string { + return f.EnvVars +} + // Apply populates the flag given the flag set and environment func (f *StringFlag) Apply(set *flag.FlagSet) error { - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, _, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { f.Value = val f.HasBeenSet = true } @@ -73,10 +60,15 @@ func (f *StringFlag) Apply(set *flag.FlagSet) error { return nil } +// Get returns the flag’s value in the given Context. +func (f *StringFlag) Get(ctx *Context) string { + return ctx.String(f.Name) +} + // 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 { +func (cCtx *Context) String(name string) string { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupString(name, fs) } return "" @@ -85,10 +77,7 @@ func (c *Context) String(name string) string { 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 "" - } + parsed := f.Value.String() return parsed } return "" diff --git a/vendor/github.com/urfave/cli/v2/flag_string_slice.go b/vendor/github.com/urfave/cli/v2/flag_string_slice.go index ac363bf60..bcdfd4c55 100644 --- a/vendor/github.com/urfave/cli/v2/flag_string_slice.go +++ b/vendor/github.com/urfave/cli/v2/flag_string_slice.go @@ -18,6 +18,16 @@ func NewStringSlice(defaults ...string) *StringSlice { return &StringSlice{slice: append([]string{}, defaults...)} } +// clone allocate a copy of self object +func (s *StringSlice) clone() *StringSlice { + n := &StringSlice{ + slice: make([]string, len(s.slice)), + hasBeenSet: s.hasBeenSet, + } + copy(n.slice, s.slice) + return n +} + // Set appends the string value to the list of values func (s *StringSlice) Set(value string) error { if !s.hasBeenSet { @@ -32,7 +42,9 @@ func (s *StringSlice) Set(value string) error { return nil } - s.slice = append(s.slice, value) + for _, t := range flagSplitMultiValues(value) { + s.slice = append(s.slice, strings.TrimSpace(t)) + } return nil } @@ -58,41 +70,10 @@ 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 - HasBeenSet bool - Destination *StringSlice -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *StringSliceFlag) IsSet() bool { - return f.HasBeenSet -} - // 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.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *StringSliceFlag) IsRequired() bool { - return f.Required + return withEnvHint(f.GetEnvVars(), stringifyStringSliceFlag(f)) } // TakesValue returns true of the flag takes a value, otherwise false @@ -105,6 +86,11 @@ func (f *StringSliceFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *StringSliceFlag) GetCategory() string { + return f.Category +} + // 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 { @@ -114,47 +100,67 @@ func (f *StringSliceFlag) GetValue() string { return "" } +// GetDefaultText returns the default text for this flag +func (f *StringSliceFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *StringSliceFlag) GetEnvVars() []string { + return f.EnvVars +} + // 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{} - destination := f.Value - if f.Destination != nil { - destination = f.Destination - } + // apply any default + if f.Destination != nil && f.Value != nil { + f.Destination.slice = make([]string, len(f.Value.slice)) + copy(f.Destination.slice, f.Value.slice) + } + + // resolve setValue (what we will assign to the set) + var setValue *StringSlice + switch { + case f.Destination != nil: + setValue = f.Destination + case f.Value != nil: + setValue = f.Value.clone() + default: + setValue = new(StringSlice) + } - for _, s := range strings.Split(val, ",") { - if err := destination.Set(strings.TrimSpace(s)); err != nil { - return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { + for _, s := range flagSplitMultiValues(val) { + if err := setValue.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as string value from %s for flag %s: %s", val, source, f.Name, err) } } // Set this to false so that we reset the slice if we then set values from // flags that have already been set by the environment. - destination.hasBeenSet = false + setValue.hasBeenSet = false f.HasBeenSet = true } for _, name := range f.Names() { - if f.Value == nil { - f.Value = &StringSlice{} - } - - if f.Destination != nil { - set.Var(f.Destination, name, f.Usage) - continue - } - - set.Var(f.Value, name, f.Usage) + set.Var(setValue, name, f.Usage) } return nil } +// Get returns the flag’s value in the given Context. +func (f *StringSliceFlag) Get(ctx *Context) []string { + return ctx.StringSlice(f.Name) +} + // 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 { +func (cCtx *Context) StringSlice(name string) []string { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupStringSlice(name, fs) } return nil @@ -163,7 +169,7 @@ func (c *Context) StringSlice(name string) []string { func lookupStringSlice(name string, set *flag.FlagSet) []string { f := set.Lookup(name) if f != nil { - if slice, ok := f.Value.(*StringSlice); ok { + if slice, ok := unwrapFlagValue(f.Value).(*StringSlice); ok { return slice.Value() } } diff --git a/vendor/github.com/urfave/cli/v2/flag_timestamp.go b/vendor/github.com/urfave/cli/v2/flag_timestamp.go index 9fac1d1e2..16f42dd01 100644 --- a/vendor/github.com/urfave/cli/v2/flag_timestamp.go +++ b/vendor/github.com/urfave/cli/v2/flag_timestamp.go @@ -11,6 +11,7 @@ type Timestamp struct { timestamp *time.Time hasBeenSet bool layout string + location *time.Location } // Timestamp constructor @@ -31,9 +32,22 @@ func (t *Timestamp) SetLayout(layout string) { t.layout = layout } +// Set perceived timezone of the to-be parsed time string +func (t *Timestamp) SetLocation(loc *time.Location) { + t.location = loc +} + // Parses the string value to timestamp func (t *Timestamp) Set(value string) error { - timestamp, err := time.Parse(t.layout, value) + var timestamp time.Time + var err error + + if t.location != nil { + timestamp, err = time.ParseInLocation(t.layout, value, t.location) + } else { + timestamp, err = time.Parse(t.layout, value) + } + if err != nil { return err } @@ -58,42 +72,6 @@ func (t *Timestamp) Get() interface{} { return *t } -// TimestampFlag is a flag with type time -type TimestampFlag struct { - Name string - Aliases []string - Usage string - EnvVars []string - FilePath string - Required bool - Hidden bool - Layout string - Value *Timestamp - DefaultText string - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *TimestampFlag) IsSet() bool { - return f.HasBeenSet -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f *TimestampFlag) String() string { - return FlagStringer(f) -} - -// Names returns the names of the flag -func (f *TimestampFlag) Names() []string { - return flagNames(f.Name, f.Aliases) -} - -// IsRequired returns whether or not the flag is required -func (f *TimestampFlag) IsRequired() bool { - return f.Required -} - // TakesValue returns true of the flag takes a value, otherwise false func (f *TimestampFlag) TakesValue() bool { return true @@ -104,39 +82,75 @@ func (f *TimestampFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *TimestampFlag) GetCategory() string { + return f.Category +} + // GetValue returns the flags value as string representation and an empty // string if the flag takes no value at all. func (f *TimestampFlag) GetValue() string { - if f.Value != nil { + if f.Value != nil && f.Value.timestamp != nil { return f.Value.timestamp.String() } return "" } +// GetDefaultText returns the default text for this flag +func (f *TimestampFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *TimestampFlag) GetEnvVars() []string { + return f.EnvVars +} + // Apply populates the flag given the flag set and environment func (f *TimestampFlag) Apply(set *flag.FlagSet) error { if f.Layout == "" { return fmt.Errorf("timestamp Layout is required") } - f.Value = &Timestamp{} + if f.Value == nil { + f.Value = &Timestamp{} + } f.Value.SetLayout(f.Layout) + f.Value.SetLocation(f.Timezone) + + if f.Destination != nil { + f.Destination.SetLayout(f.Layout) + f.Destination.SetLocation(f.Timezone) + } - if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if err := f.Value.Set(val); err != nil { - return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as timestamp value from %s for flag %s: %s", val, source, f.Name, err) } f.HasBeenSet = true } for _, name := range f.Names() { + if f.Destination != nil { + set.Var(f.Destination, name, f.Usage) + continue + } + set.Var(f.Value, name, f.Usage) } return nil } +// Get returns the flag’s value in the given Context. +func (f *TimestampFlag) Get(ctx *Context) *time.Time { + return ctx.Timestamp(f.Name) +} + // Timestamp gets the timestamp from a flag name -func (c *Context) Timestamp(name string) *time.Time { - if fs := lookupFlagSet(name, c); fs != nil { +func (cCtx *Context) Timestamp(name string) *time.Time { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupTimestamp(name, fs) } return nil diff --git a/vendor/github.com/urfave/cli/v2/flag_uint.go b/vendor/github.com/urfave/cli/v2/flag_uint.go index 2e5e76b0e..6092b1ad6 100644 --- a/vendor/github.com/urfave/cli/v2/flag_uint.go +++ b/vendor/github.com/urfave/cli/v2/flag_uint.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *UintFlag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,13 +16,18 @@ func (f *UintFlag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *UintFlag) GetCategory() string { + return f.Category +} + // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = uint(valInt) @@ -83,10 +52,28 @@ func (f *UintFlag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *UintFlag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *UintFlag) GetEnvVars() []string { + return f.EnvVars +} + +// Get returns the flag’s value in the given Context. +func (f *UintFlag) Get(ctx *Context) uint { + return ctx.Uint(f.Name) +} + // 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 { +func (cCtx *Context) Uint(name string) uint { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupUint(name, fs) } return 0 diff --git a/vendor/github.com/urfave/cli/v2/flag_uint64.go b/vendor/github.com/urfave/cli/v2/flag_uint64.go index 8fc3289d8..a37f30d9f 100644 --- a/vendor/github.com/urfave/cli/v2/flag_uint64.go +++ b/vendor/github.com/urfave/cli/v2/flag_uint64.go @@ -6,42 +6,6 @@ import ( "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 - HasBeenSet bool -} - -// IsSet returns whether or not the flag has been set through env or file -func (f *Uint64Flag) IsSet() bool { - return f.HasBeenSet -} - -// 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.Name, f.Aliases) -} - -// 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 @@ -52,13 +16,18 @@ func (f *Uint64Flag) GetUsage() string { return f.Usage } +// GetCategory returns the category for the flag +func (f *Uint64Flag) GetCategory() string { + return f.Category +} + // 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, source, found := flagFromEnvOrFile(f.EnvVars, f.FilePath); found { if val != "" { valInt, err := strconv.ParseUint(val, 0, 64) if err != nil { - return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err) + return fmt.Errorf("could not parse %q as uint64 value from %s for flag %s: %s", val, source, f.Name, err) } f.Value = valInt @@ -83,10 +52,28 @@ func (f *Uint64Flag) GetValue() string { return fmt.Sprintf("%d", f.Value) } +// GetDefaultText returns the default text for this flag +func (f *Uint64Flag) GetDefaultText() string { + if f.DefaultText != "" { + return f.DefaultText + } + return f.GetValue() +} + +// GetEnvVars returns the env vars for this flag +func (f *Uint64Flag) GetEnvVars() []string { + return f.EnvVars +} + +// Get returns the flag’s value in the given Context. +func (f *Uint64Flag) Get(ctx *Context) uint64 { + return ctx.Uint64(f.Name) +} + // 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 { +func (cCtx *Context) Uint64(name string) uint64 { + if fs := cCtx.lookupFlagSet(name); fs != nil { return lookupUint64(name, fs) } return 0 diff --git a/vendor/github.com/urfave/cli/v2/funcs.go b/vendor/github.com/urfave/cli/v2/funcs.go index 474c48faf..0a9b22c94 100644 --- a/vendor/github.com/urfave/cli/v2/funcs.go +++ b/vendor/github.com/urfave/cli/v2/funcs.go @@ -17,15 +17,15 @@ type ActionFunc func(*Context) error // CommandNotFoundFunc is executed if the proper command cannot be found type CommandNotFoundFunc func(*Context, string) -// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying +// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying // customized usage error messages. This function is able to replace the // original error messages. If this function is not set, the "Incorrect usage" // is displayed and the execution is interrupted. -type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error +type OnUsageErrorFunc func(cCtx *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) +type ExitErrHandlerFunc func(cCtx *Context, err error) // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. diff --git a/vendor/github.com/urfave/cli/v2/godoc-current.txt b/vendor/github.com/urfave/cli/v2/godoc-current.txt new file mode 100644 index 000000000..1ba48cc65 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/godoc-current.txt @@ -0,0 +1,2318 @@ +package cli // import "github.com/urfave/cli/v2" + +Package cli provides a minimal framework for creating and organizing command +line Go applications. cli is designed to be easy to understand and write, +the most simple cli application can be written as follows: + + func main() { + (&cli.App{}).Run(os.Args) + } + +Of course this application does not do much, so let's make this an actual +application: + + func main() { + app := &cli.App{ + Name: "greet", + Usage: "say a greeting", + Action: func(c *cli.Context) error { + fmt.Println("Greetings") + return nil + }, + } + + app.Run(os.Args) + } + +VARIABLES + +var ( + SuggestFlag SuggestFlagFunc = suggestFlag + SuggestCommand SuggestCommandFunc = suggestCommand + SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate +) +var AppHelpTemplate = `NAME: + {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + +USAGE: + {{if .UsageText}}{{wrap .UsageText 3}}{{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: + {{wrap .Description 3}}{{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 .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{wrap .Copyright 3}}{{end}} +` + 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 CommandHelpTemplate = `NAME: + {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + +USAGE: + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{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 ErrWriter io.Writer = os.Stderr + ErrWriter is used to write errors to the user. This can be anything + implementing the io.Writer interface and defaults to os.Stderr. + +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 }}` +var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} + +{{end}}# NAME + +{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} + +# SYNOPSIS + +{{ .App.Name }} +{{ if .SynopsisArgs }} +` + "```" + ` +{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` +{{ end }}{{ if .App.Description }} +# DESCRIPTION + +{{ .App.Description }} +{{ end }} +**Usage**: + +` + "```" + `{{ if .App.UsageText }} +{{ .App.UsageText }} +{{ else }} +{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +{{ end }}` + "```" + ` +{{ if .GlobalArgs }} +# GLOBAL OPTIONS +{{ range $v := .GlobalArgs }} +{{ $v }}{{ end }} +{{ end }}{{ if .Commands }} +# COMMANDS +{{ range $v := .Commands }} +{{ $v }}{{ end }}{{ end }}` +var OsExiter = os.Exit + OsExiter is the function used when the app exits. If not set defaults to + os.Exit. + +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{wrap .Description 3}}{{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}} +` + 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 VersionPrinter = printVersion + VersionPrinter prints the version for the App + +var HelpPrinter helpPrinter = printHelp + HelpPrinter is a function that writes the help output. If not set + explicitly, this calls HelpPrinterCustom using only the default template + functions. + + If custom logic for printing help is required, this function can be + overridden. If the ExtraInfo field is defined on an App, this function + should not be modified, as HelpPrinterCustom will be used directly in order + to capture the extra information. + +var HelpPrinterCustom helpPrinterCustom = printHelpCustom + HelpPrinterCustom is a function that writes the help output. It is used as + the default implementation of HelpPrinter, and may be called directly if the + ExtraInfo field is set on an App. + + In the default implementation, if the customFuncs argument contains a + "wrapAt" key, which is a function which takes no arguments and returns an + int, this int value will be used to produce a "wrap" function used by the + default template to wrap long lines. + + +FUNCTIONS + +func DefaultAppComplete(cCtx *Context) + DefaultAppComplete prints the list of subcommands as the default app + completion method + +func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) +func FlagNames(name string, aliases []string) []string +func HandleAction(action interface{}, cCtx *Context) (err error) + 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 HandleExitCoder(err error) + HandleExitCoder handles errors implementing ExitCoder by printing their + message and calling OsExiter with the given exit code. + + If the given error instead implements MultiError, each error will be checked + for the ExitCoder interface, and OsExiter will be called with the last exit + code found, or exit code 1 if no ExitCoder is found. + + This function is the default error-handling behavior for an App. + +func ShowAppHelp(cCtx *Context) error + ShowAppHelp is an action that displays the help. + +func ShowAppHelpAndExit(c *Context, exitCode int) + ShowAppHelpAndExit - Prints the list of subcommands for the app and exits + with exit code. + +func ShowCommandCompletions(ctx *Context, command string) + ShowCommandCompletions prints the custom completions for a given command + +func ShowCommandHelp(ctx *Context, command string) error + ShowCommandHelp prints help for the given command + +func ShowCommandHelpAndExit(c *Context, command string, code int) + ShowCommandHelpAndExit - exits with code after showing help + +func ShowCompletions(cCtx *Context) + ShowCompletions prints the lists of commands within a given context + +func ShowSubcommandHelp(cCtx *Context) error + ShowSubcommandHelp prints help for the given subcommand + +func ShowSubcommandHelpAndExit(c *Context, exitCode int) + ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits + with exit code. + +func ShowVersion(cCtx *Context) + ShowVersion prints the version number of the App + + +TYPES + +type ActionFunc func(*Context) error + ActionFunc is the action to execute when no subcommands are specified + +type AfterFunc func(*Context) error + AfterFunc is an action to execute after any subcommands are run, but after + the subcommand has finished it is run even if Action() panics + +type App struct { + // The name of the program. Defaults to path.Base(os.Args[0]) + Name string + // Full name of command for help, defaults to Name + HelpName string + // Description of the program. + Usage string + // Text to override the USAGE section of help + UsageText string + // Description of the program argument format. + ArgsUsage string + // Version of the program + Version string + // Description of the program + Description string + // DefaultCommand is the (optional) name of a command + // to run if no command names are passed as CLI arguments. + DefaultCommand string + // List of commands to execute + Commands []*Command + // List of flags to parse + Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool + // Boolean to hide built-in help command and help flag + HideHelp bool + // Boolean to hide built-in help command but keep help flag. + // Ignored if HideHelp is true. + HideHelpCommand bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool + + // An action to execute when the shell completion flag is set + 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 + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The action to execute when no subcommands are specified + Action ActionFunc + // Execute this function if the proper command cannot be found + CommandNotFound CommandNotFoundFunc + // Execute this function if a usage error occurs + OnUsageError OnUsageErrorFunc + // Compilation date + Compiled time.Time + // List of all authors who contributed + Authors []*Author + // Copyright of the binary if any + Copyright string + // Reader reader to write input to (useful for tests) + Reader io.Reader + // Writer writer to write output to + Writer io.Writer + // ErrWriter writes error output + ErrWriter io.Writer + // ExitErrHandler processes any error encountered while running an App before + // it is returned to the caller. If no function is provided, HandleExitCoder + // is used as the default behavior. + ExitErrHandler ExitErrHandlerFunc + // Other custom info + Metadata map[string]interface{} + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string + // CustomAppHelpTemplate the text template for app help topic. + // 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 arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool + // Enable suggestions for commands and flags + Suggest bool + + // Has unexported fields. +} + App is the main structure of a cli application. It is recommended that an + app be created with the cli.NewApp() function + +func NewApp() *App + NewApp creates a new cli Application with some reasonable defaults for Name, + Usage, Version and Action. + +func (a *App) Command(name string) *Command + Command returns the named command on App. Returns nil if the command does + not exist + +func (a *App) Run(arguments []string) (err error) + Run is the entry point to the cli app. Parses the arguments slice and routes + to the proper flag/args combination + +func (a *App) RunAndExitOnError() + 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 error + code in the cli.ExitCoder + +func (a *App) RunAsSubcommand(ctx *Context) (err error) + RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() + to generate command-specific flags + +func (a *App) RunContext(ctx context.Context, arguments []string) (err error) + RunContext is like Run except it takes a Context that will be passed to its + commands and sub-commands. Through this, you can propagate timeouts and + cancellation requests + +func (a *App) Setup() + 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. + +func (a *App) ToFishCompletion() (string, error) + ToFishCompletion creates a fish completion string for the `*App` The + function errors if either parsing or writing of the string fails. + +func (a *App) ToMan() (string, error) + ToMan creates a man page string for the `*App` The function errors if either + parsing or writing of the string fails. + +func (a *App) ToManWithSection(sectionNumber int) (string, error) + ToMan creates a man page string with section number for the `*App` The + function errors if either parsing or writing of the string fails. + +func (a *App) ToMarkdown() (string, error) + ToMarkdown creates a markdown string for the `*App` The function errors if + either parsing or writing of the string fails. + +func (a *App) VisibleCategories() []CommandCategory + VisibleCategories returns a slice of categories and commands that are + Hidden=false + +func (a *App) VisibleCommands() []*Command + VisibleCommands returns a slice of the Commands with Hidden=false + +func (a *App) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the categories with the + flags they contain + +func (a *App) VisibleFlags() []Flag + VisibleFlags returns a slice of the Flags with Hidden=false + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + Author represents someone who has contributed to a cli project. + +func (a *Author) String() string + String makes Author comply to the Stringer interface, to allow an easy print + in the templating process + +type BashCompleteFunc func(*Context) + BashCompleteFunc is an action to execute when the shell completion flag is + set + +type BeforeFunc func(*Context) error + 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 + +type BoolFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value bool + Destination *bool + + Aliases []string + EnvVars []string +} + BoolFlag is a flag with type bool + +func (f *BoolFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *BoolFlag) Get(ctx *Context) bool + Get returns the flag’s value in the given Context. + +func (f *BoolFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *BoolFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *BoolFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *BoolFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *BoolFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *BoolFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *BoolFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *BoolFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *BoolFlag) Names() []string + Names returns the names of the flag + +func (f *BoolFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *BoolFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type CategorizableFlag interface { + VisibleFlag + + GetCategory() string +} + CategorizableFlag is an interface that allows us to potentially use a flag + in a categorized representation. + +type Command struct { + // The name of the command + Name string + // A list of aliases for the command + Aliases []string + // A short description of the usage of this command + Usage string + // Custom text to show on USAGE section of help + UsageText string + // A longer explanation of how the command works + Description string + // A short description of the arguments of this command + ArgsUsage string + // The category the command is part of + Category string + // 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 + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The function to call when this command is invoked + Action ActionFunc + // Execute this function if a usage error occurs. + OnUsageError OnUsageErrorFunc + // List of child commands + Subcommands []*Command + // List of flags to parse + Flags []Flag + + // Treat all flags as normal arguments if true + SkipFlagParsing bool + // Boolean to hide built-in help command and help flag + HideHelp bool + // Boolean to hide built-in help command but keep help flag + // Ignored if HideHelp is true. + HideHelpCommand 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 + + // CustomHelpTemplate 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. + CustomHelpTemplate string + // Has unexported fields. +} + Command is a subcommand for a cli.App. + +func (c *Command) FullName() string + FullName returns the full name of the command. For subcommands this ensures + that parent commands are part of the command path + +func (c *Command) HasName(name string) bool + HasName returns true if Command.Name matches given name + +func (c *Command) Names() []string + Names returns the names including short names and aliases. + +func (c *Command) Run(ctx *Context) (err error) + Run invokes the command given the context, parses ctx.Args() to generate + command-specific flags + +func (c *Command) VisibleFlagCategories() []VisibleFlagCategory + VisibleFlagCategories returns a slice containing all the visible flag + categories with the flags they contain + +func (c *Command) VisibleFlags() []Flag + VisibleFlags returns a slice of the Flags with Hidden=false + +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // Categories returns a slice of categories sorted by name + Categories() []CommandCategory +} + CommandCategories interface allows for category manipulation + +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command +} + CommandCategory is a category containing commands. + +type CommandNotFoundFunc func(*Context, string) + CommandNotFoundFunc is executed if the proper command cannot be found + +type Commands []*Command + +type CommandsByName []*Command + +func (c CommandsByName) Len() int + +func (c CommandsByName) Less(i, j int) bool + +func (c CommandsByName) Swap(i, j int) + +type Context struct { + context.Context + App *App + Command *Command + + // Has unexported fields. +} + Context is a type that is passed through to each Handler action in a cli + application. Context can be used to retrieve context-specific args and + parsed command-line options. + +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context + NewContext creates a new context. For use in when invoking an App or Command + action. + +func (cCtx *Context) Args() Args + Args returns the command line arguments associated with the context. + +func (cCtx *Context) Bool(name string) bool + Bool looks up the value of a local BoolFlag, returns false if not found + +func (cCtx *Context) Duration(name string) time.Duration + Duration looks up the value of a local DurationFlag, returns 0 if not found + +func (cCtx *Context) FlagNames() []string + FlagNames returns a slice of flag names used by the this context and all of + its parent contexts. + +func (cCtx *Context) Float64(name string) float64 + Float64 looks up the value of a local Float64Flag, returns 0 if not found + +func (cCtx *Context) Float64Slice(name string) []float64 + Float64Slice looks up the value of a local Float64SliceFlag, returns nil if + not found + +func (cCtx *Context) Generic(name string) interface{} + Generic looks up the value of a local GenericFlag, returns nil if not found + +func (cCtx *Context) Int(name string) int + Int looks up the value of a local IntFlag, returns 0 if not found + +func (cCtx *Context) Int64(name string) int64 + Int64 looks up the value of a local Int64Flag, returns 0 if not found + +func (cCtx *Context) Int64Slice(name string) []int64 + Int64Slice looks up the value of a local Int64SliceFlag, returns nil if not + found + +func (cCtx *Context) IntSlice(name string) []int + IntSlice looks up the value of a local IntSliceFlag, returns nil if not + found + +func (cCtx *Context) IsSet(name string) bool + IsSet determines if the flag was actually set + +func (cCtx *Context) Lineage() []*Context + Lineage returns *this* context and all of its ancestor contexts in order + from child to parent + +func (cCtx *Context) LocalFlagNames() []string + LocalFlagNames returns a slice of flag names used in this context. + +func (cCtx *Context) NArg() int + NArg returns the number of the command line arguments. + +func (cCtx *Context) NumFlags() int + NumFlags returns the number of flags set + +func (cCtx *Context) Path(name string) string + Path looks up the value of a local PathFlag, returns "" if not found + +func (cCtx *Context) Set(name, value string) error + Set sets a context flag to a value. + +func (cCtx *Context) String(name string) string + String looks up the value of a local StringFlag, returns "" if not found + +func (cCtx *Context) StringSlice(name string) []string + StringSlice looks up the value of a local StringSliceFlag, returns nil if + not found + +func (cCtx *Context) Timestamp(name string) *time.Time + Timestamp gets the timestamp from a flag name + +func (cCtx *Context) Uint(name string) uint + Uint looks up the value of a local UintFlag, returns 0 if not found + +func (cCtx *Context) Uint64(name string) uint64 + Uint64 looks up the value of a local Uint64Flag, returns 0 if not found + +func (cCtx *Context) Value(name string) interface{} + Value returns the value of the flag corresponding to `name` + +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if 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 + + // GetDefaultText returns the default text for this flag + GetDefaultText() string + + // GetEnvVars returns the env vars for this flag + GetEnvVars() []string +} + DocGenerationFlag is an interface that allows documentation generation for + the flag + +type DurationFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value time.Duration + Destination *time.Duration + + Aliases []string + EnvVars []string +} + DurationFlag is a flag with type time.Duration + +func (f *DurationFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *DurationFlag) Get(ctx *Context) time.Duration + Get returns the flag’s value in the given Context. + +func (f *DurationFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *DurationFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *DurationFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *DurationFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *DurationFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *DurationFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *DurationFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *DurationFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *DurationFlag) Names() []string + Names returns the names of the flag + +func (f *DurationFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *DurationFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type ErrorFormatter interface { + Format(s fmt.State, verb rune) +} + ErrorFormatter is the interface that will suitably format the error output + +type ExitCoder interface { + error + ExitCode() int +} + ExitCoder is the interface checked by `App` and `Command` for a custom exit + code + +func Exit(message interface{}, exitCode int) ExitCoder + Exit wraps a message and exit code into an error, which by default is + handled with a call to os.Exit during default error handling. + + This is the simplest way to trigger a non-zero exit code for an App without + having to call os.Exit manually. During testing, this behavior can be + avoided by overiding the ExitErrHandler function on an App or the + package-global OsExiter function. + +func NewExitError(message interface{}, exitCode int) ExitCoder + NewExitError calls Exit to create a new ExitCoder. + + Deprecated: This function is a duplicate of Exit and will eventually be + removed. + +type ExitErrHandlerFunc func(cCtx *Context, err error) + ExitErrHandlerFunc is executed if provided in order to handle exitError + values returned by Actions and Before/After functions. + +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) error + Names() []string + IsSet() bool +} + Flag is a common interface related to parsing flags in cli. For more + advanced flag parsing techniques, it is recommended that this interface be + implemented. + +var BashCompletionFlag Flag = &BoolFlag{ + Name: "generate-bash-completion", + Hidden: true, +} + BashCompletionFlag enables bash-completion for all commands and subcommands + +var HelpFlag Flag = &BoolFlag{ + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", +} + HelpFlag prints the help for all commands and subcommands. Set to nil to + disable the flag. The subcommand will still be added unless HideHelp or + HideHelpCommand is set to true. + +var VersionFlag Flag = &BoolFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", +} + VersionFlag prints the version for the application + +type FlagCategories interface { + // AddFlags adds a flag to a category, creating a new category if necessary. + AddFlag(category string, fl Flag) + // VisibleCategories returns a slice of visible flag categories sorted by name + VisibleCategories() []VisibleFlagCategory +} + FlagCategories interface allows for category manipulation + +type FlagEnvHintFunc func(envVars []string, str string) string + FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help + with the environment variable details. + +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + FlagEnvHinter annotates flag help message with the environment variable + details. This is used by the default FlagStringer. + +type FlagFileHintFunc func(filePath, str string) string + FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help + with the file path details. + +var FlagFileHinter FlagFileHintFunc = withFileHint + FlagFileHinter annotates flag help message with the environment variable + details. This is used by the default FlagStringer. + +type FlagNamePrefixFunc func(fullName []string, placeholder string) string + FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix + text for a flag's full name. + +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + FlagNamePrefixer converts a full flag name and its placeholder into the help + message flag prefix. This is used by the default FlagStringer. + +type FlagStringFunc func(Flag) string + FlagStringFunc is used by the help generation to display a flag, which is + expected to be a single line. + +var FlagStringer FlagStringFunc = stringifyFlag + FlagStringer converts a flag definition to a string. This is used by help to + display a flag. + +type FlagsByName []Flag + FlagsByName is a slice of Flag. + +func (f FlagsByName) Len() int + +func (f FlagsByName) Less(i, j int) bool + +func (f FlagsByName) Swap(i, j int) + +type Float64Flag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value float64 + Destination *float64 + + Aliases []string + EnvVars []string +} + Float64Flag is a flag with type float64 + +func (f *Float64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Float64Flag) Get(ctx *Context) float64 + Get returns the flag’s value in the given Context. + +func (f *Float64Flag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Float64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Float64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Float64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Float64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Float64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Float64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Float64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Float64Flag) Names() []string + Names returns the names of the flag + +func (f *Float64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Float64Slice struct { + // Has unexported fields. +} + Float64Slice wraps []float64 to satisfy flag.Value + +func NewFloat64Slice(defaults ...float64) *Float64Slice + NewFloat64Slice makes a *Float64Slice with default values + +func (f *Float64Slice) Get() interface{} + Get returns the slice of float64s set by this flag + +func (f *Float64Slice) Serialize() string + Serialize allows Float64Slice to fulfill Serializer + +func (f *Float64Slice) Set(value string) error + Set parses the value into a float64 and appends it to the list of values + +func (f *Float64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64Slice) Value() []float64 + Value returns the slice of float64s set by this flag + +type Float64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Float64Slice + Destination *Float64Slice + + Aliases []string + EnvVars []string +} + Float64SliceFlag is a flag with type *Float64Slice + +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Float64SliceFlag) Get(ctx *Context) []float64 + Get returns the flag’s value in the given Context. + +func (f *Float64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Float64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Float64SliceFlag) GetDestination() []float64 + +func (f *Float64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Float64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Float64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Float64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Float64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Float64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Float64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Float64SliceFlag) SetDestination(slice []float64) + +func (f *Float64SliceFlag) SetValue(slice []float64) + +func (f *Float64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Float64SliceFlag) TakesValue() bool + TakesValue returns true if the flag takes a value, otherwise false + +type Generic interface { + Set(value string) error + String() string +} + Generic is a generic parseable type identified by a specific flag + +type GenericFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Generic + Destination *Generic + + Aliases []string + EnvVars []string + + TakesFile bool +} + GenericFlag is a flag with type Generic + +func (f GenericFlag) Apply(set *flag.FlagSet) error + 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) Get(ctx *Context) interface{} + Get returns the flag’s value in the given Context. + +func (f *GenericFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *GenericFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *GenericFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *GenericFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *GenericFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *GenericFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *GenericFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *GenericFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *GenericFlag) Names() []string + Names returns the names of the flag + +func (f *GenericFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *GenericFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Int64Flag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int64 + Destination *int64 + + Aliases []string + EnvVars []string +} + Int64Flag is a flag with type int64 + +func (f *Int64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Int64Flag) Get(ctx *Context) int64 + Get returns the flag’s value in the given Context. + +func (f *Int64Flag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Int64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Int64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Int64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Int64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Int64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Int64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Int64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Int64Flag) Names() []string + Names returns the names of the flag + +func (f *Int64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Int64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Int64Slice struct { + // Has unexported fields. +} + Int64Slice wraps []int64 to satisfy flag.Value + +func NewInt64Slice(defaults ...int64) *Int64Slice + NewInt64Slice makes an *Int64Slice with default values + +func (i *Int64Slice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *Int64Slice) Serialize() string + Serialize allows Int64Slice to fulfill Serializer + +func (i *Int64Slice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *Int64Slice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *Int64Slice) Value() []int64 + Value returns the slice of ints set by this flag + +type Int64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Int64Slice + Destination *Int64Slice + + Aliases []string + EnvVars []string +} + Int64SliceFlag is a flag with type *Int64Slice + +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Int64SliceFlag) Get(ctx *Context) []int64 + Get returns the flag’s value in the given Context. + +func (f *Int64SliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Int64SliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Int64SliceFlag) GetDestination() []int64 + +func (f *Int64SliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Int64SliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Int64SliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Int64SliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Int64SliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Int64SliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Int64SliceFlag) Names() []string + Names returns the names of the flag + +func (f *Int64SliceFlag) SetDestination(slice []int64) + +func (f *Int64SliceFlag) SetValue(slice []int64) + +func (f *Int64SliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Int64SliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type IntFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int + Destination *int + + Aliases []string + EnvVars []string +} + IntFlag is a flag with type int + +func (f *IntFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *IntFlag) Get(ctx *Context) int + Get returns the flag’s value in the given Context. + +func (f *IntFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *IntFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *IntFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *IntFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *IntFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *IntFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *IntFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *IntFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *IntFlag) Names() []string + Names returns the names of the flag + +func (f *IntFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *IntFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type IntSlice struct { + // Has unexported fields. +} + IntSlice wraps []int to satisfy flag.Value + +func NewIntSlice(defaults ...int) *IntSlice + NewIntSlice makes an *IntSlice with default values + +func (i *IntSlice) Get() interface{} + Get returns the slice of ints set by this flag + +func (i *IntSlice) Serialize() string + Serialize allows IntSlice to fulfill Serializer + +func (i *IntSlice) Set(value string) error + Set parses the value into an integer and appends it to the list of values + +func (i *IntSlice) SetInt(value int) + TODO: Consistently have specific Set function for Int64 and Float64 ? SetInt + directly adds an integer to the list of values + +func (i *IntSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (i *IntSlice) Value() []int + Value returns the slice of ints set by this flag + +type IntSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *IntSlice + Destination *IntSlice + + Aliases []string + EnvVars []string +} + IntSliceFlag is a flag with type *IntSlice + +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *IntSliceFlag) Get(ctx *Context) []int + Get returns the flag’s value in the given Context. + +func (f *IntSliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *IntSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *IntSliceFlag) GetDestination() []int + +func (f *IntSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *IntSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *IntSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *IntSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *IntSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *IntSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *IntSliceFlag) Names() []string + Names returns the names of the flag + +func (f *IntSliceFlag) SetDestination(slice []int) + +func (f *IntSliceFlag) SetValue(slice []int) + +func (f *IntSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *IntSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type MultiError interface { + error + Errors() []error +} + MultiError is an error that wraps multiple errors. + +type MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64] + MultiFloat64Flag extends Float64SliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] + MultiInt64Flag extends Int64SliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] + MultiIntFlag extends IntSliceFlag with support for using slices directly, as + Value and/or Destination. See also SliceFlag. + +type MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] + MultiStringFlag extends StringSliceFlag with support for using slices + directly, as Value and/or Destination. See also SliceFlag. + +type OnUsageErrorFunc func(cCtx *Context, err error, isSubcommand bool) error + OnUsageErrorFunc is executed if a usage error occurs. This is useful for + displaying customized usage error messages. This function is able to replace + the original error messages. If this function is not set, the "Incorrect + usage" is displayed and the execution is interrupted. + +type Path = string + +type PathFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Path + Destination *Path + + Aliases []string + EnvVars []string + + TakesFile bool +} + PathFlag is a flag with type Path + +func (f *PathFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *PathFlag) Get(ctx *Context) string + Get returns the flag’s value in the given Context. + +func (f *PathFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *PathFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *PathFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *PathFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *PathFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *PathFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *PathFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *PathFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *PathFlag) Names() []string + Names returns the names of the flag + +func (f *PathFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *PathFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type RequiredFlag interface { + Flag + + IsRequired() bool +} + 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 Serializer interface { + Serialize() string +} + Serializer is used to circumvent the limitations of flag.FlagSet.Set + +type SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { + Target T + Value S + Destination *S +} + SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with + support for using slices directly, as Value and/or Destination. See also + SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, + MultiIntFlag. + +func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error + +func (x *SliceFlag[T, S, E]) GetCategory() string + +func (x *SliceFlag[T, S, E]) GetDefaultText() string + +func (x *SliceFlag[T, S, E]) GetDestination() S + +func (x *SliceFlag[T, S, E]) GetEnvVars() []string + +func (x *SliceFlag[T, S, E]) GetUsage() string + +func (x *SliceFlag[T, S, E]) GetValue() string + +func (x *SliceFlag[T, S, E]) IsRequired() bool + +func (x *SliceFlag[T, S, E]) IsSet() bool + +func (x *SliceFlag[T, S, E]) IsVisible() bool + +func (x *SliceFlag[T, S, E]) Names() []string + +func (x *SliceFlag[T, S, E]) SetDestination(slice S) + +func (x *SliceFlag[T, S, E]) SetValue(slice S) + +func (x *SliceFlag[T, S, E]) String() string + +func (x *SliceFlag[T, S, E]) TakesValue() bool + +type SliceFlagTarget[E any] interface { + Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag + + // SetValue should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). + SetValue(slice []E) + // SetDestination should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~*[]E). + SetDestination(slice []E) + // GetDestination should return the current value referenced by any destination, or nil if nil/unset. + GetDestination() []E +} + SliceFlagTarget models a target implementation for use with SliceFlag. The + three methods, SetValue, SetDestination, and GetDestination, are necessary + to propagate Value and Destination, where Value is propagated inwards + (initially), and Destination is propagated outwards (on every update). + +type StringFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value string + Destination *string + + Aliases []string + EnvVars []string + + TakesFile bool +} + StringFlag is a flag with type string + +func (f *StringFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *StringFlag) Get(ctx *Context) string + Get returns the flag’s value in the given Context. + +func (f *StringFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *StringFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *StringFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *StringFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *StringFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *StringFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *StringFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *StringFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *StringFlag) Names() []string + Names returns the names of the flag + +func (f *StringFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *StringFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type StringSlice struct { + // Has unexported fields. +} + StringSlice wraps a []string to satisfy flag.Value + +func NewStringSlice(defaults ...string) *StringSlice + NewStringSlice creates a *StringSlice with default values + +func (s *StringSlice) Get() interface{} + Get returns the slice of strings set by this flag + +func (s *StringSlice) Serialize() string + Serialize allows StringSlice to fulfill Serializer + +func (s *StringSlice) Set(value string) error + Set appends the string value to the list of values + +func (s *StringSlice) String() string + String returns a readable representation of this value (for usage defaults) + +func (s *StringSlice) Value() []string + Value returns the slice of strings set by this flag + +type StringSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *StringSlice + Destination *StringSlice + + Aliases []string + EnvVars []string + + TakesFile bool +} + StringSliceFlag is a flag with type *StringSlice + +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *StringSliceFlag) Get(ctx *Context) []string + Get returns the flag’s value in the given Context. + +func (f *StringSliceFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *StringSliceFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *StringSliceFlag) GetDestination() []string + +func (f *StringSliceFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *StringSliceFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *StringSliceFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *StringSliceFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *StringSliceFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *StringSliceFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *StringSliceFlag) Names() []string + Names returns the names of the flag + +func (f *StringSliceFlag) SetDestination(slice []string) + +func (f *StringSliceFlag) SetValue(slice []string) + +func (f *StringSliceFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *StringSliceFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type SuggestCommandFunc func(commands []*Command, provided string) string + +type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string + +type Timestamp struct { + // Has unexported fields. +} + Timestamp wrap to satisfy golang's flag interface. + +func NewTimestamp(timestamp time.Time) *Timestamp + Timestamp constructor + +func (t *Timestamp) Get() interface{} + Get returns the flag structure + +func (t *Timestamp) Set(value string) error + Parses the string value to timestamp + +func (t *Timestamp) SetLayout(layout string) + Set the timestamp string layout for future parsing + +func (t *Timestamp) SetLocation(loc *time.Location) + Set perceived timezone of the to-be parsed time string + +func (t *Timestamp) SetTimestamp(value time.Time) + Set the timestamp value directly + +func (t *Timestamp) String() string + String returns a readable representation of this value (for usage defaults) + +func (t *Timestamp) Value() *time.Time + Value returns the timestamp value stored in the flag + +type TimestampFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Timestamp + Destination *Timestamp + + Aliases []string + EnvVars []string + + Layout string + + Timezone *time.Location +} + TimestampFlag is a flag with type *Timestamp + +func (f *TimestampFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *TimestampFlag) Get(ctx *Context) *time.Time + Get returns the flag’s value in the given Context. + +func (f *TimestampFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *TimestampFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *TimestampFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *TimestampFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *TimestampFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *TimestampFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *TimestampFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *TimestampFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *TimestampFlag) Names() []string + Names returns the names of the flag + +func (f *TimestampFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *TimestampFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type Uint64Flag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint64 + Destination *uint64 + + Aliases []string + EnvVars []string +} + Uint64Flag is a flag with type uint64 + +func (f *Uint64Flag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *Uint64Flag) Get(ctx *Context) uint64 + Get returns the flag’s value in the given Context. + +func (f *Uint64Flag) GetCategory() string + GetCategory returns the category for the flag + +func (f *Uint64Flag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *Uint64Flag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *Uint64Flag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *Uint64Flag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *Uint64Flag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *Uint64Flag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *Uint64Flag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *Uint64Flag) Names() []string + Names returns the names of the flag + +func (f *Uint64Flag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *Uint64Flag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type UintFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint + Destination *uint + + Aliases []string + EnvVars []string +} + UintFlag is a flag with type uint + +func (f *UintFlag) Apply(set *flag.FlagSet) error + Apply populates the flag given the flag set and environment + +func (f *UintFlag) Get(ctx *Context) uint + Get returns the flag’s value in the given Context. + +func (f *UintFlag) GetCategory() string + GetCategory returns the category for the flag + +func (f *UintFlag) GetDefaultText() string + GetDefaultText returns the default text for this flag + +func (f *UintFlag) GetEnvVars() []string + GetEnvVars returns the env vars for this flag + +func (f *UintFlag) GetUsage() string + GetUsage returns the usage string for the flag + +func (f *UintFlag) GetValue() string + GetValue returns the flags value as string representation and an empty + string if the flag takes no value at all. + +func (f *UintFlag) IsRequired() bool + IsRequired returns whether or not the flag is required + +func (f *UintFlag) IsSet() bool + IsSet returns whether or not the flag has been set through env or file + +func (f *UintFlag) IsVisible() bool + IsVisible returns true if the flag is not hidden, otherwise false + +func (f *UintFlag) Names() []string + Names returns the names of the flag + +func (f *UintFlag) String() string + String returns a readable representation of this value (for usage defaults) + +func (f *UintFlag) TakesValue() bool + TakesValue returns true of the flag takes a value, otherwise false + +type VisibleFlag interface { + Flag + + // IsVisible returns true if the flag is not hidden, otherwise false + IsVisible() bool +} + VisibleFlag is an interface that allows to check if a flag is visible + +type VisibleFlagCategory interface { + // Name returns the category name string + Name() string + // Flags returns a slice of VisibleFlag sorted by name + Flags() []VisibleFlag +} + VisibleFlagCategory is a category containing flags. + +package altsrc // import "github.com/urfave/cli/v2/altsrc" + + +FUNCTIONS + +func ApplyInputSourceValues(cCtx *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error + ApplyInputSourceValues iterates over all provided flags and executes + ApplyInputSourceValue on flags implementing the FlagInputSourceExtension + interface to initialize these flags to an alternate input source. + +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc + InitInputSource is used to to setup an InputSourceContext on a cli.Command + Before method. It will create a new input source based on the func provided. + If there is no error it will then apply the new input source to any flags + that are supported by the input source + +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(cCtx *cli.Context) (InputSourceContext, error)) cli.BeforeFunc + InitInputSourceWithContext is used to to setup an InputSourceContext on a + cli.Command Before method. It will create a new input source based on the + func provided with potentially using existing cli.Context values to + initialize itself. If there is no error it will then apply the new input + source to any flags that are supported by the input source + +func NewJSONSourceFromFlagFunc(flag string) func(c *cli.Context) (InputSourceContext, error) + NewJSONSourceFromFlagFunc returns a func that takes a cli.Context and + returns an InputSourceContext suitable for retrieving config variables from + a file containing JSON data with the file name defined by the given flag. + +func NewTomlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) + NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a + provided flag name and source context. + +func NewYamlSourceFromFlagFunc(flagFileName string) func(cCtx *cli.Context) (InputSourceContext, error) + NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a + provided flag name and source context. + + +TYPES + +type BoolFlag struct { + *cli.BoolFlag + // Has unexported fields. +} + BoolFlag is the flag type that wraps cli.BoolFlag to allow for other values + to be specified + +func NewBoolFlag(fl *cli.BoolFlag) *BoolFlag + NewBoolFlag creates a new BoolFlag + +func (f *BoolFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + BoolFlag.Apply + +func (f *BoolFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Bool value to the flagSet if required + +type DurationFlag struct { + *cli.DurationFlag + // Has unexported fields. +} + DurationFlag is the flag type that wraps cli.DurationFlag to allow for other + values to be specified + +func NewDurationFlag(fl *cli.DurationFlag) *DurationFlag + NewDurationFlag creates a new DurationFlag + +func (f *DurationFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + DurationFlag.Apply + +func (f *DurationFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Duration value to the flagSet if required + +type FlagInputSourceExtension interface { + cli.Flag + ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error +} + FlagInputSourceExtension is an extension interface of cli.Flag that allows a + value to be set on the existing parsed flags. + +type Float64Flag struct { + *cli.Float64Flag + // Has unexported fields. +} + Float64Flag is the flag type that wraps cli.Float64Flag to allow for other + values to be specified + +func NewFloat64Flag(fl *cli.Float64Flag) *Float64Flag + NewFloat64Flag creates a new Float64Flag + +func (f *Float64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Float64Flag.Apply + +func (f *Float64Flag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Float64 value to the flagSet if required + +type Float64SliceFlag struct { + *cli.Float64SliceFlag + // Has unexported fields. +} + Float64SliceFlag is the flag type that wraps cli.Float64SliceFlag to allow + for other values to be specified + +func NewFloat64SliceFlag(fl *cli.Float64SliceFlag) *Float64SliceFlag + NewFloat64SliceFlag creates a new Float64SliceFlag + +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Float64SliceFlag.Apply + +type GenericFlag struct { + *cli.GenericFlag + // Has unexported fields. +} + GenericFlag is the flag type that wraps cli.GenericFlag to allow for other + values to be specified + +func NewGenericFlag(fl *cli.GenericFlag) *GenericFlag + NewGenericFlag creates a new GenericFlag + +func (f *GenericFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + GenericFlag.Apply + +func (f *GenericFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a generic value to the flagSet if required + +type InputSourceContext interface { + Source() string + + Int(name string) (int, error) + Duration(name string) (time.Duration, error) + Float64(name string) (float64, error) + String(name string) (string, error) + StringSlice(name string) ([]string, error) + IntSlice(name string) ([]int, error) + Generic(name string) (cli.Generic, error) + Bool(name string) (bool, error) + + // Has unexported methods. +} + InputSourceContext is an interface used to allow other input sources to be + implemented as needed. + + Source returns an identifier for the input source. In case of file source it + should return path to the file. + +func NewJSONSource(data []byte) (InputSourceContext, error) + NewJSONSource returns an InputSourceContext suitable for retrieving config + variables from raw JSON data. + +func NewJSONSourceFromFile(f string) (InputSourceContext, error) + NewJSONSourceFromFile returns an InputSourceContext suitable for retrieving + config variables from a file (or url) containing JSON data. + +func NewJSONSourceFromReader(r io.Reader) (InputSourceContext, error) + NewJSONSourceFromReader returns an InputSourceContext suitable for + retrieving config variables from an io.Reader that returns JSON data. + +func NewTomlSourceFromFile(file string) (InputSourceContext, error) + NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath. + +func NewYamlSourceFromFile(file string) (InputSourceContext, error) + NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. + +type Int64Flag struct { + *cli.Int64Flag + // Has unexported fields. +} + Int64Flag is the flag type that wraps cli.Int64Flag to allow for other + values to be specified + +func NewInt64Flag(fl *cli.Int64Flag) *Int64Flag + NewInt64Flag creates a new Int64Flag + +func (f *Int64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Int64Flag.Apply + +type Int64SliceFlag struct { + *cli.Int64SliceFlag + // Has unexported fields. +} + Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow for + other values to be specified + +func NewInt64SliceFlag(fl *cli.Int64SliceFlag) *Int64SliceFlag + NewInt64SliceFlag creates a new Int64SliceFlag + +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Int64SliceFlag.Apply + +type IntFlag struct { + *cli.IntFlag + // Has unexported fields. +} + IntFlag is the flag type that wraps cli.IntFlag to allow for other values to + be specified + +func NewIntFlag(fl *cli.IntFlag) *IntFlag + NewIntFlag creates a new IntFlag + +func (f *IntFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + IntFlag.Apply + +func (f *IntFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a int value to the flagSet if required + +type IntSliceFlag struct { + *cli.IntSliceFlag + // Has unexported fields. +} + IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow for other + values to be specified + +func NewIntSliceFlag(fl *cli.IntSliceFlag) *IntSliceFlag + NewIntSliceFlag creates a new IntSliceFlag + +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + IntSliceFlag.Apply + +func (f *IntSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a IntSlice value if required + +type MapInputSource struct { + // Has unexported fields. +} + MapInputSource implements InputSourceContext to return data from the map + that is loaded. + +func NewMapInputSource(file string, valueMap map[interface{}]interface{}) *MapInputSource + NewMapInputSource creates a new MapInputSource for implementing custom input + sources. + +func (fsm *MapInputSource) Bool(name string) (bool, error) + Bool returns an bool from the map otherwise returns false + +func (fsm *MapInputSource) Duration(name string) (time.Duration, error) + Duration returns a duration from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) Float64(name string) (float64, error) + Float64 returns an float64 from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) + Generic returns an cli.Generic from the map if it exists otherwise returns + nil + +func (fsm *MapInputSource) Int(name string) (int, error) + Int returns an int from the map if it exists otherwise returns 0 + +func (fsm *MapInputSource) IntSlice(name string) ([]int, error) + IntSlice returns an []int from the map if it exists otherwise returns nil + +func (fsm *MapInputSource) Source() string + Source returns the path of the source file + +func (fsm *MapInputSource) String(name string) (string, error) + String returns a string from the map if it exists otherwise returns an empty + string + +func (fsm *MapInputSource) StringSlice(name string) ([]string, error) + StringSlice returns an []string from the map if it exists otherwise returns + nil + +type PathFlag struct { + *cli.PathFlag + // Has unexported fields. +} + PathFlag is the flag type that wraps cli.PathFlag to allow for other values + to be specified + +func NewPathFlag(fl *cli.PathFlag) *PathFlag + NewPathFlag creates a new PathFlag + +func (f *PathFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + PathFlag.Apply + +func (f *PathFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a Path value to the flagSet if required + +type StringFlag struct { + *cli.StringFlag + // Has unexported fields. +} + StringFlag is the flag type that wraps cli.StringFlag to allow for other + values to be specified + +func NewStringFlag(fl *cli.StringFlag) *StringFlag + NewStringFlag creates a new StringFlag + +func (f *StringFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + StringFlag.Apply + +func (f *StringFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a String value to the flagSet if required + +type StringSliceFlag struct { + *cli.StringSliceFlag + // Has unexported fields. +} + StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow for + other values to be specified + +func NewStringSliceFlag(fl *cli.StringSliceFlag) *StringSliceFlag + NewStringSliceFlag creates a new StringSliceFlag + +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + StringSliceFlag.Apply + +func (f *StringSliceFlag) ApplyInputSourceValue(cCtx *cli.Context, isc InputSourceContext) error + ApplyInputSourceValue applies a StringSlice value to the flagSet if required + +type Uint64Flag struct { + *cli.Uint64Flag + // Has unexported fields. +} + Uint64Flag is the flag type that wraps cli.Uint64Flag to allow for other + values to be specified + +func NewUint64Flag(fl *cli.Uint64Flag) *Uint64Flag + NewUint64Flag creates a new Uint64Flag + +func (f *Uint64Flag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + Uint64Flag.Apply + +type UintFlag struct { + *cli.UintFlag + // Has unexported fields. +} + UintFlag is the flag type that wraps cli.UintFlag to allow for other values + to be specified + +func NewUintFlag(fl *cli.UintFlag) *UintFlag + NewUintFlag creates a new UintFlag + +func (f *UintFlag) Apply(set *flag.FlagSet) error + Apply saves the flagSet for later usage calls, then calls the wrapped + UintFlag.Apply + diff --git a/vendor/github.com/urfave/cli/v2/help.go b/vendor/github.com/urfave/cli/v2/help.go index c1e974a48..9a8d2437d 100644 --- a/vendor/github.com/urfave/cli/v2/help.go +++ b/vendor/github.com/urfave/cli/v2/help.go @@ -10,34 +10,39 @@ import ( "unicode/utf8" ) +const ( + helpName = "help" + helpAlias = "h" +) + var helpCommand = &Command{ - Name: "help", - Aliases: []string{"h"}, + Name: helpName, + Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) error { - args := c.Args() + Action: func(cCtx *Context) error { + args := cCtx.Args() if args.Present() { - return ShowCommandHelp(c, args.First()) + return ShowCommandHelp(cCtx, args.First()) } - _ = ShowAppHelp(c) + _ = ShowAppHelp(cCtx) return nil }, } var helpSubcommand = &Command{ - Name: "help", - Aliases: []string{"h"}, + Name: helpName, + Aliases: []string{helpAlias}, Usage: "Shows a list of commands or help for one command", ArgsUsage: "[command]", - Action: func(c *Context) error { - args := c.Args() + Action: func(cCtx *Context) error { + args := cCtx.Args() if args.Present() { - return ShowCommandHelp(c, args.First()) + return ShowCommandHelp(cCtx, args.First()) } - return ShowSubcommandHelp(c) + return ShowSubcommandHelp(cCtx) }, } @@ -59,6 +64,11 @@ var HelpPrinter helpPrinter = printHelp // HelpPrinterCustom is a function that writes the help output. It is used as // the default implementation of HelpPrinter, and may be called directly if // the ExtraInfo field is set on an App. +// +// In the default implementation, if the customFuncs argument contains a +// "wrapAt" key, which is a function which takes no arguments and returns +// an int, this int value will be used to produce a "wrap" function used +// by the default template to wrap long lines. var HelpPrinterCustom helpPrinterCustom = printHelpCustom // VersionPrinter prints the version for the App @@ -71,30 +81,30 @@ func ShowAppHelpAndExit(c *Context, exitCode int) { } // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) error { - template := c.App.CustomAppHelpTemplate - if template == "" { - template = AppHelpTemplate +func ShowAppHelp(cCtx *Context) error { + tpl := cCtx.App.CustomAppHelpTemplate + if tpl == "" { + tpl = AppHelpTemplate } - if c.App.ExtraInfo == nil { - HelpPrinter(c.App.Writer, template, c.App) + if cCtx.App.ExtraInfo == nil { + HelpPrinter(cCtx.App.Writer, tpl, cCtx.App) return nil } customAppData := func() map[string]interface{} { return map[string]interface{}{ - "ExtraInfo": c.App.ExtraInfo, + "ExtraInfo": cCtx.App.ExtraInfo, } } - HelpPrinterCustom(c.App.Writer, template, c.App, customAppData()) + HelpPrinterCustom(cCtx.App.Writer, tpl, cCtx.App, customAppData()) return nil } // DefaultAppComplete prints the list of subcommands as the default app completion method -func DefaultAppComplete(c *Context) { - DefaultCompleteWithFlags(nil)(c) +func DefaultAppComplete(cCtx *Context) { + DefaultCompleteWithFlags(nil)(cCtx) } func printCommandSuggestions(commands []*Command, writer io.Writer) { @@ -102,7 +112,7 @@ func printCommandSuggestions(commands []*Command, writer io.Writer) { if command.Hidden { continue } - if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + if strings.HasSuffix(os.Getenv("SHELL"), "zsh") { for _, name := range command.Names() { _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) } @@ -159,23 +169,30 @@ func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { } } -func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { - return func(c *Context) { +func DefaultCompleteWithFlags(cmd *Command) func(cCtx *Context) { + return func(cCtx *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) + printFlagSuggestions(lastArg, cmd.Flags, cCtx.App.Writer) + + return } + + printFlagSuggestions(lastArg, cCtx.App.Flags, cCtx.App.Writer) + return } } + if cmd != nil { - printCommandSuggestions(cmd.Subcommands, c.App.Writer) - } else { - printCommandSuggestions(c.App.Commands, c.App.Writer) + printCommandSuggestions(cmd.Subcommands, cCtx.App.Writer) + return } + + printCommandSuggestions(cCtx.App.Commands, cCtx.App.Writer) } } @@ -207,40 +224,52 @@ func ShowCommandHelp(ctx *Context, command string) error { } if ctx.App.CommandNotFound == nil { - return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) + errMsg := fmt.Sprintf("No help topic for '%v'", command) + if ctx.App.Suggest { + if suggestion := SuggestCommand(ctx.App.Commands, command); suggestion != "" { + errMsg += ". " + suggestion + } + } + return Exit(errMsg, 3) } ctx.App.CommandNotFound(ctx, command) return nil } +// ShowSubcommandHelpAndExit - Prints help for the given subcommand and exits with exit code. +func ShowSubcommandHelpAndExit(c *Context, exitCode int) { + _ = ShowSubcommandHelp(c) + os.Exit(exitCode) +} + // ShowSubcommandHelp prints help for the given subcommand -func ShowSubcommandHelp(c *Context) error { - if c == nil { +func ShowSubcommandHelp(cCtx *Context) error { + if cCtx == nil { return nil } - if c.Command != nil { - return ShowCommandHelp(c, c.Command.Name) + if cCtx.Command != nil { + return ShowCommandHelp(cCtx, cCtx.Command.Name) } - return ShowCommandHelp(c, "") + return ShowCommandHelp(cCtx, "") } // ShowVersion prints the version number of the App -func ShowVersion(c *Context) { - VersionPrinter(c) +func ShowVersion(cCtx *Context) { + VersionPrinter(cCtx) } -func printVersion(c *Context) { - _, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) +func printVersion(cCtx *Context) { + _, _ = fmt.Fprintf(cCtx.App.Writer, "%v version %v\n", cCtx.App.Name, cCtx.App.Version) } // ShowCompletions prints the lists of commands within a given context -func ShowCompletions(c *Context) { - a := c.App +func ShowCompletions(cCtx *Context) { + a := cCtx.App if a != nil && a.BashComplete != nil { - a.BashComplete(c) + a.BashComplete(cCtx) } } @@ -262,9 +291,29 @@ func ShowCommandCompletions(ctx *Context, command string) { // The customFuncs map will be combined with a default template.FuncMap to // allow using arbitrary functions in template rendering. func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) { + + const maxLineLength = 10000 + funcMap := template.FuncMap{ - "join": strings.Join, + "join": strings.Join, + "indent": indent, + "nindent": nindent, + "trim": strings.TrimSpace, + "wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) }, + "offset": offset, } + + if customFuncs["wrapAt"] != nil { + if wa, ok := customFuncs["wrapAt"]; ok { + if waf, ok := wa.(func() int); ok { + wrapAt := waf() + customFuncs["wrap"] = func(input string, offset int) string { + return wrap(input, offset, wrapAt) + } + } + } + } + for key, value := range customFuncs { funcMap[key] = value } @@ -288,20 +337,20 @@ func printHelp(out io.Writer, templ string, data interface{}) { HelpPrinterCustom(out, templ, data, nil) } -func checkVersion(c *Context) bool { +func checkVersion(cCtx *Context) bool { found := false for _, name := range VersionFlag.Names() { - if c.Bool(name) { + if cCtx.Bool(name) { found = true } } return found } -func checkHelp(c *Context) bool { +func checkHelp(cCtx *Context) bool { found := false for _, name := range HelpFlag.Names() { - if c.Bool(name) { + if cCtx.Bool(name) { found = true } } @@ -317,9 +366,9 @@ func checkCommandHelp(c *Context, name string) bool { return false } -func checkSubcommandHelp(c *Context) bool { - if c.Bool("h") || c.Bool("help") { - _ = ShowSubcommandHelp(c) +func checkSubcommandHelp(cCtx *Context) bool { + if cCtx.Bool("h") || cCtx.Bool("help") { + _ = ShowSubcommandHelp(cCtx) return true } @@ -341,20 +390,20 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { return true, arguments[:pos] } -func checkCompletions(c *Context) bool { - if !c.shellComplete { +func checkCompletions(cCtx *Context) bool { + if !cCtx.shellComplete { return false } - if args := c.Args(); args.Present() { + if args := cCtx.Args(); args.Present() { name := args.First() - if cmd := c.App.Command(name); cmd != nil { + if cmd := cCtx.App.Command(name); cmd != nil { // let the command handle the completion return false } } - ShowCompletions(c) + ShowCompletions(cCtx) return true } @@ -366,3 +415,64 @@ func checkCommandCompletions(c *Context, name string) bool { ShowCommandCompletions(c, name) return true } + +func indent(spaces int, v string) string { + pad := strings.Repeat(" ", spaces) + return pad + strings.Replace(v, "\n", "\n"+pad, -1) +} + +func nindent(spaces int, v string) string { + return "\n" + indent(spaces, v) +} + +func wrap(input string, offset int, wrapAt int) string { + var sb strings.Builder + + lines := strings.Split(input, "\n") + + padding := strings.Repeat(" ", offset) + + for i, line := range lines { + if i != 0 { + sb.WriteString(padding) + } + + sb.WriteString(wrapLine(line, offset, wrapAt, padding)) + + if i != len(lines)-1 { + sb.WriteString("\n") + } + } + + return sb.String() +} + +func wrapLine(input string, offset int, wrapAt int, padding string) string { + if wrapAt <= offset || len(input) <= wrapAt-offset { + return input + } + + lineWidth := wrapAt - offset + words := strings.Fields(input) + if len(words) == 0 { + return input + } + + wrapped := words[0] + spaceLeft := lineWidth - len(wrapped) + for _, word := range words[1:] { + if len(word)+1 > spaceLeft { + wrapped += "\n" + padding + word + spaceLeft = lineWidth - len(word) + } else { + wrapped += " " + word + spaceLeft -= 1 + len(word) + } + } + + return wrapped +} + +func offset(input string, fixed int) int { + return len(input) + fixed +} diff --git a/vendor/github.com/urfave/cli/v2/mkdocs-requirements.txt b/vendor/github.com/urfave/cli/v2/mkdocs-requirements.txt new file mode 100644 index 000000000..482ad0622 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/mkdocs-requirements.txt @@ -0,0 +1,5 @@ +mkdocs-git-revision-date-localized-plugin~=1.0 +mkdocs-material-extensions~=1.0 +mkdocs-material~=8.2 +mkdocs~=1.3 +pygments~=2.12 diff --git a/vendor/github.com/urfave/cli/v2/mkdocs.yml b/vendor/github.com/urfave/cli/v2/mkdocs.yml new file mode 100644 index 000000000..73b88c509 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/mkdocs.yml @@ -0,0 +1,62 @@ +# NOTE: the mkdocs dependencies will need to be installed out of +# band until this whole thing gets more automated: +# +# pip install -r mkdocs-requirements.txt +# + +site_name: urfave/cli +site_url: https://cli.urfave.org/ +repo_url: https://github.com/urfave/cli +edit_uri: edit/main/docs/ +nav: + - Home: index.md + - v2 Manual: v2/index.md + - v1 Manual: v1/index.md +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-4 + name: dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-7 + name: light mode +plugins: + - git-revision-date-localized + - search +# NOTE: this is the recommended configuration from +# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration +markdown_extensions: + - abbr + - admonition + - attr_list + - def_list + - footnotes + - meta + - md_in_html + - toc: + permalink: true + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde diff --git a/vendor/github.com/urfave/cli/v2/parse.go b/vendor/github.com/urfave/cli/v2/parse.go index 7df17296a..a2db306e1 100644 --- a/vendor/github.com/urfave/cli/v2/parse.go +++ b/vendor/github.com/urfave/cli/v2/parse.go @@ -26,9 +26,8 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple return err } - errStr := err.Error() - trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") - if errStr == trimmed { + trimmed, trimErr := flagFromError(err) + if trimErr != nil { return err } @@ -67,6 +66,19 @@ func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComple } } +const providedButNotDefinedErrMsg = "flag provided but not defined: -" + +// flagFromError tries to parse a provided flag from an error message. If the +// parsing fials, it returns the input error and an empty string +func flagFromError(err error) (string, error) { + errStr := err.Error() + trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg) + if errStr == trimmed { + return "", err + } + return trimmed, nil +} + func splitShortOptions(set *flag.FlagSet, arg string) []string { shortFlagsExist := func(s string) bool { for _, c := range s[1:] { diff --git a/vendor/github.com/urfave/cli/v2/sliceflag.go b/vendor/github.com/urfave/cli/v2/sliceflag.go new file mode 100644 index 000000000..7dea3576a --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/sliceflag.go @@ -0,0 +1,293 @@ +//go:build go1.18 +// +build go1.18 + +package cli + +import ( + "flag" + "reflect" +) + +type ( + // SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with support for using slices directly, + // as Value and/or Destination. + // See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. + SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { + Target T + Value S + Destination *S + } + + // SliceFlagTarget models a target implementation for use with SliceFlag. + // The three methods, SetValue, SetDestination, and GetDestination, are necessary to propagate Value and + // Destination, where Value is propagated inwards (initially), and Destination is propagated outwards (on every + // update). + SliceFlagTarget[E any] interface { + Flag + RequiredFlag + DocGenerationFlag + VisibleFlag + CategorizableFlag + + // SetValue should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). + SetValue(slice []E) + // SetDestination should propagate the given slice to the target, ideally as a new value. + // Note that a nil slice should nil/clear any existing value (modelled as ~*[]E). + SetDestination(slice []E) + // GetDestination should return the current value referenced by any destination, or nil if nil/unset. + GetDestination() []E + } + + // MultiStringFlag extends StringSliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] + + // MultiFloat64Flag extends Float64SliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64] + + // MultiInt64Flag extends Int64SliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] + + // MultiIntFlag extends IntSliceFlag with support for using slices directly, as Value and/or Destination. + // See also SliceFlag. + MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] + + flagValueHook struct { + value Generic + hook func() + } +) + +var ( + // compile time assertions + + _ SliceFlagTarget[string] = (*StringSliceFlag)(nil) + _ SliceFlagTarget[string] = (*SliceFlag[*StringSliceFlag, []string, string])(nil) + _ SliceFlagTarget[string] = (*MultiStringFlag)(nil) + _ SliceFlagTarget[float64] = (*MultiFloat64Flag)(nil) + _ SliceFlagTarget[int64] = (*MultiInt64Flag)(nil) + _ SliceFlagTarget[int] = (*MultiIntFlag)(nil) + + _ Generic = (*flagValueHook)(nil) + _ Serializer = (*flagValueHook)(nil) +) + +func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error { + x.Target.SetValue(x.convertSlice(x.Value)) + + destination := x.Destination + if destination == nil { + x.Target.SetDestination(nil) + + return x.Target.Apply(set) + } + + x.Target.SetDestination(x.convertSlice(*destination)) + + return applyFlagValueHook(set, x.Target.Apply, func() { + *destination = x.Target.GetDestination() + }) +} + +func (x *SliceFlag[T, S, E]) convertSlice(slice S) []E { + result := make([]E, len(slice)) + copy(result, slice) + return result +} + +func (x *SliceFlag[T, S, E]) SetValue(slice S) { + x.Value = slice +} + +func (x *SliceFlag[T, S, E]) SetDestination(slice S) { + if slice != nil { + x.Destination = &slice + } else { + x.Destination = nil + } +} + +func (x *SliceFlag[T, S, E]) GetDestination() S { + if destination := x.Destination; destination != nil { + return *destination + } + return nil +} + +func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } +func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } +func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } +func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } +func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } +func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } +func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } +func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } +func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } +func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } +func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } + +func (x *flagValueHook) Set(value string) error { + if err := x.value.Set(value); err != nil { + return err + } + x.hook() + return nil +} + +func (x *flagValueHook) String() string { + // note: this is necessary due to the way Go's flag package handles defaults + isZeroValue := func(f flag.Value, v string) bool { + /* + https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/flag/flag.go;drc=2580d0e08d5e9f979b943758d3c49877fb2324cb;l=453 + + Copyright (c) 2009 The Go Authors. All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + // Build a zero value of the flag's Value type, and see if the + // result of calling its String method equals the value passed in. + // This works unless the Value type is itself an interface type. + typ := reflect.TypeOf(f) + var z reflect.Value + if typ.Kind() == reflect.Pointer { + z = reflect.New(typ.Elem()) + } else { + z = reflect.Zero(typ) + } + return v == z.Interface().(flag.Value).String() + } + if x.value != nil { + // only return non-empty if not the same string as returned by the zero value + if s := x.value.String(); !isZeroValue(x.value, s) { + return s + } + } + return `` +} + +func (x *flagValueHook) Serialize() string { + if value, ok := x.value.(Serializer); ok { + return value.Serialize() + } + return x.String() +} + +// applyFlagValueHook wraps calls apply then wraps flags to call a hook function on update and after initial apply. +func applyFlagValueHook(set *flag.FlagSet, apply func(set *flag.FlagSet) error, hook func()) error { + if apply == nil || set == nil || hook == nil { + panic(`invalid input`) + } + var tmp flag.FlagSet + if err := apply(&tmp); err != nil { + return err + } + tmp.VisitAll(func(f *flag.Flag) { set.Var(&flagValueHook{value: f.Value, hook: hook}, f.Name, f.Usage) }) + hook() + return nil +} + +// newSliceFlagValue is for implementing SliceFlagTarget.SetValue and SliceFlagTarget.SetDestination. +// It's e.g. as part of StringSliceFlag.SetValue, using the factory NewStringSlice. +func newSliceFlagValue[R any, S ~[]E, E any](factory func(defaults ...E) *R, defaults S) *R { + if defaults == nil { + return nil + } + return factory(defaults...) +} + +// unwrapFlagValue strips any/all *flagValueHook wrappers. +func unwrapFlagValue(v flag.Value) flag.Value { + for { + h, ok := v.(*flagValueHook) + if !ok { + return v + } + v = h.value + } +} + +// NOTE: the methods below are in this file to make use of the build constraint + +func (f *Float64SliceFlag) SetValue(slice []float64) { + f.Value = newSliceFlagValue(NewFloat64Slice, slice) +} + +func (f *Float64SliceFlag) SetDestination(slice []float64) { + f.Destination = newSliceFlagValue(NewFloat64Slice, slice) +} + +func (f *Float64SliceFlag) GetDestination() []float64 { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + +func (f *Int64SliceFlag) SetValue(slice []int64) { + f.Value = newSliceFlagValue(NewInt64Slice, slice) +} + +func (f *Int64SliceFlag) SetDestination(slice []int64) { + f.Destination = newSliceFlagValue(NewInt64Slice, slice) +} + +func (f *Int64SliceFlag) GetDestination() []int64 { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + +func (f *IntSliceFlag) SetValue(slice []int) { + f.Value = newSliceFlagValue(NewIntSlice, slice) +} + +func (f *IntSliceFlag) SetDestination(slice []int) { + f.Destination = newSliceFlagValue(NewIntSlice, slice) +} + +func (f *IntSliceFlag) GetDestination() []int { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} + +func (f *StringSliceFlag) SetValue(slice []string) { + f.Value = newSliceFlagValue(NewStringSlice, slice) +} + +func (f *StringSliceFlag) SetDestination(slice []string) { + f.Destination = newSliceFlagValue(NewStringSlice, slice) +} + +func (f *StringSliceFlag) GetDestination() []string { + if destination := f.Destination; destination != nil { + return destination.Value() + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/sliceflag_pre18.go b/vendor/github.com/urfave/cli/v2/sliceflag_pre18.go new file mode 100644 index 000000000..1173ae740 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/sliceflag_pre18.go @@ -0,0 +1,10 @@ +//go:build !go1.18 +// +build !go1.18 + +package cli + +import ( + "flag" +) + +func unwrapFlagValue(v flag.Value) flag.Value { return v } diff --git a/vendor/github.com/urfave/cli/v2/suggestions.go b/vendor/github.com/urfave/cli/v2/suggestions.go new file mode 100644 index 000000000..87fa905dd --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/suggestions.go @@ -0,0 +1,60 @@ +package cli + +import ( + "fmt" + + "github.com/xrash/smetrics" +) + +func jaroWinkler(a, b string) float64 { + // magic values are from https://github.com/xrash/smetrics/blob/039620a656736e6ad994090895784a7af15e0b80/jaro-winkler.go#L8 + const ( + boostThreshold = 0.7 + prefixSize = 4 + ) + return smetrics.JaroWinkler(a, b, boostThreshold, prefixSize) +} + +func suggestFlag(flags []Flag, provided string, hideHelp bool) string { + distance := 0.0 + suggestion := "" + + for _, flag := range flags { + flagNames := flag.Names() + if !hideHelp { + flagNames = append(flagNames, HelpFlag.Names()...) + } + for _, name := range flagNames { + newDistance := jaroWinkler(name, provided) + if newDistance > distance { + distance = newDistance + suggestion = name + } + } + } + + if len(suggestion) == 1 { + suggestion = "-" + suggestion + } else if len(suggestion) > 1 { + suggestion = "--" + suggestion + } + + return suggestion +} + +// suggestCommand takes a list of commands and a provided string to suggest a +// command name +func suggestCommand(commands []*Command, provided string) (suggestion string) { + distance := 0.0 + for _, command := range commands { + for _, name := range append(command.Names(), helpName, helpAlias) { + newDistance := jaroWinkler(name, provided) + if newDistance > distance { + distance = newDistance + suggestion = name + } + } + } + + return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) +} diff --git a/vendor/github.com/urfave/cli/v2/template.go b/vendor/github.com/urfave/cli/v2/template.go index aee3e0494..f3116fd2c 100644 --- a/vendor/github.com/urfave/cli/v2/template.go +++ b/vendor/github.com/urfave/cli/v2/template.go @@ -4,16 +4,16 @@ package cli // 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}} + {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{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}} + {{if .UsageText}}{{wrap .UsageText 3}}{{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}} + {{wrap .Description 3}}{{end}}{{if len .Authors}} AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: {{range $index, $author := .Authors}}{{if $index}} @@ -22,34 +22,44 @@ AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{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}}{{end}}{{if .VisibleFlags}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlagCategories}} + +GLOBAL OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} GLOBAL OPTIONS: {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + {{end}}{{wrap $option.String 6}}{{end}}{{end}}{{end}}{{if .Copyright}} COPYRIGHT: - {{.Copyright}}{{end}} + {{wrap .Copyright 3}}{{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}} + {{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + {{if .UsageText}}{{wrap .UsageText 3}}{{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}} + {{wrap .Description 3}}{{end}}{{if .VisibleFlagCategories}} + +OPTIONS:{{range .VisibleFlagCategories}} + {{if .Name}}{{.Name}} + {{end}}{{range .Flags}}{{.}} + {{end}}{{end}}{{else}}{{if .VisibleFlags}} OPTIONS: {{range .VisibleFlags}}{{.}} - {{end}}{{end}} + {{end}}{{end}}{{end}} ` // SubcommandHelpTemplate is the text template for the subcommand help topic. @@ -59,10 +69,10 @@ var SubcommandHelpTemplate = `NAME: {{.HelpName}} - {{.Usage}} USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{.Description}}{{end}} + {{wrap .Description 3}}{{end}} COMMANDS:{{range .VisibleCategories}}{{if .Name}} {{.Name}}:{{range .VisibleCommands}} @@ -74,9 +84,9 @@ OPTIONS: {{end}}{{end}} ` -var MarkdownDocTemplate = `% {{ .App.Name }} 8 +var MarkdownDocTemplate = `{{if gt .SectionNum 0}}% {{ .App.Name }} {{ .SectionNum }} -# NAME +{{end}}# NAME {{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} @@ -86,16 +96,18 @@ var MarkdownDocTemplate = `% {{ .App.Name }} 8 {{ if .SynopsisArgs }} ` + "```" + ` {{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` -{{ end }}{{ if .App.UsageText }} +{{ end }}{{ if .App.Description }} # DESCRIPTION -{{ .App.UsageText }} +{{ .App.Description }} {{ end }} **Usage**: -` + "```" + ` +` + "```" + `{{ if .App.UsageText }} +{{ .App.UsageText }} +{{ else }} {{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] -` + "```" + ` +{{ end }}` + "```" + ` {{ if .GlobalArgs }} # GLOBAL OPTIONS {{ range $v := .GlobalArgs }} diff --git a/vendor/github.com/urfave/cli/v2/zz_generated.flags.go b/vendor/github.com/urfave/cli/v2/zz_generated.flags.go new file mode 100644 index 000000000..b89566f90 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/zz_generated.flags.go @@ -0,0 +1,674 @@ +// WARNING: this file is generated. DO NOT EDIT + +package cli + +import "time" + +// Float64SliceFlag is a flag with type *Float64Slice +type Float64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Float64Slice + Destination *Float64Slice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Float64SliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64SliceFlag) IsVisible() bool { + return !f.Hidden +} + +// GenericFlag is a flag with type Generic +type GenericFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Generic + Destination *Generic + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f *GenericFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *GenericFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *GenericFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *GenericFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *GenericFlag) IsVisible() bool { + return !f.Hidden +} + +// Int64SliceFlag is a flag with type *Int64Slice +type Int64SliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Int64Slice + Destination *Int64Slice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Int64SliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Int64SliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64SliceFlag) IsVisible() bool { + return !f.Hidden +} + +// IntSliceFlag is a flag with type *IntSlice +type IntSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *IntSlice + Destination *IntSlice + + Aliases []string + EnvVars []string +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *IntSliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *IntSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntSliceFlag) IsVisible() bool { + return !f.Hidden +} + +// PathFlag is a flag with type Path +type PathFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value Path + Destination *Path + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f *PathFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *PathFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *PathFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *PathFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *PathFlag) IsVisible() bool { + return !f.Hidden +} + +// StringSliceFlag is a flag with type *StringSlice +type StringSliceFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *StringSlice + Destination *StringSlice + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *StringSliceFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *StringSliceFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringSliceFlag) IsVisible() bool { + return !f.Hidden +} + +// TimestampFlag is a flag with type *Timestamp +type TimestampFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value *Timestamp + Destination *Timestamp + + Aliases []string + EnvVars []string + + Layout string + + Timezone *time.Location +} + +// String returns a readable representation of this value (for usage defaults) +func (f *TimestampFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *TimestampFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *TimestampFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *TimestampFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *TimestampFlag) IsVisible() bool { + return !f.Hidden +} + +// BoolFlag is a flag with type bool +type BoolFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value bool + Destination *bool + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *BoolFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *BoolFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *BoolFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *BoolFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *BoolFlag) IsVisible() bool { + return !f.Hidden +} + +// Float64Flag is a flag with type float64 +type Float64Flag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value float64 + Destination *float64 + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Float64Flag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64Flag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Float64Flag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Float64Flag) IsVisible() bool { + return !f.Hidden +} + +// IntFlag is a flag with type int +type IntFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int + Destination *int + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *IntFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *IntFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *IntFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *IntFlag) IsVisible() bool { + return !f.Hidden +} + +// Int64Flag is a flag with type int64 +type Int64Flag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value int64 + Destination *int64 + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Int64Flag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64Flag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Int64Flag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Int64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Int64Flag) IsVisible() bool { + return !f.Hidden +} + +// StringFlag is a flag with type string +type StringFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value string + Destination *string + + Aliases []string + EnvVars []string + + TakesFile bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f *StringFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *StringFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *StringFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *StringFlag) IsVisible() bool { + return !f.Hidden +} + +// DurationFlag is a flag with type time.Duration +type DurationFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value time.Duration + Destination *time.Duration + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *DurationFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *DurationFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *DurationFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *DurationFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *DurationFlag) IsVisible() bool { + return !f.Hidden +} + +// UintFlag is a flag with type uint +type UintFlag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint + Destination *uint + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *UintFlag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintFlag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *UintFlag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *UintFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *UintFlag) IsVisible() bool { + return !f.Hidden +} + +// Uint64Flag is a flag with type uint64 +type Uint64Flag struct { + Name string + + Category string + DefaultText string + FilePath string + Usage string + + Required bool + Hidden bool + HasBeenSet bool + + Value uint64 + Destination *uint64 + + Aliases []string + EnvVars []string +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Uint64Flag) String() string { + return FlagStringer(f) +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64Flag) IsSet() bool { + return f.HasBeenSet +} + +// Names returns the names of the flag +func (f *Uint64Flag) Names() []string { + return FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether or not the flag is required +func (f *Uint64Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false +func (f *Uint64Flag) IsVisible() bool { + return !f.Hidden +} + +// vim:ro diff --git a/vendor/github.com/xrash/smetrics/.travis.yml b/vendor/github.com/xrash/smetrics/.travis.yml new file mode 100644 index 000000000..d1cd67ff9 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.11 + - 1.12 + - 1.13 + - 1.14.x + - master +script: + - cd tests && make diff --git a/vendor/github.com/xrash/smetrics/LICENSE b/vendor/github.com/xrash/smetrics/LICENSE new file mode 100644 index 000000000..80445682f --- /dev/null +++ b/vendor/github.com/xrash/smetrics/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2016 Felipe da Cunha Gonçalves +All Rights Reserved. + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/xrash/smetrics/README.md b/vendor/github.com/xrash/smetrics/README.md new file mode 100644 index 000000000..5e0c1a463 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/README.md @@ -0,0 +1,49 @@ +[![Build Status](https://travis-ci.org/xrash/smetrics.svg?branch=master)](http://travis-ci.org/xrash/smetrics) + +# smetrics + +`smetrics` is "string metrics". + +Package smetrics provides a bunch of algorithms for calculating the distance between strings. + +There are implementations for calculating the popular Levenshtein distance (aka Edit Distance or Wagner-Fischer), as well as the Jaro distance, the Jaro-Winkler distance, and more. + +# How to import + +```go +import "github.com/xrash/smetrics" +``` + +# Documentation + +Go to [https://pkg.go.dev/github.com/xrash/smetrics](https://pkg.go.dev/github.com/xrash/smetrics) for complete documentation. + +# Example + +```go +package main + +import ( + "github.com/xrash/smetrics" +) + +func main() { + smetrics.WagnerFischer("POTATO", "POTATTO", 1, 1, 2) + smetrics.WagnerFischer("MOUSE", "HOUSE", 2, 2, 4) + + smetrics.Ukkonen("POTATO", "POTATTO", 1, 1, 2) + smetrics.Ukkonen("MOUSE", "HOUSE", 2, 2, 4) + + smetrics.Jaro("AL", "AL") + smetrics.Jaro("MARTHA", "MARHTA") + + smetrics.JaroWinkler("AL", "AL", 0.7, 4) + smetrics.JaroWinkler("MARTHA", "MARHTA", 0.7, 4) + + smetrics.Soundex("Euler") + smetrics.Soundex("Ellery") + + smetrics.Hamming("aaa", "aaa") + smetrics.Hamming("aaa", "aab") +} +``` diff --git a/vendor/github.com/xrash/smetrics/doc.go b/vendor/github.com/xrash/smetrics/doc.go new file mode 100644 index 000000000..21bc986c9 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/doc.go @@ -0,0 +1,19 @@ +/* +Package smetrics provides a bunch of algorithms for calculating +the distance between strings. + +There are implementations for calculating the popular Levenshtein +distance (aka Edit Distance or Wagner-Fischer), as well as the Jaro +distance, the Jaro-Winkler distance, and more. + +For the Levenshtein distance, you can use the functions WagnerFischer() +and Ukkonen(). Read the documentation on these functions. + +For the Jaro and Jaro-Winkler algorithms, check the functions +Jaro() and JaroWinkler(). Read the documentation on these functions. + +For the Soundex algorithm, check the function Soundex(). + +For the Hamming distance algorithm, check the function Hamming(). +*/ +package smetrics diff --git a/vendor/github.com/xrash/smetrics/hamming.go b/vendor/github.com/xrash/smetrics/hamming.go new file mode 100644 index 000000000..505d3e5da --- /dev/null +++ b/vendor/github.com/xrash/smetrics/hamming.go @@ -0,0 +1,25 @@ +package smetrics + +import ( + "fmt" +) + +// The Hamming distance is the minimum number of substitutions required to change string A into string B. Both strings must have the same size. If the strings have different sizes, the function returns an error. +func Hamming(a, b string) (int, error) { + al := len(a) + bl := len(b) + + if al != bl { + return -1, fmt.Errorf("strings are not equal (len(a)=%d, len(b)=%d)", al, bl) + } + + var difference = 0 + + for i := range a { + if a[i] != b[i] { + difference = difference + 1 + } + } + + return difference, nil +} diff --git a/vendor/github.com/xrash/smetrics/jaro-winkler.go b/vendor/github.com/xrash/smetrics/jaro-winkler.go new file mode 100644 index 000000000..abdb28883 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/jaro-winkler.go @@ -0,0 +1,28 @@ +package smetrics + +import ( + "math" +) + +// The Jaro-Winkler distance. The result is 1 for equal strings, and 0 for completely different strings. It is commonly used on Record Linkage stuff, thus it tries to be accurate for common typos when writing real names such as person names and street names. +// Jaro-Winkler is a modification of the Jaro algorithm. It works by first running Jaro, then boosting the score of exact matches at the beginning of the strings. Because of that, it introduces two more parameters: the boostThreshold and the prefixSize. These are commonly set to 0.7 and 4, respectively. +func JaroWinkler(a, b string, boostThreshold float64, prefixSize int) float64 { + j := Jaro(a, b) + + if j <= boostThreshold { + return j + } + + prefixSize = int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b))))) + + var prefixMatch float64 + for i := 0; i < prefixSize; i++ { + if a[i] == b[i] { + prefixMatch++ + } else { + break + } + } + + return j + 0.1*prefixMatch*(1.0-j) +} diff --git a/vendor/github.com/xrash/smetrics/jaro.go b/vendor/github.com/xrash/smetrics/jaro.go new file mode 100644 index 000000000..75f924e11 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/jaro.go @@ -0,0 +1,86 @@ +package smetrics + +import ( + "math" +) + +// The Jaro distance. The result is 1 for equal strings, and 0 for completely different strings. +func Jaro(a, b string) float64 { + // If both strings are zero-length, they are completely equal, + // therefore return 1. + if len(a) == 0 && len(b) == 0 { + return 1 + } + + // If one string is zero-length, strings are completely different, + // therefore return 0. + if len(a) == 0 || len(b) == 0 { + return 0 + } + + // Define the necessary variables for the algorithm. + la := float64(len(a)) + lb := float64(len(b)) + matchRange := int(math.Max(0, math.Floor(math.Max(la, lb)/2.0)-1)) + matchesA := make([]bool, len(a)) + matchesB := make([]bool, len(b)) + var matches float64 = 0 + + // Step 1: Matches + // Loop through each character of the first string, + // looking for a matching character in the second string. + for i := 0; i < len(a); i++ { + start := int(math.Max(0, float64(i-matchRange))) + end := int(math.Min(lb-1, float64(i+matchRange))) + + for j := start; j <= end; j++ { + if matchesB[j] { + continue + } + + if a[i] == b[j] { + matchesA[i] = true + matchesB[j] = true + matches++ + break + } + } + } + + // If there are no matches, strings are completely different, + // therefore return 0. + if matches == 0 { + return 0 + } + + // Step 2: Transpositions + // Loop through the matches' arrays, looking for + // unaligned matches. Count the number of unaligned matches. + unaligned := 0 + j := 0 + for i := 0; i < len(a); i++ { + if !matchesA[i] { + continue + } + + for !matchesB[j] { + j++ + } + + if a[i] != b[j] { + unaligned++ + } + + j++ + } + + // The number of unaligned matches divided by two, is the number of _transpositions_. + transpositions := math.Floor(float64(unaligned / 2)) + + // Jaro distance is the average between these three numbers: + // 1. matches / length of string A + // 2. matches / length of string B + // 3. (matches - transpositions/matches) + // So, all that divided by three is the final result. + return ((matches / la) + (matches / lb) + ((matches - transpositions) / matches)) / 3.0 +} diff --git a/vendor/github.com/xrash/smetrics/soundex.go b/vendor/github.com/xrash/smetrics/soundex.go new file mode 100644 index 000000000..a2ad034d5 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/soundex.go @@ -0,0 +1,41 @@ +package smetrics + +import ( + "strings" +) + +// The Soundex encoding. It is a phonetic algorithm that considers how the words sound in English. Soundex maps a string to a 4-byte code consisting of the first letter of the original string and three numbers. Strings that sound similar should map to the same code. +func Soundex(s string) string { + m := map[byte]string{ + 'B': "1", 'P': "1", 'F': "1", 'V': "1", + 'C': "2", 'S': "2", 'K': "2", 'G': "2", 'J': "2", 'Q': "2", 'X': "2", 'Z': "2", + 'D': "3", 'T': "3", + 'L': "4", + 'M': "5", 'N': "5", + 'R': "6", + } + + s = strings.ToUpper(s) + + r := string(s[0]) + p := s[0] + for i := 1; i < len(s) && len(r) < 4; i++ { + c := s[i] + + if (c < 'A' || c > 'Z') || (c == p) { + continue + } + + p = c + + if n, ok := m[c]; ok { + r += n + } + } + + for i := len(r); i < 4; i++ { + r += "0" + } + + return r +} diff --git a/vendor/github.com/xrash/smetrics/ukkonen.go b/vendor/github.com/xrash/smetrics/ukkonen.go new file mode 100644 index 000000000..3c5579cd9 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/ukkonen.go @@ -0,0 +1,94 @@ +package smetrics + +import ( + "math" +) + +// The Ukkonen algorithm for calculating the Levenshtein distance. The algorithm is described in http://www.cs.helsinki.fi/u/ukkonen/InfCont85.PDF, or in docs/InfCont85.PDF. It runs on O(t . min(m, n)) where t is the actual distance between strings a and b. It needs O(min(t, m, n)) space. This function might be preferred over WagnerFischer() for *very* similar strings. But test it out yourself. +// The first two parameters are the two strings to be compared. The last three parameters are the insertion cost, the deletion cost and the substitution cost. These are normally defined as 1, 1 and 2 respectively. +func Ukkonen(a, b string, icost, dcost, scost int) int { + var lowerCost int + + if icost < dcost && icost < scost { + lowerCost = icost + } else if dcost < scost { + lowerCost = dcost + } else { + lowerCost = scost + } + + infinite := math.MaxInt32 / 2 + + var r []int + var k, kprime, p, t int + var ins, del, sub int + + if len(a) > len(b) { + t = (len(a) - len(b) + 1) * lowerCost + } else { + t = (len(b) - len(a) + 1) * lowerCost + } + + for { + if (t / lowerCost) < (len(b) - len(a)) { + continue + } + + // This is the right damn thing since the original Ukkonen + // paper minimizes the expression result only, but the uncommented version + // doesn't need to deal with floats so it's faster. + // p = int(math.Floor(0.5*((float64(t)/float64(lowerCost)) - float64(len(b) - len(a))))) + p = ((t / lowerCost) - (len(b) - len(a))) / 2 + + k = -p + kprime = k + + rowlength := (len(b) - len(a)) + (2 * p) + + r = make([]int, rowlength+2) + + for i := 0; i < rowlength+2; i++ { + r[i] = infinite + } + + for i := 0; i <= len(a); i++ { + for j := 0; j <= rowlength; j++ { + if i == j+k && i == 0 { + r[j] = 0 + } else { + if j-1 < 0 { + ins = infinite + } else { + ins = r[j-1] + icost + } + + del = r[j+1] + dcost + sub = r[j] + scost + + if i-1 < 0 || i-1 >= len(a) || j+k-1 >= len(b) || j+k-1 < 0 { + sub = infinite + } else if a[i-1] == b[j+k-1] { + sub = r[j] + } + + if ins < del && ins < sub { + r[j] = ins + } else if del < sub { + r[j] = del + } else { + r[j] = sub + } + } + } + k++ + } + + if r[(len(b)-len(a))+(2*p)+kprime] <= t { + break + } else { + t *= 2 + } + } + + return r[(len(b)-len(a))+(2*p)+kprime] +} diff --git a/vendor/github.com/xrash/smetrics/wagner-fischer.go b/vendor/github.com/xrash/smetrics/wagner-fischer.go new file mode 100644 index 000000000..9883aea04 --- /dev/null +++ b/vendor/github.com/xrash/smetrics/wagner-fischer.go @@ -0,0 +1,48 @@ +package smetrics + +// The Wagner-Fischer algorithm for calculating the Levenshtein distance. +// The first two parameters are the two strings to be compared. The last three parameters are the insertion cost, the deletion cost and the substitution cost. These are normally defined as 1, 1 and 2 respectively. +func WagnerFischer(a, b string, icost, dcost, scost int) int { + + // Allocate both rows. + row1 := make([]int, len(b)+1) + row2 := make([]int, len(b)+1) + var tmp []int + + // Initialize the first row. + for i := 1; i <= len(b); i++ { + row1[i] = i * icost + } + + // For each row... + for i := 1; i <= len(a); i++ { + row2[0] = i * dcost + + // For each column... + for j := 1; j <= len(b); j++ { + if a[i-1] == b[j-1] { + row2[j] = row1[j-1] + } else { + ins := row2[j-1] + icost + del := row1[j] + dcost + sub := row1[j-1] + scost + + if ins < del && ins < sub { + row2[j] = ins + } else if del < sub { + row2[j] = del + } else { + row2[j] = sub + } + } + } + + // Swap the rows at the end of each row. + tmp = row1 + row1 = row2 + row2 = tmp + } + + // Because we swapped the rows, the final result is in row1 instead of row2. + return row1[len(row1)-1] +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 375fbc7dd..33299bd24 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -53,8 +53,8 @@ github.com/aws/aws-sdk-go/service/sso github.com/aws/aws-sdk-go/service/sso/ssoiface github.com/aws/aws-sdk-go/service/sts github.com/aws/aws-sdk-go/service/sts/stsiface -# github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d -## explicit; go 1.12 +# github.com/cpuguy83/go-md2man/v2 v2.0.2 +## explicit; go 1.11 github.com/cpuguy83/go-md2man/v2/md2man # github.com/davecgh/go-spew v1.1.1 ## explicit @@ -105,7 +105,7 @@ github.com/pmezard/go-difflib/difflib github.com/posener/complete github.com/posener/complete/cmd github.com/posener/complete/cmd/install -# github.com/russross/blackfriday/v2 v2.0.1 +# github.com/russross/blackfriday/v2 v2.1.0 ## explicit github.com/russross/blackfriday/v2 # github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 @@ -114,9 +114,6 @@ github.com/ryszard/goskiplist/skiplist # github.com/shabbyrobe/gocovmerge v0.0.0-20180507124511-f6ea450bfb63 ## explicit github.com/shabbyrobe/gocovmerge -# github.com/shurcooL/sanitized_anchor_name v1.0.0 -## explicit -github.com/shurcooL/sanitized_anchor_name # github.com/stretchr/objx v0.1.0 ## explicit github.com/stretchr/objx @@ -127,9 +124,12 @@ github.com/stretchr/testify/mock # github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae ## explicit github.com/termie/go-shutil -# github.com/urfave/cli/v2 v2.2.0 -## explicit; go 1.11 +# github.com/urfave/cli/v2 v2.11.1 +## explicit; go 1.18 github.com/urfave/cli/v2 +# github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 +## explicit +github.com/xrash/smetrics # go.etcd.io/bbolt v1.3.6 ## explicit; go 1.12 go.etcd.io/bbolt From ea557fd0cb8aee15e7648c6cb849ebbcf306b79f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 12 Aug 2022 17:12:56 +0300 Subject: [PATCH 02/34] remove posener/complete package --- command/app.go | 27 +-- command/autocomplete.go | 42 ----- go.mod | 1 - go.sum | 2 - vendor/github.com/posener/complete/.gitignore | 4 - .../github.com/posener/complete/.travis.yml | 16 -- .../github.com/posener/complete/LICENSE.txt | 21 --- vendor/github.com/posener/complete/README.md | 131 ------------- vendor/github.com/posener/complete/args.go | 114 ------------ vendor/github.com/posener/complete/cmd/cmd.go | 128 ------------- .../posener/complete/cmd/install/bash.go | 37 ---- .../posener/complete/cmd/install/fish.go | 69 ------- .../posener/complete/cmd/install/install.go | 148 --------------- .../posener/complete/cmd/install/utils.go | 140 -------------- .../posener/complete/cmd/install/zsh.go | 44 ----- vendor/github.com/posener/complete/command.go | 111 ----------- .../github.com/posener/complete/complete.go | 104 ----------- vendor/github.com/posener/complete/doc.go | 110 ----------- .../github.com/posener/complete/goreadme.json | 9 - vendor/github.com/posener/complete/log.go | 22 --- vendor/github.com/posener/complete/predict.go | 41 ----- .../posener/complete/predict_files.go | 174 ------------------ .../posener/complete/predict_set.go | 12 -- vendor/modules.txt | 5 - 24 files changed, 5 insertions(+), 1507 deletions(-) delete mode 100644 command/autocomplete.go delete mode 100644 vendor/github.com/posener/complete/.gitignore delete mode 100644 vendor/github.com/posener/complete/.travis.yml delete mode 100644 vendor/github.com/posener/complete/LICENSE.txt delete mode 100644 vendor/github.com/posener/complete/README.md delete mode 100644 vendor/github.com/posener/complete/args.go delete mode 100644 vendor/github.com/posener/complete/cmd/cmd.go delete mode 100644 vendor/github.com/posener/complete/cmd/install/bash.go delete mode 100644 vendor/github.com/posener/complete/cmd/install/fish.go delete mode 100644 vendor/github.com/posener/complete/cmd/install/install.go delete mode 100644 vendor/github.com/posener/complete/cmd/install/utils.go delete mode 100644 vendor/github.com/posener/complete/cmd/install/zsh.go delete mode 100644 vendor/github.com/posener/complete/command.go delete mode 100644 vendor/github.com/posener/complete/complete.go delete mode 100644 vendor/github.com/posener/complete/doc.go delete mode 100644 vendor/github.com/posener/complete/goreadme.json delete mode 100644 vendor/github.com/posener/complete/log.go delete mode 100644 vendor/github.com/posener/complete/predict.go delete mode 100644 vendor/github.com/posener/complete/predict_files.go delete mode 100644 vendor/github.com/posener/complete/predict_set.go diff --git a/command/app.go b/command/app.go index c0a927e5f..cb9cc2c5c 100644 --- a/command/app.go +++ b/command/app.go @@ -4,8 +4,8 @@ import ( "context" "fmt" "os" + "path/filepath" - cmpinstall "github.com/posener/complete/cmd/install" "github.com/urfave/cli/v2" "github.com/peak/s5cmd/log" @@ -62,10 +62,6 @@ var app = &cli.App{ Name: "install-completion", Usage: "install completion for your shell (only avialble for bash, zsh, and fish)", }, - &cli.BoolFlag{ - Name: "uninstall-completion", - Usage: "uninstall completion from your shell", - }, &cli.BoolFlag{ Name: "dry-run", Usage: "fake run; show what commands will be executed without actually executing them", @@ -149,20 +145,11 @@ var app = &cli.App{ }, Action: func(c *cli.Context) error { if c.Bool("install-completion") { - if cmpinstall.IsInstalled(appName) { - return nil - } - - return cmpinstall.Install(appName) - } - if c.Bool("unsinstall-completion") { - if !cmpinstall.IsInstalled(appName) { - return nil - } - - return cmpinstall.Uninstall(appName) + shell := os.Getenv("SHELL") + // todo printout the script + fmt.Println("You should add following script to ~/." + filepath.Base(shell) + "rc\nTODO") + return nil } - args := c.Args() if args.Present() { cli.ShowCommandHelp(c, args.First()) @@ -230,9 +217,5 @@ func AppCommand(name string) *cli.Command { func Main(ctx context.Context, args []string) error { app.Commands = Commands() - if maybeAutoComplete() { - return nil - } - return app.RunContext(ctx, args) } diff --git a/command/autocomplete.go b/command/autocomplete.go deleted file mode 100644 index ab3e2659e..000000000 --- a/command/autocomplete.go +++ /dev/null @@ -1,42 +0,0 @@ -package command - -import ( - "github.com/posener/complete" - "github.com/urfave/cli/v2" -) - -func adaptCommand(cmd *cli.Command) complete.Command { - return complete.Command{ - Flags: adaptFlags(cmd.Flags), - // TODO(ig): add args predictors - } -} - -func adaptFlags(flags []cli.Flag) complete.Flags { - completionFlags := make(complete.Flags) - - for _, flag := range flags { - for _, flagname := range flag.Names() { - if len(flagname) == 1 { - flagname = "-" + flagname - } else { - flagname = "--" + flagname - } - completionFlags[flagname] = complete.PredictNothing - } - } - return completionFlags -} - -func maybeAutoComplete() bool { - cmpCommands := make(complete.Commands) - for _, cmd := range app.Commands { - cmpCommands[cmd.Name] = adaptCommand(cmd) - } - - completionCmd := complete.Command{ - Flags: adaptFlags(app.Flags), - Sub: cmpCommands, - } - return complete.New(appName, completionCmd).Complete() -} diff --git a/go.mod b/go.mod index c5da25cc4..018881e05 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,6 @@ require ( github.com/igungor/gofakes3 v0.0.11 github.com/karrick/godirwalk v1.15.3 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/posener/complete v1.2.3 github.com/stretchr/testify v1.4.0 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae github.com/urfave/cli/v2 v2.11.1 diff --git a/go.sum b/go.sum index 0857e477c..02de7808b 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= diff --git a/vendor/github.com/posener/complete/.gitignore b/vendor/github.com/posener/complete/.gitignore deleted file mode 100644 index 293955f99..000000000 --- a/vendor/github.com/posener/complete/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.idea -coverage.txt -gocomplete/gocomplete -example/self/self diff --git a/vendor/github.com/posener/complete/.travis.yml b/vendor/github.com/posener/complete/.travis.yml deleted file mode 100644 index 6ba8d865b..000000000 --- a/vendor/github.com/posener/complete/.travis.yml +++ /dev/null @@ -1,16 +0,0 @@ -language: go -go: - - tip - - 1.12.x - - 1.11.x - - 1.10.x - -script: - - go test -race -coverprofile=coverage.txt -covermode=atomic ./... - -after_success: - - bash <(curl -s https://codecov.io/bash) - -matrix: - allow_failures: - - go: tip \ No newline at end of file diff --git a/vendor/github.com/posener/complete/LICENSE.txt b/vendor/github.com/posener/complete/LICENSE.txt deleted file mode 100644 index 16249b4a1..000000000 --- a/vendor/github.com/posener/complete/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License - -Copyright (c) 2017 Eyal Posener - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/posener/complete/README.md b/vendor/github.com/posener/complete/README.md deleted file mode 100644 index dcc6c8932..000000000 --- a/vendor/github.com/posener/complete/README.md +++ /dev/null @@ -1,131 +0,0 @@ -# complete - -[![Build Status](https://travis-ci.org/posener/complete.svg?branch=master)](https://travis-ci.org/posener/complete) -[![codecov](https://codecov.io/gh/posener/complete/branch/master/graph/badge.svg)](https://codecov.io/gh/posener/complete) -[![golangci](https://golangci.com/badges/github.com/posener/complete.svg)](https://golangci.com/r/github.com/posener/complete) -[![GoDoc](https://godoc.org/github.com/posener/complete?status.svg)](http://godoc.org/github.com/posener/complete) -[![goreadme](https://goreadme.herokuapp.com/badge/posener/complete.svg)](https://goreadme.herokuapp.com) - -Package complete provides a tool for bash writing bash completion in go, and bash completion for the go command line. - -Writing bash completion scripts is a hard work. This package provides an easy way -to create bash completion scripts for any command, and also an easy way to install/uninstall -the completion of the command. - -#### Go Command Bash Completion - -In [./cmd/gocomplete](./cmd/gocomplete) there is an example for bash completion for the `go` command line. - -This is an example that uses the `complete` package on the `go` command - the `complete` package -can also be used to implement any completions, see #usage. - -#### Install - -1. Type in your shell: - -```go -go get -u github.com/posener/complete/gocomplete -gocomplete -install -``` - -2. Restart your shell - -Uninstall by `gocomplete -uninstall` - -#### Features - -- Complete `go` command, including sub commands and all flags. -- Complete packages names or `.go` files when necessary. -- Complete test names after `-run` flag. - -#### Complete package - -Supported shells: - -- [x] bash -- [x] zsh -- [x] fish - -#### Usage - -Assuming you have program called `run` and you want to have bash completion -for it, meaning, if you type `run` then space, then press the `Tab` key, -the shell will suggest relevant complete options. - -In that case, we will create a program called `runcomplete`, a go program, -with a `func main()` and so, that will make the completion of the `run` -program. Once the `runcomplete` will be in a binary form, we could -`runcomplete -install` and that will add to our shell all the bash completion -options for `run`. - -So here it is: - -```go -import "github.com/posener/complete" - -func main() { - - // create a Command object, that represents the command we want - // to complete. - run := complete.Command{ - - // Sub defines a list of sub commands of the program, - // this is recursive, since every command is of type command also. - Sub: complete.Commands{ - - // add a build sub command - "build": complete.Command { - - // define flags of the build sub command - Flags: complete.Flags{ - // build sub command has a flag '-cpus', which - // expects number of cpus after it. in that case - // anything could complete this flag. - "-cpus": complete.PredictAnything, - }, - }, - }, - - // define flags of the 'run' main command - Flags: complete.Flags{ - // a flag -o, which expects a file ending with .out after - // it, the tab completion will auto complete for files matching - // the given pattern. - "-o": complete.PredictFiles("*.out"), - }, - - // define global flags of the 'run' main command - // those will show up also when a sub command was entered in the - // command line - GlobalFlags: complete.Flags{ - - // a flag '-h' which does not expects anything after it - "-h": complete.PredictNothing, - }, - } - - // run the command completion, as part of the main() function. - // this triggers the autocompletion when needed. - // name must be exactly as the binary that we want to complete. - complete.New("run", run).Run() -} -``` - -#### Self completing program - -In case that the program that we want to complete is written in go we -can make it self completing. -Here is an example: [./example/self/main.go](./example/self/main.go) . - -## Sub Packages - -* [cmd](./cmd): Package cmd used for command line options for the complete tool - -* [gocomplete](./gocomplete): Package main is complete tool for the go command line - -* [match](./match): Package match contains matchers that decide if to apply completion. - - ---- - -Created by [goreadme](https://github.com/apps/goreadme) diff --git a/vendor/github.com/posener/complete/args.go b/vendor/github.com/posener/complete/args.go deleted file mode 100644 index 3340285e1..000000000 --- a/vendor/github.com/posener/complete/args.go +++ /dev/null @@ -1,114 +0,0 @@ -package complete - -import ( - "os" - "path/filepath" - "strings" - "unicode" -) - -// Args describes command line arguments -type Args struct { - // All lists of all arguments in command line (not including the command itself) - All []string - // Completed lists of all completed arguments in command line, - // If the last one is still being typed - no space after it, - // it won't appear in this list of arguments. - Completed []string - // Last argument in command line, the one being typed, if the last - // character in the command line is a space, this argument will be empty, - // otherwise this would be the last word. - Last string - // LastCompleted is the last argument that was fully typed. - // If the last character in the command line is space, this would be the - // last word, otherwise, it would be the word before that. - LastCompleted string -} - -// Directory gives the directory of the current written -// last argument if it represents a file name being written. -// in case that it is not, we fall back to the current directory. -// -// Deprecated. -func (a Args) Directory() string { - if info, err := os.Stat(a.Last); err == nil && info.IsDir() { - return fixPathForm(a.Last, a.Last) - } - dir := filepath.Dir(a.Last) - if info, err := os.Stat(dir); err != nil || !info.IsDir() { - return "./" - } - return fixPathForm(a.Last, dir) -} - -func newArgs(line string) Args { - var ( - all []string - completed []string - ) - parts := splitFields(line) - if len(parts) > 0 { - all = parts[1:] - completed = removeLast(parts[1:]) - } - return Args{ - All: all, - Completed: completed, - Last: last(parts), - LastCompleted: last(completed), - } -} - -// splitFields returns a list of fields from the given command line. -// If the last character is space, it appends an empty field in the end -// indicating that the field before it was completed. -// If the last field is of the form "a=b", it splits it to two fields: "a", "b", -// So it can be completed. -func splitFields(line string) []string { - parts := strings.Fields(line) - - // Add empty field if the last field was completed. - if len(line) > 0 && unicode.IsSpace(rune(line[len(line)-1])) { - parts = append(parts, "") - } - - // Treat the last field if it is of the form "a=b" - parts = splitLastEqual(parts) - return parts -} - -func splitLastEqual(line []string) []string { - if len(line) == 0 { - return line - } - parts := strings.Split(line[len(line)-1], "=") - return append(line[:len(line)-1], parts...) -} - -// from returns a copy of Args of all arguments after the i'th argument. -func (a Args) from(i int) Args { - if i >= len(a.All) { - i = len(a.All) - 1 - } - a.All = a.All[i+1:] - - if i >= len(a.Completed) { - i = len(a.Completed) - 1 - } - a.Completed = a.Completed[i+1:] - return a -} - -func removeLast(a []string) []string { - if len(a) > 0 { - return a[:len(a)-1] - } - return a -} - -func last(args []string) string { - if len(args) == 0 { - return "" - } - return args[len(args)-1] -} diff --git a/vendor/github.com/posener/complete/cmd/cmd.go b/vendor/github.com/posener/complete/cmd/cmd.go deleted file mode 100644 index b99fe5290..000000000 --- a/vendor/github.com/posener/complete/cmd/cmd.go +++ /dev/null @@ -1,128 +0,0 @@ -// Package cmd used for command line options for the complete tool -package cmd - -import ( - "errors" - "flag" - "fmt" - "os" - "strings" - - "github.com/posener/complete/cmd/install" -) - -// CLI for command line -type CLI struct { - Name string - InstallName string - UninstallName string - - install bool - uninstall bool - yes bool -} - -const ( - defaultInstallName = "install" - defaultUninstallName = "uninstall" -) - -// Run is used when running complete in command line mode. -// this is used when the complete is not completing words, but to -// install it or uninstall it. -func (f *CLI) Run() bool { - err := f.validate() - if err != nil { - os.Stderr.WriteString(err.Error() + "\n") - os.Exit(1) - } - - switch { - case f.install: - f.prompt() - err = install.Install(f.Name) - case f.uninstall: - f.prompt() - err = install.Uninstall(f.Name) - default: - // non of the action flags matched, - // returning false should make the real program execute - return false - } - - if err != nil { - fmt.Printf("%s failed! %s\n", f.action(), err) - os.Exit(3) - } - fmt.Println("Done!") - return true -} - -// prompt use for approval -// exit if approval was not given -func (f *CLI) prompt() { - defer fmt.Println(f.action() + "ing...") - if f.yes { - return - } - fmt.Printf("%s completion for %s? ", f.action(), f.Name) - var answer string - fmt.Scanln(&answer) - - switch strings.ToLower(answer) { - case "y", "yes": - return - default: - fmt.Println("Cancelling...") - os.Exit(1) - } -} - -// AddFlags adds the CLI flags to the flag set. -// If flags is nil, the default command line flags will be taken. -// Pass non-empty strings as installName and uninstallName to override the default -// flag names. -func (f *CLI) AddFlags(flags *flag.FlagSet) { - if flags == nil { - flags = flag.CommandLine - } - - if f.InstallName == "" { - f.InstallName = defaultInstallName - } - if f.UninstallName == "" { - f.UninstallName = defaultUninstallName - } - - if flags.Lookup(f.InstallName) == nil { - flags.BoolVar(&f.install, f.InstallName, false, - fmt.Sprintf("Install completion for %s command", f.Name)) - } - if flags.Lookup(f.UninstallName) == nil { - flags.BoolVar(&f.uninstall, f.UninstallName, false, - fmt.Sprintf("Uninstall completion for %s command", f.Name)) - } - if flags.Lookup("y") == nil { - flags.BoolVar(&f.yes, "y", false, "Don't prompt user for typing 'yes' when installing completion") - } -} - -// validate the CLI -func (f *CLI) validate() error { - if f.install && f.uninstall { - return errors.New("Install and uninstall are mutually exclusive") - } - return nil -} - -// action name according to the CLI values. -func (f *CLI) action() string { - switch { - case f.install: - return "Install" - case f.uninstall: - return "Uninstall" - default: - return "unknown" - } -} diff --git a/vendor/github.com/posener/complete/cmd/install/bash.go b/vendor/github.com/posener/complete/cmd/install/bash.go deleted file mode 100644 index 17c64de13..000000000 --- a/vendor/github.com/posener/complete/cmd/install/bash.go +++ /dev/null @@ -1,37 +0,0 @@ -package install - -import "fmt" - -// (un)install in bash -// basically adds/remove from .bashrc: -// -// complete -C -type bash struct { - rc string -} - -func (b bash) IsInstalled(cmd, bin string) bool { - completeCmd := b.cmd(cmd, bin) - return lineInFile(b.rc, completeCmd) -} - -func (b bash) Install(cmd, bin string) error { - if b.IsInstalled(cmd, bin) { - return fmt.Errorf("already installed in %s", b.rc) - } - completeCmd := b.cmd(cmd, bin) - return appendToFile(b.rc, completeCmd) -} - -func (b bash) Uninstall(cmd, bin string) error { - if !b.IsInstalled(cmd, bin) { - return fmt.Errorf("does not installed in %s", b.rc) - } - - completeCmd := b.cmd(cmd, bin) - return removeFromFile(b.rc, completeCmd) -} - -func (bash) cmd(cmd, bin string) string { - return fmt.Sprintf("complete -C %s %s", bin, cmd) -} diff --git a/vendor/github.com/posener/complete/cmd/install/fish.go b/vendor/github.com/posener/complete/cmd/install/fish.go deleted file mode 100644 index 2b64bfc83..000000000 --- a/vendor/github.com/posener/complete/cmd/install/fish.go +++ /dev/null @@ -1,69 +0,0 @@ -package install - -import ( - "bytes" - "fmt" - "os" - "path/filepath" - "text/template" -) - -// (un)install in fish - -type fish struct { - configDir string -} - -func (f fish) IsInstalled(cmd, bin string) bool { - completionFile := f.getCompletionFilePath(cmd) - if _, err := os.Stat(completionFile); err == nil { - return true - } - return false -} - -func (f fish) Install(cmd, bin string) error { - if f.IsInstalled(cmd, bin) { - return fmt.Errorf("already installed at %s", f.getCompletionFilePath(cmd)) - } - - completionFile := f.getCompletionFilePath(cmd) - completeCmd, err := f.cmd(cmd, bin) - if err != nil { - return err - } - - return createFile(completionFile, completeCmd) -} - -func (f fish) Uninstall(cmd, bin string) error { - if !f.IsInstalled(cmd, bin) { - return fmt.Errorf("does not installed in %s", f.configDir) - } - - completionFile := f.getCompletionFilePath(cmd) - return os.Remove(completionFile) -} - -func (f fish) getCompletionFilePath(cmd string) string { - return filepath.Join(f.configDir, "completions", fmt.Sprintf("%s.fish", cmd)) -} - -func (f fish) cmd(cmd, bin string) (string, error) { - var buf bytes.Buffer - params := struct{ Cmd, Bin string }{cmd, bin} - tmpl := template.Must(template.New("cmd").Parse(` -function __complete_{{.Cmd}} - set -lx COMP_LINE (commandline -cp) - test -z (commandline -ct) - and set COMP_LINE "$COMP_LINE " - {{.Bin}} -end -complete -f -c {{.Cmd}} -a "(__complete_{{.Cmd}})" -`)) - err := tmpl.Execute(&buf, params) - if err != nil { - return "", err - } - return buf.String(), nil -} diff --git a/vendor/github.com/posener/complete/cmd/install/install.go b/vendor/github.com/posener/complete/cmd/install/install.go deleted file mode 100644 index 884c23f5b..000000000 --- a/vendor/github.com/posener/complete/cmd/install/install.go +++ /dev/null @@ -1,148 +0,0 @@ -package install - -import ( - "errors" - "os" - "os/user" - "path/filepath" - "runtime" - - "github.com/hashicorp/go-multierror" -) - -type installer interface { - IsInstalled(cmd, bin string) bool - Install(cmd, bin string) error - Uninstall(cmd, bin string) error -} - -// Install complete command given: -// cmd: is the command name -func Install(cmd string) error { - is := installers() - if len(is) == 0 { - return errors.New("Did not find any shells to install") - } - bin, err := getBinaryPath() - if err != nil { - return err - } - - for _, i := range is { - errI := i.Install(cmd, bin) - if errI != nil { - err = multierror.Append(err, errI) - } - } - - return err -} - -// IsInstalled returns true if the completion -// for the given cmd is installed. -func IsInstalled(cmd string) bool { - bin, err := getBinaryPath() - if err != nil { - return false - } - - for _, i := range installers() { - installed := i.IsInstalled(cmd, bin) - if installed { - return true - } - } - - return false -} - -// Uninstall complete command given: -// cmd: is the command name -func Uninstall(cmd string) error { - is := installers() - if len(is) == 0 { - return errors.New("Did not find any shells to uninstall") - } - bin, err := getBinaryPath() - if err != nil { - return err - } - - for _, i := range is { - errI := i.Uninstall(cmd, bin) - if errI != nil { - err = multierror.Append(err, errI) - } - } - - return err -} - -func installers() (i []installer) { - // The list of bash config files candidates where it is - // possible to install the completion command. - var bashConfFiles []string - switch runtime.GOOS { - case "darwin": - bashConfFiles = []string{".bash_profile"} - default: - bashConfFiles = []string{".bashrc", ".bash_profile", ".bash_login", ".profile"} - } - for _, rc := range bashConfFiles { - if f := rcFile(rc); f != "" { - i = append(i, bash{f}) - break - } - } - if f := rcFile(".zshrc"); f != "" { - i = append(i, zsh{f}) - } - if d := fishConfigDir(); d != "" { - i = append(i, fish{d}) - } - return -} - -func fishConfigDir() string { - configDir := filepath.Join(getConfigHomePath(), "fish") - if configDir == "" { - return "" - } - if info, err := os.Stat(configDir); err != nil || !info.IsDir() { - return "" - } - return configDir -} - -func getConfigHomePath() string { - u, err := user.Current() - if err != nil { - return "" - } - - configHome := os.Getenv("XDG_CONFIG_HOME") - if configHome == "" { - return filepath.Join(u.HomeDir, ".config") - } - return configHome -} - -func getBinaryPath() (string, error) { - bin, err := os.Executable() - if err != nil { - return "", err - } - return filepath.Abs(bin) -} - -func rcFile(name string) string { - u, err := user.Current() - if err != nil { - return "" - } - path := filepath.Join(u.HomeDir, name) - if _, err := os.Stat(path); err != nil { - return "" - } - return path -} diff --git a/vendor/github.com/posener/complete/cmd/install/utils.go b/vendor/github.com/posener/complete/cmd/install/utils.go deleted file mode 100644 index d34ac8cae..000000000 --- a/vendor/github.com/posener/complete/cmd/install/utils.go +++ /dev/null @@ -1,140 +0,0 @@ -package install - -import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" -) - -func lineInFile(name string, lookFor string) bool { - f, err := os.Open(name) - if err != nil { - return false - } - defer f.Close() - r := bufio.NewReader(f) - prefix := []byte{} - for { - line, isPrefix, err := r.ReadLine() - if err == io.EOF { - return false - } - if err != nil { - return false - } - if isPrefix { - prefix = append(prefix, line...) - continue - } - line = append(prefix, line...) - if string(line) == lookFor { - return true - } - prefix = prefix[:0] - } -} - -func createFile(name string, content string) error { - // make sure file directory exists - if err := os.MkdirAll(filepath.Dir(name), 0775); err != nil { - return err - } - - // create the file - f, err := os.Create(name) - if err != nil { - return err - } - defer f.Close() - - // write file content - _, err = f.WriteString(fmt.Sprintf("%s\n", content)) - return err -} - -func appendToFile(name string, content string) error { - f, err := os.OpenFile(name, os.O_RDWR|os.O_APPEND, 0) - if err != nil { - return err - } - defer f.Close() - _, err = f.WriteString(fmt.Sprintf("\n%s\n", content)) - return err -} - -func removeFromFile(name string, content string) error { - backup := name + ".bck" - err := copyFile(name, backup) - if err != nil { - return err - } - temp, err := removeContentToTempFile(name, content) - if err != nil { - return err - } - - err = copyFile(temp, name) - if err != nil { - return err - } - - return os.Remove(backup) -} - -func removeContentToTempFile(name, content string) (string, error) { - rf, err := os.Open(name) - if err != nil { - return "", err - } - defer rf.Close() - wf, err := ioutil.TempFile("/tmp", "complete-") - if err != nil { - return "", err - } - defer wf.Close() - - r := bufio.NewReader(rf) - prefix := []byte{} - for { - line, isPrefix, err := r.ReadLine() - if err == io.EOF { - break - } - if err != nil { - return "", err - } - if isPrefix { - prefix = append(prefix, line...) - continue - } - line = append(prefix, line...) - str := string(line) - if str == content { - continue - } - _, err = wf.WriteString(str + "\n") - if err != nil { - return "", err - } - prefix = prefix[:0] - } - return wf.Name(), nil -} - -func copyFile(src string, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - _, err = io.Copy(out, in) - return err -} diff --git a/vendor/github.com/posener/complete/cmd/install/zsh.go b/vendor/github.com/posener/complete/cmd/install/zsh.go deleted file mode 100644 index 29950ab17..000000000 --- a/vendor/github.com/posener/complete/cmd/install/zsh.go +++ /dev/null @@ -1,44 +0,0 @@ -package install - -import "fmt" - -// (un)install in zsh -// basically adds/remove from .zshrc: -// -// autoload -U +X bashcompinit && bashcompinit" -// complete -C -type zsh struct { - rc string -} - -func (z zsh) IsInstalled(cmd, bin string) bool { - completeCmd := z.cmd(cmd, bin) - return lineInFile(z.rc, completeCmd) -} - -func (z zsh) Install(cmd, bin string) error { - if z.IsInstalled(cmd, bin) { - return fmt.Errorf("already installed in %s", z.rc) - } - - completeCmd := z.cmd(cmd, bin) - bashCompInit := "autoload -U +X bashcompinit && bashcompinit" - if !lineInFile(z.rc, bashCompInit) { - completeCmd = bashCompInit + "\n" + completeCmd - } - - return appendToFile(z.rc, completeCmd) -} - -func (z zsh) Uninstall(cmd, bin string) error { - if !z.IsInstalled(cmd, bin) { - return fmt.Errorf("does not installed in %s", z.rc) - } - - completeCmd := z.cmd(cmd, bin) - return removeFromFile(z.rc, completeCmd) -} - -func (zsh) cmd(cmd, bin string) string { - return fmt.Sprintf("complete -o nospace -C %s %s", bin, cmd) -} diff --git a/vendor/github.com/posener/complete/command.go b/vendor/github.com/posener/complete/command.go deleted file mode 100644 index 82d37d529..000000000 --- a/vendor/github.com/posener/complete/command.go +++ /dev/null @@ -1,111 +0,0 @@ -package complete - -// Command represents a command line -// It holds the data that enables auto completion of command line -// Command can also be a sub command. -type Command struct { - // Sub is map of sub commands of the current command - // The key refer to the sub command name, and the value is it's - // Command descriptive struct. - Sub Commands - - // Flags is a map of flags that the command accepts. - // The key is the flag name, and the value is it's predictions. - Flags Flags - - // GlobalFlags is a map of flags that the command accepts. - // Global flags that can appear also after a sub command. - GlobalFlags Flags - - // Args are extra arguments that the command accepts, those who are - // given without any flag before. - Args Predictor -} - -// Predict returns all possible predictions for args according to the command struct -func (c *Command) Predict(a Args) []string { - options, _ := c.predict(a) - return options -} - -// Commands is the type of Sub member, it maps a command name to a command struct -type Commands map[string]Command - -// Predict completion of sub command names names according to command line arguments -func (c Commands) Predict(a Args) (prediction []string) { - for sub := range c { - prediction = append(prediction, sub) - } - return -} - -// Flags is the type Flags of the Flags member, it maps a flag name to the flag predictions. -type Flags map[string]Predictor - -// Predict completion of flags names according to command line arguments -func (f Flags) Predict(a Args) (prediction []string) { - for flag := range f { - // If the flag starts with a hyphen, we avoid emitting the prediction - // unless the last typed arg contains a hyphen as well. - flagHyphenStart := len(flag) != 0 && flag[0] == '-' - lastHyphenStart := len(a.Last) != 0 && a.Last[0] == '-' - if flagHyphenStart && !lastHyphenStart { - continue - } - prediction = append(prediction, flag) - } - return -} - -// predict options -// only is set to true if no more options are allowed to be returned -// those are in cases of special flag that has specific completion arguments, -// and other flags or sub commands can't come after it. -func (c *Command) predict(a Args) (options []string, only bool) { - - // search sub commands for predictions first - subCommandFound := false - for i, arg := range a.Completed { - if cmd, ok := c.Sub[arg]; ok { - subCommandFound = true - - // recursive call for sub command - options, only = cmd.predict(a.from(i)) - if only { - return - } - - // We matched so stop searching. Continuing to search can accidentally - // match a subcommand with current set of commands, see issue #46. - break - } - } - - // if last completed word is a global flag that we need to complete - if predictor, ok := c.GlobalFlags[a.LastCompleted]; ok && predictor != nil { - Log("Predicting according to global flag %s", a.LastCompleted) - return predictor.Predict(a), true - } - - options = append(options, c.GlobalFlags.Predict(a)...) - - // if a sub command was entered, we won't add the parent command - // completions and we return here. - if subCommandFound { - return - } - - // if last completed word is a command flag that we need to complete - if predictor, ok := c.Flags[a.LastCompleted]; ok && predictor != nil { - Log("Predicting according to flag %s", a.LastCompleted) - return predictor.Predict(a), true - } - - options = append(options, c.Sub.Predict(a)...) - options = append(options, c.Flags.Predict(a)...) - if c.Args != nil { - options = append(options, c.Args.Predict(a)...) - } - - return -} diff --git a/vendor/github.com/posener/complete/complete.go b/vendor/github.com/posener/complete/complete.go deleted file mode 100644 index 423cbec6c..000000000 --- a/vendor/github.com/posener/complete/complete.go +++ /dev/null @@ -1,104 +0,0 @@ -package complete - -import ( - "flag" - "fmt" - "io" - "os" - "strconv" - "strings" - - "github.com/posener/complete/cmd" -) - -const ( - envLine = "COMP_LINE" - envPoint = "COMP_POINT" - envDebug = "COMP_DEBUG" -) - -// Complete structs define completion for a command with CLI options -type Complete struct { - Command Command - cmd.CLI - Out io.Writer -} - -// New creates a new complete command. -// name is the name of command we want to auto complete. -// IMPORTANT: it must be the same name - if the auto complete -// completes the 'go' command, name must be equal to "go". -// command is the struct of the command completion. -func New(name string, command Command) *Complete { - return &Complete{ - Command: command, - CLI: cmd.CLI{Name: name}, - Out: os.Stdout, - } -} - -// Run runs the completion and add installation flags beforehand. -// The flags are added to the main flag CommandLine variable. -func (c *Complete) Run() bool { - c.AddFlags(nil) - flag.Parse() - return c.Complete() -} - -// Complete a command from completion line in environment variable, -// and print out the complete options. -// returns success if the completion ran or if the cli matched -// any of the given flags, false otherwise -// For installation: it assumes that flags were added and parsed before -// it was called. -func (c *Complete) Complete() bool { - line, point, ok := getEnv() - if !ok { - // make sure flags parsed, - // in case they were not added in the main program - return c.CLI.Run() - } - - if point >= 0 && point < len(line) { - line = line[:point] - } - - Log("Completing phrase: %s", line) - a := newArgs(line) - Log("Completing last field: %s", a.Last) - options := c.Command.Predict(a) - Log("Options: %s", options) - - // filter only options that match the last argument - matches := []string{} - for _, option := range options { - if strings.HasPrefix(option, a.Last) { - matches = append(matches, option) - } - } - Log("Matches: %s", matches) - c.output(matches) - return true -} - -func getEnv() (line string, point int, ok bool) { - line = os.Getenv(envLine) - if line == "" { - return - } - point, err := strconv.Atoi(os.Getenv(envPoint)) - if err != nil { - // If failed parsing point for some reason, set it to point - // on the end of the line. - Log("Failed parsing point %s: %v", os.Getenv(envPoint), err) - point = len(line) - } - return line, point, true -} - -func (c *Complete) output(options []string) { - // stdout of program defines the complete options - for _, option := range options { - fmt.Fprintln(c.Out, option) - } -} diff --git a/vendor/github.com/posener/complete/doc.go b/vendor/github.com/posener/complete/doc.go deleted file mode 100644 index 0ae09a1b7..000000000 --- a/vendor/github.com/posener/complete/doc.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Package complete provides a tool for bash writing bash completion in go, and bash completion for the go command line. - -Writing bash completion scripts is a hard work. This package provides an easy way -to create bash completion scripts for any command, and also an easy way to install/uninstall -the completion of the command. - -Go Command Bash Completion - -In ./cmd/gocomplete there is an example for bash completion for the `go` command line. - -This is an example that uses the `complete` package on the `go` command - the `complete` package -can also be used to implement any completions, see #usage. - -Install - -1. Type in your shell: - - go get -u github.com/posener/complete/gocomplete - gocomplete -install - -2. Restart your shell - -Uninstall by `gocomplete -uninstall` - -Features - -- Complete `go` command, including sub commands and all flags. -- Complete packages names or `.go` files when necessary. -- Complete test names after `-run` flag. - -Complete package - -Supported shells: - -- [x] bash -- [x] zsh -- [x] fish - -Usage - -Assuming you have program called `run` and you want to have bash completion -for it, meaning, if you type `run` then space, then press the `Tab` key, -the shell will suggest relevant complete options. - -In that case, we will create a program called `runcomplete`, a go program, -with a `func main()` and so, that will make the completion of the `run` -program. Once the `runcomplete` will be in a binary form, we could -`runcomplete -install` and that will add to our shell all the bash completion -options for `run`. - -So here it is: - - import "github.com/posener/complete" - - func main() { - - // create a Command object, that represents the command we want - // to complete. - run := complete.Command{ - - // Sub defines a list of sub commands of the program, - // this is recursive, since every command is of type command also. - Sub: complete.Commands{ - - // add a build sub command - "build": complete.Command { - - // define flags of the build sub command - Flags: complete.Flags{ - // build sub command has a flag '-cpus', which - // expects number of cpus after it. in that case - // anything could complete this flag. - "-cpus": complete.PredictAnything, - }, - }, - }, - - // define flags of the 'run' main command - Flags: complete.Flags{ - // a flag -o, which expects a file ending with .out after - // it, the tab completion will auto complete for files matching - // the given pattern. - "-o": complete.PredictFiles("*.out"), - }, - - // define global flags of the 'run' main command - // those will show up also when a sub command was entered in the - // command line - GlobalFlags: complete.Flags{ - - // a flag '-h' which does not expects anything after it - "-h": complete.PredictNothing, - }, - } - - // run the command completion, as part of the main() function. - // this triggers the autocompletion when needed. - // name must be exactly as the binary that we want to complete. - complete.New("run", run).Run() - } - -Self completing program - -In case that the program that we want to complete is written in go we -can make it self completing. -Here is an example: ./example/self/main.go . - -*/ -package complete diff --git a/vendor/github.com/posener/complete/goreadme.json b/vendor/github.com/posener/complete/goreadme.json deleted file mode 100644 index 025ec76c9..000000000 --- a/vendor/github.com/posener/complete/goreadme.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "badges": { - "travis_ci": true, - "code_cov": true, - "golang_ci": true, - "go_doc": true, - "goreadme": true - } -} \ No newline at end of file diff --git a/vendor/github.com/posener/complete/log.go b/vendor/github.com/posener/complete/log.go deleted file mode 100644 index c3029556e..000000000 --- a/vendor/github.com/posener/complete/log.go +++ /dev/null @@ -1,22 +0,0 @@ -package complete - -import ( - "io/ioutil" - "log" - "os" -) - -// Log is used for debugging purposes -// since complete is running on tab completion, it is nice to -// have logs to the stderr (when writing your own completer) -// to write logs, set the COMP_DEBUG environment variable and -// use complete.Log in the complete program -var Log = getLogger() - -func getLogger() func(format string, args ...interface{}) { - var logfile = ioutil.Discard - if os.Getenv(envDebug) != "" { - logfile = os.Stderr - } - return log.New(logfile, "complete ", log.Flags()).Printf -} diff --git a/vendor/github.com/posener/complete/predict.go b/vendor/github.com/posener/complete/predict.go deleted file mode 100644 index 820706325..000000000 --- a/vendor/github.com/posener/complete/predict.go +++ /dev/null @@ -1,41 +0,0 @@ -package complete - -// Predictor implements a predict method, in which given -// command line arguments returns a list of options it predicts. -type Predictor interface { - Predict(Args) []string -} - -// PredictOr unions two predicate functions, so that the result predicate -// returns the union of their predication -func PredictOr(predictors ...Predictor) Predictor { - return PredictFunc(func(a Args) (prediction []string) { - for _, p := range predictors { - if p == nil { - continue - } - prediction = append(prediction, p.Predict(a)...) - } - return - }) -} - -// PredictFunc determines what terms can follow a command or a flag -// It is used for auto completion, given last - the last word in the already -// in the command line, what words can complete it. -type PredictFunc func(Args) []string - -// Predict invokes the predict function and implements the Predictor interface -func (p PredictFunc) Predict(a Args) []string { - if p == nil { - return nil - } - return p(a) -} - -// PredictNothing does not expect anything after. -var PredictNothing Predictor - -// PredictAnything expects something, but nothing particular, such as a number -// or arbitrary name. -var PredictAnything = PredictFunc(func(Args) []string { return nil }) diff --git a/vendor/github.com/posener/complete/predict_files.go b/vendor/github.com/posener/complete/predict_files.go deleted file mode 100644 index 25ae2d514..000000000 --- a/vendor/github.com/posener/complete/predict_files.go +++ /dev/null @@ -1,174 +0,0 @@ -package complete - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" -) - -// PredictDirs will search for directories in the given started to be typed -// path, if no path was started to be typed, it will complete to directories -// in the current working directory. -func PredictDirs(pattern string) Predictor { - return files(pattern, false) -} - -// PredictFiles will search for files matching the given pattern in the started to -// be typed path, if no path was started to be typed, it will complete to files that -// match the pattern in the current working directory. -// To match any file, use "*" as pattern. To match go files use "*.go", and so on. -func PredictFiles(pattern string) Predictor { - return files(pattern, true) -} - -func files(pattern string, allowFiles bool) PredictFunc { - - // search for files according to arguments, - // if only one directory has matched the result, search recursively into - // this directory to give more results. - return func(a Args) (prediction []string) { - prediction = predictFiles(a, pattern, allowFiles) - - // if the number of prediction is not 1, we either have many results or - // have no results, so we return it. - if len(prediction) != 1 { - return - } - - // only try deeper, if the one item is a directory - if stat, err := os.Stat(prediction[0]); err != nil || !stat.IsDir() { - return - } - - a.Last = prediction[0] - return predictFiles(a, pattern, allowFiles) - } -} - -func predictFiles(a Args, pattern string, allowFiles bool) []string { - if strings.HasSuffix(a.Last, "/..") { - return nil - } - - dir := directory(a.Last) - files := listFiles(dir, pattern, allowFiles) - - // add dir if match - files = append(files, dir) - - return PredictFilesSet(files).Predict(a) -} - -// directory gives the directory of the given partial path -// in case that it is not, we fall back to the current directory. -func directory(path string) string { - if info, err := os.Stat(path); err == nil && info.IsDir() { - return fixPathForm(path, path) - } - dir := filepath.Dir(path) - if info, err := os.Stat(dir); err == nil && info.IsDir() { - return fixPathForm(path, dir) - } - return "./" -} - -// PredictFilesSet predict according to file rules to a given set of file names -func PredictFilesSet(files []string) PredictFunc { - return func(a Args) (prediction []string) { - // add all matching files to prediction - for _, f := range files { - f = fixPathForm(a.Last, f) - - // test matching of file to the argument - if matchFile(f, a.Last) { - prediction = append(prediction, f) - } - } - return - } -} - -func listFiles(dir, pattern string, allowFiles bool) []string { - // set of all file names - m := map[string]bool{} - - // list files - if files, err := filepath.Glob(filepath.Join(dir, pattern)); err == nil { - for _, f := range files { - if stat, err := os.Stat(f); err != nil || stat.IsDir() || allowFiles { - m[f] = true - } - } - } - - // list directories - if dirs, err := ioutil.ReadDir(dir); err == nil { - for _, d := range dirs { - if d.IsDir() { - m[filepath.Join(dir, d.Name())] = true - } - } - } - - list := make([]string, 0, len(m)) - for k := range m { - list = append(list, k) - } - return list -} - -// MatchFile returns true if prefix can match the file -func matchFile(file, prefix string) bool { - // special case for current directory completion - if file == "./" && (prefix == "." || prefix == "") { - return true - } - if prefix == "." && strings.HasPrefix(file, ".") { - return true - } - - file = strings.TrimPrefix(file, "./") - prefix = strings.TrimPrefix(prefix, "./") - - return strings.HasPrefix(file, prefix) -} - -// fixPathForm changes a file name to a relative name -func fixPathForm(last string, file string) string { - // get wording directory for relative name - workDir, err := os.Getwd() - if err != nil { - return file - } - - abs, err := filepath.Abs(file) - if err != nil { - return file - } - - // if last is absolute, return path as absolute - if filepath.IsAbs(last) { - return fixDirPath(abs) - } - - rel, err := filepath.Rel(workDir, abs) - if err != nil { - return file - } - - // fix ./ prefix of path - if rel != "." && strings.HasPrefix(last, ".") { - rel = "./" + rel - } - - return fixDirPath(rel) -} - -func fixDirPath(path string) string { - info, err := os.Stat(path) - if err == nil && info.IsDir() && !strings.HasSuffix(path, "/") { - path += "/" - } - return path -} diff --git a/vendor/github.com/posener/complete/predict_set.go b/vendor/github.com/posener/complete/predict_set.go deleted file mode 100644 index fa4a34ae4..000000000 --- a/vendor/github.com/posener/complete/predict_set.go +++ /dev/null @@ -1,12 +0,0 @@ -package complete - -// PredictSet expects specific set of terms, given in the options argument. -func PredictSet(options ...string) Predictor { - return predictSet(options) -} - -type predictSet []string - -func (p predictSet) Predict(a Args) []string { - return p -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 33299bd24..379277d05 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -100,11 +100,6 @@ github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.0 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/posener/complete v1.2.3 -## explicit; go 1.13 -github.com/posener/complete -github.com/posener/complete/cmd -github.com/posener/complete/cmd/install # github.com/russross/blackfriday/v2 v2.1.0 ## explicit github.com/russross/blackfriday/v2 From 91966478c02f1dd64e1be5c417b771f24154782e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 12 Aug 2022 17:57:00 +0300 Subject: [PATCH 03/34] Add bucket listing as ls's default autocomplete --- command/ls.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/command/ls.go b/command/ls.go index 43a7dffda..baac79777 100644 --- a/command/ls.go +++ b/command/ls.go @@ -53,6 +53,29 @@ func NewListCommand() *cli.Command { HelpName: "ls", Usage: "list buckets and objects", CustomHelpTemplate: listHelpTemplate, + BashComplete: func(ctx *cli.Context) { + ListBuckets(ctx.Context, NewStorageOpts(ctx)) + // fmt.Println("Argüman:", ctx.Args().First(), "BÄ°TTÄ°") + // + // if !ctx.Args().Present() || strings.HasPrefix(ctx.Args().First(), "s3://") { + // url := &url.URL{Type: 0} + // c := ctx.Context + // client, err := storage.NewRemoteClient(c, url, NewStorageOpts(ctx)) + // if err != nil { + // return + // } + // + // buckets, err := client.ListBuckets(c, "") + // if err != nil { + // return + // } + // + // for _, bucket := range buckets { + // fmt.Println("s3://" + bucket.Name) + // } + // } + // fmt.Println(ctx.Args().First()) + }, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "etag", From df05a387bc35a37c6ac197cf078b2fe30a90a317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 12 Aug 2022 18:48:08 +0300 Subject: [PATCH 04/34] ls supports bucket listing add escape colon method for autocomplete parameters. --- command/ls.go | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/command/ls.go b/command/ls.go index baac79777..254e4d3c6 100644 --- a/command/ls.go +++ b/command/ls.go @@ -3,6 +3,7 @@ package command import ( "context" "fmt" + "strings" "github.com/hashicorp/go-multierror" "github.com/urfave/cli/v2" @@ -54,27 +55,24 @@ func NewListCommand() *cli.Command { Usage: "list buckets and objects", CustomHelpTemplate: listHelpTemplate, BashComplete: func(ctx *cli.Context) { - ListBuckets(ctx.Context, NewStorageOpts(ctx)) - // fmt.Println("Argüman:", ctx.Args().First(), "BÄ°TTÄ°") - // - // if !ctx.Args().Present() || strings.HasPrefix(ctx.Args().First(), "s3://") { - // url := &url.URL{Type: 0} - // c := ctx.Context - // client, err := storage.NewRemoteClient(c, url, NewStorageOpts(ctx)) - // if err != nil { - // return - // } - // - // buckets, err := client.ListBuckets(c, "") - // if err != nil { - // return - // } - // - // for _, bucket := range buckets { - // fmt.Println("s3://" + bucket.Name) - // } - // } - // fmt.Println(ctx.Args().First()) + if !ctx.Args().Present() || strings.HasPrefix(ctx.Args().First(), "s3://") { + url := &url.URL{Type: 0} + c := ctx.Context + client, err := storage.NewRemoteClient(c, url, NewStorageOpts(ctx)) + if err != nil { + return + } + + buckets, err := client.ListBuckets(c, "") + if err != nil { + return + } + + for _, bucket := range buckets { + fmt.Println(escapeColon("s3://" + bucket.Name)) + } + } + fmt.Println(ctx.Args().First()) }, Flags: []cli.Flag{ &cli.BoolFlag{ @@ -288,3 +286,11 @@ func validateLSCommand(c *cli.Context) error { } return nil } + +// replace every colon : with \: +// colons are used as a seperator for the autocompletion script +// so "literal colons in completion must be quoted with a backslash" +// see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B +func escapeColon(str string) string { + return strings.ReplaceAll(str, ":", "\\:") +} From 5221856121a77256c3701e4ff24e35b4a4f2b31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 17 Aug 2022 11:27:17 +0300 Subject: [PATCH 05/34] move auto-complete logic to another method --- auto | 4 +-- command/auto_complete.go | 71 ++++++++++++++++++++++++++++++++++++++++ command/ls.go | 41 ++++++++++------------- main_test.Go | 12 ------- storage/url/url.go | 2 +- 5 files changed, 91 insertions(+), 39 deletions(-) create mode 100644 command/auto_complete.go delete mode 100644 main_test.Go diff --git a/auto b/auto index b519666f8..90667a077 100644 --- a/auto +++ b/auto @@ -1,5 +1,3 @@ -#compdef $PROG - _cli_zsh_autocomplete() { local -a opts local cur @@ -17,4 +15,4 @@ _cli_zsh_autocomplete() { fi } -compdef _cli_zsh_autocomplete $PROG +compdef _cli_zsh_autocomplete s5cmd \ No newline at end of file diff --git a/command/auto_complete.go b/command/auto_complete.go new file mode 100644 index 000000000..7657d9996 --- /dev/null +++ b/command/auto_complete.go @@ -0,0 +1,71 @@ +package command + +import ( + "context" + "fmt" + "strings" + + "github.com/peak/s5cmd/storage" + "github.com/peak/s5cmd/storage/url" + "github.com/urfave/cli/v2" +) + +func printS3Suggestions(ctx *cli.Context, arg string) { + // fmt.Println("here!", first) + + c := ctx.Context + u, err := url.New(arg) + if err != nil { + u = &url.URL{Type: 0, Scheme: "s3"} + } + client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) + if err != nil { + // fmt.Println(escapeColon(err.Error())) + return + } + + // fmt.Println("there", first) + if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) { + printListBuckets(c, client, u) + } else { + printListNURLSuggestions(c, client, u, 20) + } +} + +func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL) { + buckets, err := client.ListBuckets(ctx, u.Bucket) + if err != nil { + fmt.Println(escapeColon(err.Error())) + return + } + + for _, bucket := range buckets { + fmt.Println(escapeColon("s3://" + bucket.Name)) //+" ", u.Absolute())) + } +} + +func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int) { + abs := u.Absolute() + if u.IsBucket() { + abs = abs + "/" + } + u, err := url.New(abs + "*") + if err != nil { + fmt.Println(escapeColon(err.Error())) + return + } + + i := 0 + for obj := range (*client).List(ctx, u, false) { + if i > count { + break + } + if obj.Err != nil { + fmt.Println(escapeColon(obj.Err.Error())) + return + } + fmt.Println(escapeColon(obj.URL.Absolute())) + + i++ + } +} diff --git a/command/ls.go b/command/ls.go index 254e4d3c6..f466e6ca4 100644 --- a/command/ls.go +++ b/command/ls.go @@ -49,31 +49,11 @@ Examples: ` func NewListCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "ls", HelpName: "ls", Usage: "list buckets and objects", CustomHelpTemplate: listHelpTemplate, - BashComplete: func(ctx *cli.Context) { - if !ctx.Args().Present() || strings.HasPrefix(ctx.Args().First(), "s3://") { - url := &url.URL{Type: 0} - c := ctx.Context - client, err := storage.NewRemoteClient(c, url, NewStorageOpts(ctx)) - if err != nil { - return - } - - buckets, err := client.ListBuckets(c, "") - if err != nil { - return - } - - for _, bucket := range buckets { - fmt.Println(escapeColon("s3://" + bucket.Name)) - } - } - fmt.Println(ctx.Args().First()) - }, Flags: []cli.Flag{ &cli.BoolFlag{ Name: "etag", @@ -126,6 +106,21 @@ func NewListCommand() *cli.Command { }.Run(c.Context) }, } + cmd.BashComplete = func(ctx *cli.Context) { + // todo do not print errors and other unrelated things + arg := ctx.Args().First() + if strings.HasPrefix(arg, "s3://") { + // fmt.Println("£", ctx.Args().Slice(), "£") + printS3Suggestions(ctx, arg) + + } else { + // fmt.Println("$", ctx.Args().Slice(), "$") + cli.DefaultCompleteWithFlags(cmd)(ctx) + } + // fmt.Println(escapeColon(first, ctx.Args().Slice(), "₺")) + + } + return cmd } // List holds list operation flags and states. @@ -291,6 +286,6 @@ func validateLSCommand(c *cli.Context) error { // colons are used as a seperator for the autocompletion script // so "literal colons in completion must be quoted with a backslash" // see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B -func escapeColon(str string) string { - return strings.ReplaceAll(str, ":", "\\:") +func escapeColon(str ...interface{}) string { + return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) } diff --git a/main_test.Go b/main_test.Go deleted file mode 100644 index 66ebe50b3..000000000 --- a/main_test.Go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -func TestPlay(t *testing.T) { - a := make([]extsort.SortType, 0, 100) - fmt.Println(unsafe.Sizeof(a)) - a = make([]extsort.SortType, 0, 1000) - fmt.Println(unsafe.Sizeof(a)) - a = make([]extsort.SortType, 0, 1000) - fmt.Println(unsafe.Sizeof(a)) - - t.Fatal("It's intentional, don't worry") -} \ No newline at end of file diff --git a/storage/url/url.go b/storage/url/url.go index 22ab37df7..6dcb3cd85 100644 --- a/storage/url/url.go +++ b/storage/url/url.go @@ -103,7 +103,7 @@ func New(s string, opts ...Option) (*URL, error) { url := &URL{ Type: remoteObject, - Scheme: "s3", + Scheme: scheme, Bucket: bucket, Path: key, } From a7b87347fc56ba162604268d8b19c52cc7feefd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 17 Aug 2022 13:45:59 +0300 Subject: [PATCH 06/34] complete autocompletion for ls and cat, prepare help text --- auto | 6 +-- command/app.go | 5 +-- command/auto_complete.go | 91 ++++++++++++++++++++++++++++++++++++---- command/cat.go | 4 +- command/ls.go | 12 +++--- 5 files changed, 95 insertions(+), 23 deletions(-) diff --git a/auto b/auto index 90667a077..de78095ad 100644 --- a/auto +++ b/auto @@ -2,11 +2,7 @@ _cli_zsh_autocomplete() { local -a opts local cur cur=${words[-1]} - if [[ "$cur" == "-"* ]]; then - opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") - else - opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-bash-completion)}") - fi + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") if [[ "${opts[1]}" != "" ]]; then _describe 'values' opts diff --git a/command/app.go b/command/app.go index cb9cc2c5c..6811502ac 100644 --- a/command/app.go +++ b/command/app.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "path/filepath" "github.com/urfave/cli/v2" @@ -146,8 +145,8 @@ var app = &cli.App{ Action: func(c *cli.Context) error { if c.Bool("install-completion") { shell := os.Getenv("SHELL") - // todo printout the script - fmt.Println("You should add following script to ~/." + filepath.Base(shell) + "rc\nTODO") + installCompletionHelp(shell) + return nil } args := c.Args() diff --git a/command/auto_complete.go b/command/auto_complete.go index 7657d9996..65b42227f 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -3,6 +3,7 @@ package command import ( "context" "fmt" + "path/filepath" "strings" "github.com/peak/s5cmd/storage" @@ -10,9 +11,68 @@ import ( "github.com/urfave/cli/v2" ) -func printS3Suggestions(ctx *cli.Context, arg string) { - // fmt.Println("here!", first) +const ( + zsh = `autoload -Uz compinit +compinit + +_cli_zsh_autocomplete() { + local -a opts + local cur + cur=${words[-1]} + opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") + + if [[ "${opts[1]}" != "" ]]; then + _describe 'values' opts + else + _files + fi + } + + compdef _cli_zsh_autocomplete s5cmd +` + bash = `_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} ${cur} --generate-bash-completion ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + } + + complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd` + powershell = `$fn = $($MyInvocation.MyCommand.Name) + $name = $fn -replace "(.*)\.ps1$", '$1' + Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { + param($commandName, $wordToComplete, $cursorPosition) + $other = "$wordToComplete --generate-bash-completion" + Invoke-Expression $other | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } + Footer + ` +) + +func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { + return func(ctx *cli.Context) { + var arg string + args := ctx.Args() + if args.Len() > 0 { + arg = args.Get(args.Len() - 1) + } + if strings.HasPrefix(arg, "s3://") { + printS3Suggestions(ctx, arg) + + } else { + cli.DefaultCompleteWithFlags(cmd)(ctx) + } + } +} + +func printS3Suggestions(ctx *cli.Context, arg string) { c := ctx.Context u, err := url.New(arg) if err != nil { @@ -20,11 +80,9 @@ func printS3Suggestions(ctx *cli.Context, arg string) { } client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) if err != nil { - // fmt.Println(escapeColon(err.Error())) return } - // fmt.Println("there", first) if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) { printListBuckets(c, client, u) } else { @@ -35,12 +93,11 @@ func printS3Suggestions(ctx *cli.Context, arg string) { func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL) { buckets, err := client.ListBuckets(ctx, u.Bucket) if err != nil { - fmt.Println(escapeColon(err.Error())) return } for _, bucket := range buckets { - fmt.Println(escapeColon("s3://" + bucket.Name)) //+" ", u.Absolute())) + fmt.Println(escapeColon("s3://" + bucket.Name)) } } @@ -51,7 +108,6 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR } u, err := url.New(abs + "*") if err != nil { - fmt.Println(escapeColon(err.Error())) return } @@ -61,7 +117,6 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR break } if obj.Err != nil { - fmt.Println(escapeColon(obj.Err.Error())) return } fmt.Println(escapeColon(obj.URL.Absolute())) @@ -69,3 +124,23 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR i++ } } + +func installCompletionHelp(shell string) { + baseShell := filepath.Base(shell) + fmt.Println("# To enable autocompletion you should add the following script to startup scripts of your shell.") + if baseShell != "" { + fmt.Println("# It is probably located at ~/." + baseShell + "rc") + } + var script string + if baseShell == "zsh" { + script = zsh + } else if baseShell == "bash" { + script = bash + } else if baseShell == "powershell" { + script = powershell + } else { + script = "# Your shell \"" + baseShell + "\" is not recognized. Auto complete is only available with zsh, bash and powershell (?)." + } + + fmt.Println(script) +} diff --git a/command/cat.go b/command/cat.go index 0e66afddb..78bf73f9d 100644 --- a/command/cat.go +++ b/command/cat.go @@ -28,7 +28,7 @@ Examples: ` func NewCatCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "cat", HelpName: "cat", Usage: "print remote object content", @@ -60,6 +60,8 @@ func NewCatCommand() *cli.Command { }.Run(c.Context) }, } + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } // Cat holds cat operation flags and states. diff --git a/command/ls.go b/command/ls.go index f466e6ca4..eebb09a0c 100644 --- a/command/ls.go +++ b/command/ls.go @@ -106,19 +106,19 @@ func NewListCommand() *cli.Command { }.Run(c.Context) }, } + cmd.BashComplete = func(ctx *cli.Context) { - // todo do not print errors and other unrelated things - arg := ctx.Args().First() + var arg string + args := ctx.Args() + if args.Len() > 0 { + arg = args.Get(args.Len() - 1) + } if strings.HasPrefix(arg, "s3://") { - // fmt.Println("£", ctx.Args().Slice(), "£") printS3Suggestions(ctx, arg) } else { - // fmt.Println("$", ctx.Args().Slice(), "$") cli.DefaultCompleteWithFlags(cmd)(ctx) } - // fmt.Println(escapeColon(first, ctx.Args().Slice(), "₺")) - } return cmd } From 8accdc959e1961a7bdb5fcafd368f68afd5f9184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 17 Aug 2022 13:59:30 +0300 Subject: [PATCH 07/34] Add auto completion support to all commands except the "version" and "mb", for which autocompletion of remote buckets/keys does not make sense. --- command/cp.go | 5 ++++- command/du.go | 5 ++++- command/mv.go | 5 ++++- command/rb.go | 5 ++++- command/rm.go | 5 ++++- command/run.go | 5 ++++- command/select.go | 5 ++++- command/sync.go | 5 ++++- 8 files changed, 32 insertions(+), 8 deletions(-) diff --git a/command/cp.go b/command/cp.go index 448bd4b37..9a837195e 100644 --- a/command/cp.go +++ b/command/cp.go @@ -213,7 +213,7 @@ func NewCopyCommandFlags() []cli.Flag { } func NewCopyCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "cp", HelpName: "cp", Usage: "copy objects", @@ -233,6 +233,9 @@ func NewCopyCommand() *cli.Command { return NewCopy(c, false).Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } // Copy holds copy operation flags and states. diff --git a/command/du.go b/command/du.go index 871910108..c188d52a9 100644 --- a/command/du.go +++ b/command/du.go @@ -36,7 +36,7 @@ Examples: ` func NewSizeCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "du", HelpName: "du", Usage: "show object size usage", @@ -80,6 +80,9 @@ func NewSizeCommand() *cli.Command { }.Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } // Size holds disk usage (du) operation flags and states. diff --git a/command/mv.go b/command/mv.go index fbc4e359d..dfc07bcd1 100644 --- a/command/mv.go +++ b/command/mv.go @@ -39,7 +39,7 @@ Examples: ` func NewMoveCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "mv", HelpName: "mv", Usage: "move/rename objects", @@ -55,4 +55,7 @@ func NewMoveCommand() *cli.Command { return NewCopy(c, true).Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } diff --git a/command/rb.go b/command/rb.go index df8eac01a..66c2f7940 100644 --- a/command/rb.go +++ b/command/rb.go @@ -26,7 +26,7 @@ Examples: ` func NewRemoveBucketCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "rb", HelpName: "rb", Usage: "remove bucket", @@ -50,6 +50,9 @@ func NewRemoveBucketCommand() *cli.Command { }.Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } // RemoveBucket holds bucket deletion operation flags and states. diff --git a/command/rm.go b/command/rm.go index 85d91943b..02c2479e7 100644 --- a/command/rm.go +++ b/command/rm.go @@ -41,7 +41,7 @@ Examples: ` func NewDeleteCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "rm", HelpName: "rm", Usage: "remove objects", @@ -78,6 +78,9 @@ func NewDeleteCommand() *cli.Command { }.Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } // Delete holds delete operation flags and states. diff --git a/command/run.go b/command/run.go index 0b1774823..6047f2ab3 100644 --- a/command/run.go +++ b/command/run.go @@ -34,7 +34,7 @@ Examples: ` func NewRunCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "run", HelpName: "run", Usage: "run commands in batch", @@ -62,6 +62,9 @@ func NewRunCommand() *cli.Command { return NewRun(c, reader).Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } type Run struct { diff --git a/command/select.go b/command/select.go index 1db02ef7c..d69083330 100644 --- a/command/select.go +++ b/command/select.go @@ -32,7 +32,7 @@ Examples: ` func NewSelectCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "select", HelpName: "select", Usage: "run SQL queries on objects", @@ -94,6 +94,9 @@ func NewSelectCommand() *cli.Command { }.Run(c.Context) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } // Select holds select operation flags and states. diff --git a/command/sync.go b/command/sync.go index 841766d9d..b68ad1d11 100644 --- a/command/sync.go +++ b/command/sync.go @@ -77,7 +77,7 @@ func NewSyncCommandFlags() []cli.Flag { } func NewSyncCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "sync", HelpName: "sync", Usage: "sync objects", @@ -97,6 +97,9 @@ func NewSyncCommand() *cli.Command { return NewSync(c).Run(c) }, } + + cmd.BashComplete = getBashCompleteFn(cmd) + return cmd } type ObjectPair struct { From 6b86541f52417c2c729de69e36484bf036cbbdaa Mon Sep 17 00:00:00 2001 From: Selman Kayrancioglu Date: Wed, 17 Aug 2022 19:24:24 +0300 Subject: [PATCH 08/34] command/autocomplete: update bash script to test --- command/auto_complete.go | 43 +++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 65b42227f..83f1c0161 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -30,18 +30,37 @@ _cli_zsh_autocomplete() { compdef _cli_zsh_autocomplete s5cmd ` - bash = `_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} ${cur} --generate-bash-completion ) - COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - return 0 - fi - } - - complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd` + // NOTE: Broken, WIP. Requires `bash-completion` to be installed/sourced; + // - https://github.com/scop/bash-completion + bash = ` +_cli_bash_autocomplete() { + + local cur prev words cword split + _init_completion -n : -s || return + + # print cur prev words cword split + echo '---------' >> bash.log + echo "cur: ${cur}" >> bash.log + echo "prev: ${prev}" >> bash.log + echo "words: ${words[*]}" >> bash.log + echo "cword: ${cword}" >> bash.log + echo "split: ${split}" >> bash.log + echo "cmd : ${words[@]:0:$cword}" >> bash.log + echo '---------' >> bash.log + local cmd="${words[@]:0:$cword}" + + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + COMPREPLY=() + local opts=$(${cmd} ${cur} --generate-bash-completion) + + echo "opts: ${opts}" >>bash.log + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + __ltrim_colon_completions "$cur" + return 0 + fi +} +complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd +` powershell = `$fn = $($MyInvocation.MyCommand.Name) $name = $fn -replace "(.*)\.ps1$", '$1' From c671bca452b7b69d6e487aeba77d7efa54f55d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 19 Aug 2022 18:52:04 +0300 Subject: [PATCH 09/34] improve bash completion --- command/auto_complete.go | 89 +++++++++++++++++++++++++--------------- command/ls.go | 14 +------ 2 files changed, 58 insertions(+), 45 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 83f1c0161..cd219f1e0 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -3,6 +3,7 @@ package command import ( "context" "fmt" + "os" "path/filepath" "strings" @@ -33,35 +34,44 @@ _cli_zsh_autocomplete() { // NOTE: Broken, WIP. Requires `bash-completion` to be installed/sourced; // - https://github.com/scop/bash-completion bash = ` -_cli_bash_autocomplete() { - - local cur prev words cword split - _init_completion -n : -s || return - - # print cur prev words cword split - echo '---------' >> bash.log - echo "cur: ${cur}" >> bash.log - echo "prev: ${prev}" >> bash.log - echo "words: ${words[*]}" >> bash.log - echo "cword: ${cword}" >> bash.log - echo "split: ${split}" >> bash.log - echo "cmd : ${words[@]:0:$cword}" >> bash.log - echo '---------' >> bash.log - local cmd="${words[@]:0:$cword}" - - if [[ "${COMP_WORDS[0]}" != "source" ]]; then - COMPREPLY=() - local opts=$(${cmd} ${cur} --generate-bash-completion) - - echo "opts: ${opts}" >>bash.log - COMPREPLY=($(compgen -W "${opts}" -- ${cur})) - __ltrim_colon_completions "$cur" - return 0 - fi +_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} ${cur} --generate-bash-completion); + COMPREPLY=($(compgen -W "${opts}")); + return 0; + fi } + complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd ` - + /* + _cli_bash_autocomplete() { + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$(${COMP_LINE} --generate-bash-completion) + # echo "! + # cur id $cur + # opts are ${opts}. + # !" + + # add each line as an element + while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -W "${ + + # COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + # echo "§ + # COMPREPLY =" "${COMPREPLY[@]}" " + # §" + return 0 + fi + } + + complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd + */ powershell = `$fn = $($MyInvocation.MyCommand.Name) $name = $fn -replace "(.*)\.ps1$", '$1' Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { @@ -79,15 +89,22 @@ func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { return func(ctx *cli.Context) { var arg string args := ctx.Args() - if args.Len() > 0 { - arg = args.Get(args.Len() - 1) + l := args.Len() + f, _ := os.Open("log.txt") + defer f.Close() + f.WriteString(fmt.Sprint("this//arg=", args, "l=", l)) + + if l > 2 && filepath.Base(os.Getenv("SHELL")) == "bash" { + arg = args.Get(l-3) + args.Get(l-2) + args.Get(l-1) + } else if l > 0 { + arg = args.Get(l - 1) } if strings.HasPrefix(arg, "s3://") { printS3Suggestions(ctx, arg) - } else { cli.DefaultCompleteWithFlags(cmd)(ctx) } + f.WriteString(escapeColon(fmt.Sprint("\n//arg=", arg, "l=", l))) } } @@ -105,7 +122,7 @@ func printS3Suggestions(ctx *cli.Context, arg string) { if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) { printListBuckets(c, client, u) } else { - printListNURLSuggestions(c, client, u, 20) + printListNURLSuggestions(c, client, u, 13) } } @@ -116,7 +133,11 @@ func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL) { } for _, bucket := range buckets { - fmt.Println(escapeColon("s3://" + bucket.Name)) + if filepath.Base(os.Getenv("SHELL")) == "bash" { + fmt.Println(escapeColon("//" + bucket.Name)) + } else { + fmt.Println(escapeColon("s3://" + bucket.Name)) + } } } @@ -138,7 +159,11 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR if obj.Err != nil { return } - fmt.Println(escapeColon(obj.URL.Absolute())) + if filepath.Base(os.Getenv("SHELL")) == "bash" { + fmt.Println(escapeColon(strings.TrimPrefix(obj.URL.Absolute(), "s3:"))) + } else { + fmt.Println(escapeColon(obj.URL.Absolute())) + } i++ } diff --git a/command/ls.go b/command/ls.go index eebb09a0c..468f06962 100644 --- a/command/ls.go +++ b/command/ls.go @@ -107,19 +107,7 @@ func NewListCommand() *cli.Command { }, } - cmd.BashComplete = func(ctx *cli.Context) { - var arg string - args := ctx.Args() - if args.Len() > 0 { - arg = args.Get(args.Len() - 1) - } - if strings.HasPrefix(arg, "s3://") { - printS3Suggestions(ctx, arg) - - } else { - cli.DefaultCompleteWithFlags(cmd)(ctx) - } - } + cmd.BashComplete = getBashCompleteFn(cmd) return cmd } From e72c6e6fb820f06307bf2e60e4eea4cc33b5d778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 24 Aug 2022 19:29:34 +0300 Subject: [PATCH 10/34] implement the The Fix - autocomplete used to fall back to default bash behaviour after the autocompletion suggestions are filtered. So the default bash completion completed "s5cmd ls s3://b" to "s3://bin/" since there is a directory named "/bin/" in the local. Default behavior ignores "s3:" since : is a COMP_WORDBREAK. by moving the completion fallback to compgen function we ensure that local completions are also filtered properly. --- command/auto_complete.go | 89 ++++++++++++++++++++++------------------ command/ls.go | 9 ---- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index cd219f1e0..666c10322 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -33,45 +33,35 @@ _cli_zsh_autocomplete() { ` // NOTE: Broken, WIP. Requires `bash-completion` to be installed/sourced; // - https://github.com/scop/bash-completion - bash = ` -_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} ${cur} --generate-bash-completion); - COMPREPLY=($(compgen -W "${opts}")); - return 0; - fi -} + bash = `_s5cmd_cli_bash_autocomplete() { + +# get current word and its index (cur and cword respectively), +# and prepare command (cmd) +# exclude : from the word breaks +local cur +cur="${COMP_WORDS[COMP_CWORD]}" +cmd="${COMP_LINE:0:$COMP_POINT}" + +echo cmd "$cmd" >> log.txt + +if [[ "${COMP_WORDS[0]}" != "source" ]]; then + COMPREPLY=() + # execute the command with '--generate-bash-completion' flag to obtain + # possible completion values for current word + local opts=$(${cmd} --generate-bash-completion) + + + # prepare completion array with possible values and filter those does not + # start with cur if no completion is found then fallback to default completion of shell. + COMPREPLY=($(compgen -o bashdefault -o default -o nospace -W "${opts}" -- ${cur})) + + return 0 +fi + } -complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd +# call the _s5cmd_cli_bash_autocomplete to complete s5cmd command. +complete -F _s5cmd_cli_bash_autocomplete s5cmd ` - /* - _cli_bash_autocomplete() { - if [[ "${COMP_WORDS[0]}" != "source" ]]; then - local cur opts base - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - opts=$(${COMP_LINE} --generate-bash-completion) - # echo "! - # cur id $cur - # opts are ${opts}. - # !" - - # add each line as an element - while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -W "${ - - # COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) - # echo "§ - # COMPREPLY =" "${COMPREPLY[@]}" " - # §" - return 0 - fi - } - - complete -o bashdefault -o default -o nospace -F _cli_bash_autocomplete s5cmd - */ powershell = `$fn = $($MyInvocation.MyCommand.Name) $name = $fn -replace "(.*)\.ps1$", '$1' Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { @@ -122,7 +112,12 @@ func printS3Suggestions(ctx *cli.Context, arg string) { if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) { printListBuckets(c, client, u) } else { - printListNURLSuggestions(c, client, u, 13) + prefix := "" + if i := strings.LastIndex(arg, ":"); i >= 0 { //os.Getenv("COMP_WORDBREAKS")) + prefix = arg[0 : i+1] + } + + printListNURLSuggestions(c, client, u, 13, prefix) } } @@ -141,7 +136,7 @@ func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL) { } } -func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int) { +func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int, prefix string) { abs := u.Absolute() if u.IsBucket() { abs = abs + "/" @@ -160,7 +155,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR return } if filepath.Base(os.Getenv("SHELL")) == "bash" { - fmt.Println(escapeColon(strings.TrimPrefix(obj.URL.Absolute(), "s3:"))) + fmt.Println(escapeColon(strings.TrimPrefix(obj.URL.Absolute(), prefix))) } else { fmt.Println(escapeColon(obj.URL.Absolute())) } @@ -188,3 +183,17 @@ func installCompletionHelp(shell string) { fmt.Println(script) } + +// replace every colon : with \: if shell is zsh +// colons are used as a seperator for the autocompletion script +// so "literal colons in completion must be quoted with a backslash" +// see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B +func escapeColon(str ...interface{}) string { + baseShell := filepath.Base(os.Getenv("SHELL")) + + if baseShell == "zsh" { + return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) + } + + return fmt.Sprint(str...) +} diff --git a/command/ls.go b/command/ls.go index 468f06962..8407d6793 100644 --- a/command/ls.go +++ b/command/ls.go @@ -3,7 +3,6 @@ package command import ( "context" "fmt" - "strings" "github.com/hashicorp/go-multierror" "github.com/urfave/cli/v2" @@ -269,11 +268,3 @@ func validateLSCommand(c *cli.Context) error { } return nil } - -// replace every colon : with \: -// colons are used as a seperator for the autocompletion script -// so "literal colons in completion must be quoted with a backslash" -// see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B -func escapeColon(str ...interface{}) string { - return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) -} From c3bfe47ea73b82fe3f7d619e5b4680b6654803ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 24 Aug 2022 19:39:09 +0300 Subject: [PATCH 11/34] change the suggestion behaviour - It used to complete full s3 keys at once, but now it only completes till next s3 seperator (slash) - bucket completion now appends slash (/) to the end of the bucket --- command/auto_complete.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 666c10322..e97402553 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -129,9 +129,9 @@ func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL) { for _, bucket := range buckets { if filepath.Base(os.Getenv("SHELL")) == "bash" { - fmt.Println(escapeColon("//" + bucket.Name)) + fmt.Println(escapeColon("//" + bucket.Name + "/")) } else { - fmt.Println(escapeColon("s3://" + bucket.Name)) + fmt.Println(escapeColon("s3://" + bucket.Name + "/")) } } } @@ -141,7 +141,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR if u.IsBucket() { abs = abs + "/" } - u, err := url.New(abs + "*") + u, err := url.New(abs) if err != nil { return } From 24bc369ed57b419fcc793d3891d16bfb9ba9f3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 25 Aug 2022 13:50:43 +0300 Subject: [PATCH 12/34] prepare suggestions depending on the shell and environment variable COMP_WORDBREAKS --- command/auto_complete.go | 190 +++++++++++++++++++++------------------ 1 file changed, 103 insertions(+), 87 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index e97402553..c76cd4f33 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -12,89 +12,94 @@ import ( "github.com/urfave/cli/v2" ) -const ( - zsh = `autoload -Uz compinit +const zsh = `autoload -Uz compinit compinit _cli_zsh_autocomplete() { local -a opts local cur cur=${words[-1]} - opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") - + opts=("${(@f)$(${words[@]:0:#words[@]-1} "${cur}" --generate-bash-completion)}") + if [[ "${opts[1]}" != "" ]]; then _describe 'values' opts else _files fi - } - - compdef _cli_zsh_autocomplete s5cmd -` - // NOTE: Broken, WIP. Requires `bash-completion` to be installed/sourced; - // - https://github.com/scop/bash-completion - bash = `_s5cmd_cli_bash_autocomplete() { - -# get current word and its index (cur and cword respectively), -# and prepare command (cmd) -# exclude : from the word breaks -local cur -cur="${COMP_WORDS[COMP_CWORD]}" -cmd="${COMP_LINE:0:$COMP_POINT}" - -echo cmd "$cmd" >> log.txt - -if [[ "${COMP_WORDS[0]}" != "source" ]]; then - COMPREPLY=() - # execute the command with '--generate-bash-completion' flag to obtain - # possible completion values for current word - local opts=$(${cmd} --generate-bash-completion) - +} - # prepare completion array with possible values and filter those does not - # start with cur if no completion is found then fallback to default completion of shell. - COMPREPLY=($(compgen -o bashdefault -o default -o nospace -W "${opts}" -- ${cur})) +compdef _cli_zsh_autocomplete s5cmd +` - return 0 -fi - } +const bash = `# prepare autocompletion suggestions for s5cmd and save them to COMPREPLY array +_s5cmd_cli_bash_autocomplete() { + + if [[ "${COMP_WORDS[0]}" != "source" ]]; then + COMPREPLY=() + local opts cur cmd + + # get current word (cur) and prepare command (cmd) + cur="${COMP_WORDS[COMP_CWORD]}" + cmd="${COMP_LINE:0:$COMP_POINT}" + + # if we want to complete the second argurment and we didn't started writing + # yet then we should pass an empty string as another argument. Otherwise + # the white spaces will be discarded and the program will make suggestions + # as if it is completing the first argument. + # shellcheck disable=SC2089,SC2090 + # Beware that the we want to pass empty string so we intentionally write + # as it is. Fixes of SC2089 and SC2090 are not what we want. + # see also https://www.shellcheck.net/wiki/SC2090 + [ "${COMP_LINE:COMP_POINT-1:$COMP_POINT}" == " " ] \ + && [ "${COMP_LINE:COMP_POINT-2:$COMP_POINT}" != '\ ' ] \ + && cmd="${cmd} \"\"" + + # execute the command with '--generate-bash-completion' flag to obtain + # possible completion values for current word. + # shellcheck disable=SC2090 + # We also want to pass COMP_WORDBREAKS to app. Because the application + # will prepare different suggestions depending on whether COMP_WORDBREAKS + # contains colons or not. + opts=$(COMP_WORDBREAKS=$COMP_WORDBREAKS $cmd --generate-bash-completion) + + # prepare completion array with possible values and filter those does not + # start with cur. if no completion is found then fallback to default completion of shell. + while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -o bashdefault -o default -o nospace -W "${opts}" -- "${cur}") + + return 0 + fi +} # call the _s5cmd_cli_bash_autocomplete to complete s5cmd command. complete -F _s5cmd_cli_bash_autocomplete s5cmd ` - powershell = `$fn = $($MyInvocation.MyCommand.Name) - $name = $fn -replace "(.*)\.ps1$", '$1' - Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { - param($commandName, $wordToComplete, $cursorPosition) - $other = "$wordToComplete --generate-bash-completion" - Invoke-Expression $other | ForEach-Object { - [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) - } - } - Footer - ` -) + +const pwsh = `$fn = $($MyInvocation.MyCommand.Name) +$name = $fn -replace "(.*)\.ps1$", '$1' +Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { + param($commandName, $wordToComplete, $cursorPosition) + $other = "$wordToComplete --generate-bash-completion" + Invoke-Expression $other | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} +` func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { return func(ctx *cli.Context) { var arg string args := ctx.Args() l := args.Len() - f, _ := os.Open("log.txt") - defer f.Close() - f.WriteString(fmt.Sprint("this//arg=", args, "l=", l)) - if l > 2 && filepath.Base(os.Getenv("SHELL")) == "bash" { - arg = args.Get(l-3) + args.Get(l-2) + args.Get(l-1) - } else if l > 0 { + if l > 0 { arg = args.Get(l - 1) } + if strings.HasPrefix(arg, "s3://") { printS3Suggestions(ctx, arg) } else { cli.DefaultCompleteWithFlags(cmd)(ctx) } - f.WriteString(escapeColon(fmt.Sprint("\n//arg=", arg, "l=", l))) } } @@ -110,33 +115,25 @@ func printS3Suggestions(ctx *cli.Context, arg string) { } if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) { - printListBuckets(c, client, u) + printListBuckets(c, client, u, arg) } else { - prefix := "" - if i := strings.LastIndex(arg, ":"); i >= 0 { //os.Getenv("COMP_WORDBREAKS")) - prefix = arg[0 : i+1] - } - - printListNURLSuggestions(c, client, u, 13, prefix) + printListNURLSuggestions(c, client, u, 13, arg) } } -func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL) { +func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argToBeCompleted string) { buckets, err := client.ListBuckets(ctx, u.Bucket) if err != nil { return } for _, bucket := range buckets { - if filepath.Base(os.Getenv("SHELL")) == "bash" { - fmt.Println(escapeColon("//" + bucket.Name + "/")) - } else { - fmt.Println(escapeColon("s3://" + bucket.Name + "/")) - } + fmt.Println(formatSuggestionForShell("s3://"+bucket.Name+"/", argToBeCompleted)) + } } -func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int, prefix string) { +func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int, argToBeCompleted string) { abs := u.Absolute() if u.IsBucket() { abs = abs + "/" @@ -154,46 +151,65 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR if obj.Err != nil { return } - if filepath.Base(os.Getenv("SHELL")) == "bash" { - fmt.Println(escapeColon(strings.TrimPrefix(obj.URL.Absolute(), prefix))) - } else { - fmt.Println(escapeColon(obj.URL.Absolute())) - } - + fmt.Println(formatSuggestionForShell(obj.URL.Absolute(), argToBeCompleted)) i++ } } func installCompletionHelp(shell string) { - baseShell := filepath.Base(shell) - fmt.Println("# To enable autocompletion you should add the following script to startup scripts of your shell.") - if baseShell != "" { - fmt.Println("# It is probably located at ~/." + baseShell + "rc") - } var script string + baseShell := filepath.Base(shell) + instructions := "# To enable autocompletion you should add the following script" + + " to startup scripts of your shell.\n" + + "# It is probably located at ~/." + baseShell + "rc" + if baseShell == "zsh" { script = zsh } else if baseShell == "bash" { script = bash - } else if baseShell == "powershell" { - script = powershell + } else if baseShell == "pwsh" { + script = pwsh + instructions = "# To enable autocompletion you should save the following" + + " script to a file named \"s5cmd.ps1\" and execute it.\n# To persist it" + + " you should add the path of \"s5cmd.ps1\" file to profile file " + + "(which you can locate with $profile) to automatically execute \"s5cmd.ps1\"" + + " on every shell start up." } else { - script = "# Your shell \"" + baseShell + "\" is not recognized. Auto complete is only available with zsh, bash and powershell (?)." + instructions = "# We couldn't recognize your SHELL \"" + baseShell + "\".\n" + + "# Shell completion is supported only for bash, pwsh and zsh." + + "# Make sure that your SHELL environment variable is set accurately." } + fmt.Println(instructions) fmt.Println(script) } -// replace every colon : with \: if shell is zsh -// colons are used as a seperator for the autocompletion script -// so "literal colons in completion must be quoted with a backslash" -// see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B -func escapeColon(str ...interface{}) string { +func formatSuggestionForShell(suggestion, argToBeCompleted string) string { + var prefix string baseShell := filepath.Base(os.Getenv("SHELL")) + if i := strings.LastIndex(argToBeCompleted, ":"); i >= 0 && baseShell == "bash" && + strings.Contains(os.Getenv("COMP_WORDBREAKS"), ":") { + prefix = argToBeCompleted[0 : i+1] + } + // fmt.Println("Wb", os.Getenv("COMP_WORDBREAKS")) + // fmt.Println("prefix", prefix) + // fmt.Println("org sug", suggestion) + + suggestion = strings.TrimPrefix(suggestion, prefix) + + // fmt.Println("new sug", suggestion) + // replace every colon : with \: if shell is zsh + // colons are used as a seperator for the autocompletion script + // so "literal colons in completion must be quoted with a backslash" + // see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B if baseShell == "zsh" { - return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) + suggestion = escapeColon(suggestion) } + return suggestion +} - return fmt.Sprint(str...) +// replace every colon : with \: +func escapeColon(str ...interface{}) string { + return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) } From a117df00ee5403bca30f9d54afd8228983437db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 25 Aug 2022 18:40:27 +0300 Subject: [PATCH 13/34] improve suggestions --- command/auto_complete.go | 40 ++++++++++++++++++++++++++++++++++++++++ command/cat.go | 2 +- command/mb.go | 5 ++++- command/rb.go | 2 +- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index c76cd4f33..d4880bcaf 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -103,6 +103,46 @@ func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { } } +func getRemoteCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { + return func(ctx *cli.Context) { + var arg string + args := ctx.Args() + l := args.Len() + + if l > 0 { + arg = args.Get(l - 1) + } + + if strings.HasPrefix(arg, "-") { + cli.DefaultCompleteWithFlags(cmd)(ctx) + } else if !strings.HasPrefix(arg, "s3://") { + arg = "s3://" + } + printS3Suggestions(ctx, arg) + } +} + +// it returns a complete function which prints the argument, itself, which is to be completed. +// If the argument is empty string it uses the defaultCompletions to make suggestions. +func ineffectiveCompleteFnWithDefault(cmd *cli.Command, defaultCompletions ...string) func(ctx *cli.Context) { + return func(ctx *cli.Context) { + var arg string + args := ctx.Args() + if args.Len() > 0 { + arg = args.Get(args.Len() - 1) + } + if arg == "" { + for _, str := range defaultCompletions { + fmt.Println(formatSuggestionForShell(str, str)) + } + } else if strings.HasPrefix(arg, "-") { + cli.DefaultCompleteWithFlags(cmd)(ctx) + } else { + fmt.Println(formatSuggestionForShell(arg, arg)) + } + } +} + func printS3Suggestions(ctx *cli.Context, arg string) { c := ctx.Context u, err := url.New(arg) diff --git a/command/cat.go b/command/cat.go index 78bf73f9d..f19330ff3 100644 --- a/command/cat.go +++ b/command/cat.go @@ -60,7 +60,7 @@ func NewCatCommand() *cli.Command { }.Run(c.Context) }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getRemoteCompleteFn(cmd) return cmd } diff --git a/command/mb.go b/command/mb.go index 9a70f9937..06bb0387b 100644 --- a/command/mb.go +++ b/command/mb.go @@ -27,7 +27,7 @@ Examples: ` func NewMakeBucketCommand() *cli.Command { - return &cli.Command{ + cmd := &cli.Command{ Name: "mb", HelpName: "mb", Usage: "make bucket", @@ -51,6 +51,9 @@ func NewMakeBucketCommand() *cli.Command { }.Run(c.Context) }, } + cmd.BashComplete = ineffectiveCompleteFnWithDefault(cmd, "s3://") + + return cmd } // MakeBucket holds bucket creation operation flags and states. diff --git a/command/rb.go b/command/rb.go index 66c2f7940..37b4b692f 100644 --- a/command/rb.go +++ b/command/rb.go @@ -51,7 +51,7 @@ func NewRemoveBucketCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getRemoteCompleteFn(cmd) return cmd } From 8ac1a986f701662777f8bbc56af4374b1ad0f3f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 26 Aug 2022 11:37:21 +0300 Subject: [PATCH 14/34] handle the leading quotation marks, URL.Match should check if filterRegex is nil --- command/auto_complete.go | 28 ++++++++++++++++++---------- storage/url/url.go | 4 ++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index d4880bcaf..4e1b55013 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -15,7 +15,7 @@ import ( const zsh = `autoload -Uz compinit compinit -_cli_zsh_autocomplete() { +_s5cmd_cli_zsh_autocomplete() { local -a opts local cur cur=${words[-1]} @@ -28,7 +28,7 @@ _cli_zsh_autocomplete() { fi } -compdef _cli_zsh_autocomplete s5cmd +compdef _s5cmd_cli_zsh_autocomplete s5cmd ` const bash = `# prepare autocompletion suggestions for s5cmd and save them to COMPREPLY array @@ -80,7 +80,7 @@ Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { param($commandName, $wordToComplete, $cursorPosition) $other = "$wordToComplete --generate-bash-completion" Invoke-Expression $other | ForEach-Object { - [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } ` @@ -95,6 +95,16 @@ func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { arg = args.Get(l - 1) } + // argument may start with a quotation mark, in this case we want to trim that before + // checking if it has prefix s3:// + // Beware that we only want to trim the first char, not all of the leading + // quotation marks, because those quotation marks may be actual charactes. + if strings.HasPrefix(arg, "'") { + arg = strings.TrimPrefix(arg, "'") + } else { + arg = strings.TrimPrefix(arg, "\"") + } + if strings.HasPrefix(arg, "s3://") { printS3Suggestions(ctx, arg) } else { @@ -149,6 +159,7 @@ func printS3Suggestions(ctx *cli.Context, arg string) { if err != nil { u = &url.URL{Type: 0, Scheme: "s3"} } + client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) if err != nil { return @@ -169,7 +180,6 @@ func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argTo for _, bucket := range buckets { fmt.Println(formatSuggestionForShell("s3://"+bucket.Name+"/", argToBeCompleted)) - } } @@ -228,17 +238,15 @@ func formatSuggestionForShell(suggestion, argToBeCompleted string) string { var prefix string baseShell := filepath.Base(os.Getenv("SHELL")) - if i := strings.LastIndex(argToBeCompleted, ":"); i >= 0 && baseShell == "bash" && - strings.Contains(os.Getenv("COMP_WORDBREAKS"), ":") { + if i := strings.LastIndex(argToBeCompleted, ":"); i >= 0 && baseShell == "bash" { + // write the original suggestion in case that the argToBeCompleted was quoted. + // Bash doesn't split on : when argument is quoted even if : is in COMP_WORDBREAKS + fmt.Println(suggestion) prefix = argToBeCompleted[0 : i+1] } - // fmt.Println("Wb", os.Getenv("COMP_WORDBREAKS")) - // fmt.Println("prefix", prefix) - // fmt.Println("org sug", suggestion) suggestion = strings.TrimPrefix(suggestion, prefix) - // fmt.Println("new sug", suggestion) // replace every colon : with \: if shell is zsh // colons are used as a seperator for the autocompletion script // so "literal colons in completion must be quoted with a backslash" diff --git a/storage/url/url.go b/storage/url/url.go index 6dcb3cd85..f0b66b4ad 100644 --- a/storage/url/url.go +++ b/storage/url/url.go @@ -326,6 +326,10 @@ func (u *URL) SetRelative(base *URL) { // Match reports whether if given key matches with the object. func (u *URL) Match(key string) bool { + if u.filterRegex == nil { + return false + } + if !u.filterRegex.MatchString(key) { return false } From 3caa60c535aea3072d00a311b3d794b2354c3737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 26 Aug 2022 11:48:05 +0300 Subject: [PATCH 15/34] bash completion: Don't pass COMP_WORDBREAKS, don't add space after completion --- command/auto_complete.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 4e1b55013..bd1d8c035 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -57,10 +57,7 @@ _s5cmd_cli_bash_autocomplete() { # execute the command with '--generate-bash-completion' flag to obtain # possible completion values for current word. # shellcheck disable=SC2090 - # We also want to pass COMP_WORDBREAKS to app. Because the application - # will prepare different suggestions depending on whether COMP_WORDBREAKS - # contains colons or not. - opts=$(COMP_WORDBREAKS=$COMP_WORDBREAKS $cmd --generate-bash-completion) + opts=$($cmd --generate-bash-completion) # prepare completion array with possible values and filter those does not # start with cur. if no completion is found then fallback to default completion of shell. @@ -71,7 +68,7 @@ _s5cmd_cli_bash_autocomplete() { } # call the _s5cmd_cli_bash_autocomplete to complete s5cmd command. -complete -F _s5cmd_cli_bash_autocomplete s5cmd +complete -o nospace -F _s5cmd_cli_bash_autocomplete s5cmd ` const pwsh = `$fn = $($MyInvocation.MyCommand.Name) From fa36da43b3ae331c6902cada963021cce2b6dd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 26 Aug 2022 16:30:39 +0300 Subject: [PATCH 16/34] Delete auto --- auto | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 auto diff --git a/auto b/auto deleted file mode 100644 index de78095ad..000000000 --- a/auto +++ /dev/null @@ -1,14 +0,0 @@ -_cli_zsh_autocomplete() { - local -a opts - local cur - cur=${words[-1]} - opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") - - if [[ "${opts[1]}" != "" ]]; then - _describe 'values' opts - else - _files - fi -} - -compdef _cli_zsh_autocomplete s5cmd \ No newline at end of file From 3053f1a3ad0403148678c4e9faf4283bfc838731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 26 Aug 2022 19:38:57 +0300 Subject: [PATCH 17/34] print errors to stderr, prepare test template --- command/auto_complete.go | 4 ++ e2e/app_test.go | 85 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) diff --git a/command/auto_complete.go b/command/auto_complete.go index bd1d8c035..b6e87aae0 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -159,6 +159,7 @@ func printS3Suggestions(ctx *cli.Context, arg string) { client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) if err != nil { + fmt.Fprintln(os.Stderr, err) return } @@ -172,6 +173,7 @@ func printS3Suggestions(ctx *cli.Context, arg string) { func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argToBeCompleted string) { buckets, err := client.ListBuckets(ctx, u.Bucket) if err != nil { + fmt.Fprintln(os.Stderr, err) return } @@ -187,6 +189,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR } u, err := url.New(abs) if err != nil { + fmt.Fprintln(os.Stderr, err) return } @@ -196,6 +199,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR break } if obj.Err != nil { + fmt.Fprintln(os.Stderr, obj.Err) return } fmt.Println(formatSuggestionForShell(obj.URL.Absolute(), argToBeCompleted)) diff --git a/e2e/app_test.go b/e2e/app_test.go index a4eb32d11..825e740f3 100644 --- a/e2e/app_test.go +++ b/e2e/app_test.go @@ -6,8 +6,10 @@ import ( "strconv" "strings" "testing" + "time" "gotest.tools/v3/assert" + "gotest.tools/v3/fs" "gotest.tools/v3/icmd" ) @@ -227,3 +229,86 @@ func TestInvalidLoglevel(t *testing.T) { 1: equals("See 's5cmd --help' for usage"), }) } + +func TestCompletionFlag(t *testing.T) { + t.Parallel() + + flag := "--generate-bash-completion" + testcases := []struct { + name string + precedingArgs []string + arg string + expected []string + env []string + }{ + { + name: "cp complete empty string", + precedingArgs: []string{"cp"}, + arg: "s3://", + expected: []string{}, + env: []string{"/bin/bash"}, + }, + } + _, _ = testcases, flag + + workdir := fs.NewDir(t, "completionTest", + fs.WithFiles( + map[string]string{ + "dif": "content", + "root1": "content", + "root2": "content", + }, + ), + fs.WithDir("dir", fs.WithFiles( + map[string]string{ + "root1": "content", + "root2": "content", + }, + )), + ) + defer workdir.Remove() + + bucket := s3BucketFromTestName(t) + + s3client, s5cmd, cleanup := setup(t) + defer cleanup() + + // prepare remote bucket content + createBucket(t, s3client, bucket) + + remoteFiles := []string{ + "file0.txt", + "file1.txt", + "filedir/child.txt", + "dir/child.txt", + "co:lon:in:key", + "as*terisk", + "as*oburiks", + `back\slash`, + `backback`, + "qu?estion", + "qu?vestion", + } + + for _, f := range remoteFiles { + putFile(t, s3client, bucket, f, "content") + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) + cmd.Dir = workdir.Path() + cmd.Env = append(cmd.Env, fmt.Sprintf("SHELL=%v", "/bin/zsh")) + result := icmd.RunCmd(cmd) + os.Getenv("SHELL") + fmt.Println(os.Getenv("SHELL"), result.String(), "§", result.Stdout()) + + time.Sleep(40 * time.Second) + assert.Assert(t, false) + + }) + } +} From c892c5f861e54fd330af7c166a2cde9abe4e7478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 31 Aug 2022 18:23:18 +0300 Subject: [PATCH 18/34] add tests for autocompletion --- e2e/app_test.go | 135 ++++++++++++++++++++++++++++++++--------------- e2e/util_test.go | 43 +++++++++++++++ 2 files changed, 136 insertions(+), 42 deletions(-) diff --git a/e2e/app_test.go b/e2e/app_test.go index 825e740f3..0261a9c40 100644 --- a/e2e/app_test.go +++ b/e2e/app_test.go @@ -6,7 +6,6 @@ import ( "strconv" "strings" "testing" - "time" "gotest.tools/v3/assert" "gotest.tools/v3/fs" @@ -231,25 +230,98 @@ func TestInvalidLoglevel(t *testing.T) { } func TestCompletionFlag(t *testing.T) { - t.Parallel() - flag := "--generate-bash-completion" + bucket := s3BucketFromTestName(t) + testcases := []struct { name string precedingArgs []string arg string + remoteFiles []string expected []string - env []string + shell string }{ { name: "cp complete empty string", precedingArgs: []string{"cp"}, - arg: "s3://", + arg: "", expected: []string{}, - env: []string{"/bin/bash"}, + shell: "/bin/bash", + }, + { + name: "cp complete bucket names", + precedingArgs: []string{"cp"}, + arg: "s3://", + expected: []string{"s3://" + bucket + "/"}, + shell: "/bin/pwsh", + }, + { + name: "cp complete bucket keys pwsh", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/", + remoteFiles: []string{ + "file0.txt", + "file1.txt", + "filedir/child.txt", + "dir/child.txt", + }, + expected: keysToS3URL("s3://", bucket, + "file0.txt", + "file1.txt", + "filedir/", + "dir/", + ), + shell: "/bin/pwsh", + }, { + name: "cp complete keys with colon", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/co:lo", + remoteFiles: []string{ + "co:lon:in:key", + "co:lonized", + }, + expected: append( + keysToS3URL("s3://", bucket, "co:lon:in:key", "co:lonized"), + "lon:in:key", "lonized"), + shell: "/bin/bash", + }, { + name: "cp complete keys with asterisk", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/as*", + remoteFiles: []string{ + "as*terisk", + "as*oburiks", + }, + expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), + shell: "/bin/pwsh", + }, + /* Question marks are thought to be wildcard so they cannot be properly handled yet + { + name: "cp complete keys with question mark", + precedingArgs: []string{"cp", "--raw"}, + arg: "s3://" + bucket + "/qu?", + remoteFiles: []string{ + "qu?estion", + "qu?vestion", + }, + expected: keysToS3URL("s3://", bucket, + "qu?estion", "qu?vestion"), + shell: "/bin/pwsh", + }, + */ + { + name: "cp complete keys with backslash", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/back\\", + remoteFiles: []string{ + `back\slash`, + `backback`, + }, + expected: keysToS3URL("s3://", bucket, + `back\slash`), + shell: "/bin/pwsh", }, } - _, _ = testcases, flag workdir := fs.NewDir(t, "completionTest", fs.WithFiles( @@ -266,49 +338,28 @@ func TestCompletionFlag(t *testing.T) { }, )), ) - defer workdir.Remove() - - bucket := s3BucketFromTestName(t) - - s3client, s5cmd, cleanup := setup(t) - defer cleanup() - - // prepare remote bucket content - createBucket(t, s3client, bucket) - - remoteFiles := []string{ - "file0.txt", - "file1.txt", - "filedir/child.txt", - "dir/child.txt", - "co:lon:in:key", - "as*terisk", - "as*oburiks", - `back\slash`, - `backback`, - "qu?estion", - "qu?vestion", - } - - for _, f := range remoteFiles { - putFile(t, s3client, bucket, f, "content") - } for _, tc := range testcases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() + os.Setenv("SHELL", tc.shell) - cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) - cmd.Dir = workdir.Path() - cmd.Env = append(cmd.Env, fmt.Sprintf("SHELL=%v", "/bin/zsh")) - result := icmd.RunCmd(cmd) - os.Getenv("SHELL") - fmt.Println(os.Getenv("SHELL"), result.String(), "§", result.Stdout()) + s3client, s5cmd, cleanup := setup(t) + defer cleanup() - time.Sleep(40 * time.Second) - assert.Assert(t, false) + // prepare remote bucket content + createBucket(t, s3client, bucket) + + for _, f := range tc.remoteFiles { + putFile(t, s3client, bucket, f, "content") + } + + cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) + result := icmd.RunCmd(cmd, withWorkingDir(workdir), withEnv("SHELL", tc.shell)) + fmt.Println(tc.name, "§", os.Getenv("SHELL"), "$", result.Stdout(), "$") + assertLines(t, result.Stdout(), expectedSliceToEqualsMap(tc.expected, true), sortInput(true)) }) } } diff --git a/e2e/util_test.go b/e2e/util_test.go index b4877bf40..56f7a077e 100644 --- a/e2e/util_test.go +++ b/e2e/util_test.go @@ -395,6 +395,16 @@ func withWorkingDir(dir *fs.Dir) func(*icmd.Cmd) { } } +func withEnv(key, value string) func(*icmd.Cmd) { + return func(cmd *icmd.Cmd) { + if i := indexSlice(cmd.Env, key+"=", strings.HasPrefix); i > 0 { + cmd.Env[i] = key + "=" + value + } else { + cmd.Env = append(cmd.Env, key+"="+value) + } + } +} + type compareFunc func(string) error type assertOpts struct { @@ -672,3 +682,36 @@ func (l *fixedTimeSource) Advance(by time.Duration) { l.time = l.time.Add(by) } + +func keysToS3URL(scheme, bucket string, keys ...string) []string { + l := len(keys) + res := make([]string, 0, l) + + for _, k := range keys { + res = append(res, scheme+bucket+"/"+k) + } + + return res +} + +func expectedSliceToEqualsMap(expected []string, shouldSort bool) map[int]compareFunc { + m := make(map[int]compareFunc) + + if shouldSort { + sort.Strings(expected) + } + + for i, e := range expected { + m[i] = equals(e) + } + return m +} + +func indexSlice(slice []string, target string, fn func(str, target string) bool) int { + for i, str := range slice { + if fn(str, target) { + return i + } + } + return -1 +} From 17f705d2355e8a3499810a7796432d351236ec99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 31 Aug 2022 19:03:57 +0300 Subject: [PATCH 19/34] improve suggestions upgrade urfave/cli to v2.11.2 to match #495 --- command/auto_complete.go | 41 ++++++++-------------- command/cat.go | 2 +- command/cp.go | 2 +- command/du.go | 2 +- command/ls.go | 2 +- command/mv.go | 2 +- command/rb.go | 2 +- command/rm.go | 2 +- command/run.go | 5 +-- command/select.go | 2 +- command/sync.go | 2 +- e2e/app_test.go | 18 +++++++++- go.mod | 2 +- go.sum | 4 +-- vendor/github.com/urfave/cli/v2/app.go | 32 +++++++++-------- vendor/github.com/urfave/cli/v2/command.go | 8 +++-- vendor/modules.txt | 2 +- 17 files changed, 70 insertions(+), 60 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index b6e87aae0..9b9c97f9f 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -82,7 +82,8 @@ Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { } ` -func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { +func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(ctx *cli.Context) { + isOnlyRemote = isOnlyRemote || isOnlyBucket return func(ctx *cli.Context) { var arg string args := ctx.Args() @@ -102,30 +103,18 @@ func getBashCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { arg = strings.TrimPrefix(arg, "\"") } - if strings.HasPrefix(arg, "s3://") { - printS3Suggestions(ctx, arg) - } else { - cli.DefaultCompleteWithFlags(cmd)(ctx) - } - } -} - -func getRemoteCompleteFn(cmd *cli.Command) func(ctx *cli.Context) { - return func(ctx *cli.Context) { - var arg string - args := ctx.Args() - l := args.Len() - - if l > 0 { - arg = args.Get(l - 1) + if isOnlyRemote || strings.HasPrefix(arg, "s3://") { + if strings.HasPrefix(arg, "-") { + cli.DefaultCompleteWithFlags(cmd)(ctx) + return + } else if !strings.HasPrefix(arg, "s3://") { + arg = "s3://" + } + printS3Suggestions(ctx, arg, isOnlyBucket) + return } - if strings.HasPrefix(arg, "-") { - cli.DefaultCompleteWithFlags(cmd)(ctx) - } else if !strings.HasPrefix(arg, "s3://") { - arg = "s3://" - } - printS3Suggestions(ctx, arg) + cli.DefaultCompleteWithFlags(cmd)(ctx) } } @@ -150,7 +139,7 @@ func ineffectiveCompleteFnWithDefault(cmd *cli.Command, defaultCompletions ...st } } -func printS3Suggestions(ctx *cli.Context, arg string) { +func printS3Suggestions(ctx *cli.Context, arg string, isOnlyBucket bool) { c := ctx.Context u, err := url.New(arg) if err != nil { @@ -163,10 +152,10 @@ func printS3Suggestions(ctx *cli.Context, arg string) { return } - if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) { + if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) || isOnlyBucket { printListBuckets(c, client, u, arg) } else { - printListNURLSuggestions(c, client, u, 13, arg) + printListNURLSuggestions(c, client, u, 20, arg) } } diff --git a/command/cat.go b/command/cat.go index f19330ff3..2a3b1242c 100644 --- a/command/cat.go +++ b/command/cat.go @@ -60,7 +60,7 @@ func NewCatCommand() *cli.Command { }.Run(c.Context) }, } - cmd.BashComplete = getRemoteCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, true, false) return cmd } diff --git a/command/cp.go b/command/cp.go index 403f56796..b6b766d65 100644 --- a/command/cp.go +++ b/command/cp.go @@ -234,7 +234,7 @@ func NewCopyCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, false, false) return cmd } diff --git a/command/du.go b/command/du.go index c188d52a9..380ab47c1 100644 --- a/command/du.go +++ b/command/du.go @@ -81,7 +81,7 @@ func NewSizeCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, false, false) return cmd } diff --git a/command/ls.go b/command/ls.go index 8407d6793..2f1183301 100644 --- a/command/ls.go +++ b/command/ls.go @@ -106,7 +106,7 @@ func NewListCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, false, false) return cmd } diff --git a/command/mv.go b/command/mv.go index dfc07bcd1..0693fd3da 100644 --- a/command/mv.go +++ b/command/mv.go @@ -56,6 +56,6 @@ func NewMoveCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, false, false) return cmd } diff --git a/command/rb.go b/command/rb.go index 37b4b692f..db4723cd1 100644 --- a/command/rb.go +++ b/command/rb.go @@ -51,7 +51,7 @@ func NewRemoveBucketCommand() *cli.Command { }, } - cmd.BashComplete = getRemoteCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, true, true) return cmd } diff --git a/command/rm.go b/command/rm.go index 02c2479e7..1f193b0a5 100644 --- a/command/rm.go +++ b/command/rm.go @@ -79,7 +79,7 @@ func NewDeleteCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, false, false) return cmd } diff --git a/command/run.go b/command/run.go index 6047f2ab3..0b1774823 100644 --- a/command/run.go +++ b/command/run.go @@ -34,7 +34,7 @@ Examples: ` func NewRunCommand() *cli.Command { - cmd := &cli.Command{ + return &cli.Command{ Name: "run", HelpName: "run", Usage: "run commands in batch", @@ -62,9 +62,6 @@ func NewRunCommand() *cli.Command { return NewRun(c, reader).Run(c.Context) }, } - - cmd.BashComplete = getBashCompleteFn(cmd) - return cmd } type Run struct { diff --git a/command/select.go b/command/select.go index d69083330..99f4cd5bd 100644 --- a/command/select.go +++ b/command/select.go @@ -95,7 +95,7 @@ func NewSelectCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, true, false) return cmd } diff --git a/command/sync.go b/command/sync.go index b68ad1d11..c35f27c85 100644 --- a/command/sync.go +++ b/command/sync.go @@ -98,7 +98,7 @@ func NewSyncCommand() *cli.Command { }, } - cmd.BashComplete = getBashCompleteFn(cmd) + cmd.BashComplete = getBashCompleteFn(cmd, false, false) return cmd } diff --git a/e2e/app_test.go b/e2e/app_test.go index dafbf4fbb..1cc417b1c 100644 --- a/e2e/app_test.go +++ b/e2e/app_test.go @@ -317,6 +317,23 @@ func TestCompletionFlag(t *testing.T) { "dir/", ), shell: "/bin/pwsh", + }, { + name: "cp complete bucket keys zsh", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/", + remoteFiles: []string{ + "file0.txt", + "file1.txt", + "filedir/child.txt", + "dir/child.txt", + }, + expected: keysToS3URL("s3\\://", bucket, + "file0.txt", + "file1.txt", + "filedir/", + "dir/", + ), + shell: "/bin/zsh", }, { name: "cp complete keys with colon", precedingArgs: []string{"cp"}, @@ -401,7 +418,6 @@ func TestCompletionFlag(t *testing.T) { cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) result := icmd.RunCmd(cmd, withWorkingDir(workdir), withEnv("SHELL", tc.shell)) - fmt.Println(tc.name, "§", os.Getenv("SHELL"), "$", result.Stdout(), "$") assertLines(t, result.Stdout(), expectedSliceToEqualsMap(tc.expected, true), sortInput(true)) diff --git a/go.mod b/go.mod index 018881e05..58078c665 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/stretchr/testify v1.4.0 github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae - github.com/urfave/cli/v2 v2.11.1 + github.com/urfave/cli/v2 v2.11.2 gotest.tools/v3 v3.0.2 ) diff --git a/go.sum b/go.sum index 02de7808b..b9bf1cdf8 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae h1:vgGSvdW5Lqg+I1aZOlG32uyE6xHpLdKhZzcTEktz5wM= github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae/go.mod h1:quDq6Se6jlGwiIKia/itDZxqC5rj6/8OdFyMMAwTxCs= -github.com/urfave/cli/v2 v2.11.1 h1:UKK6SP7fV3eKOefbS87iT9YHefv7iB/53ih6e+GNAsE= -github.com/urfave/cli/v2 v2.11.1/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= +github.com/urfave/cli/v2 v2.11.2 h1:FVfNg4m3vbjbBpLYxW//WjxUoHvJ9TlppXcqY9Q9ZfA= +github.com/urfave/cli/v2 v2.11.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= diff --git a/vendor/github.com/urfave/cli/v2/app.go b/vendor/github.com/urfave/cli/v2/app.go index 7e64c2d9f..0b3f45cfd 100644 --- a/vendor/github.com/urfave/cli/v2/app.go +++ b/vendor/github.com/urfave/cli/v2/app.go @@ -275,7 +275,9 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { cCtx := NewContext(a, set, &Context{Context: ctx}) if nerr != nil { _, _ = fmt.Fprintln(a.Writer, nerr) - _ = ShowAppHelp(cCtx) + if !a.HideHelp { + _ = ShowAppHelp(cCtx) + } return nerr } cCtx.shellComplete = shellComplete @@ -296,10 +298,24 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { fmt.Fprintf(a.Writer, suggestion) } } - _ = ShowAppHelp(cCtx) + if !a.HideHelp { + _ = ShowAppHelp(cCtx) + } return err } + if a.After != nil { + defer func() { + if afterErr := a.After(cCtx); afterErr != nil { + if err != nil { + err = newMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + if !a.HideHelp && checkHelp(cCtx) { _ = ShowAppHelp(cCtx) return nil @@ -316,18 +332,6 @@ func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { return cerr } - if a.After != nil { - defer func() { - if afterErr := a.After(cCtx); afterErr != nil { - if err != nil { - err = newMultiError(err, afterErr) - } else { - err = afterErr - } - } - }() - } - if a.Before != nil { beforeErr := a.Before(cCtx) if beforeErr != nil { diff --git a/vendor/github.com/urfave/cli/v2/command.go b/vendor/github.com/urfave/cli/v2/command.go index 2cafd8e0e..13b79de46 100644 --- a/vendor/github.com/urfave/cli/v2/command.go +++ b/vendor/github.com/urfave/cli/v2/command.go @@ -125,7 +125,9 @@ func (c *Command) Run(ctx *Context) (err error) { fmt.Fprintf(cCtx.App.Writer, suggestion) } } - _ = ShowCommandHelp(cCtx, c.Name) + if !c.HideHelp { + _ = ShowCommandHelp(cCtx, c.Name) + } return err } @@ -135,7 +137,9 @@ func (c *Command) Run(ctx *Context) (err error) { cerr := cCtx.checkRequiredFlags(c.Flags) if cerr != nil { - _ = ShowCommandHelp(cCtx, c.Name) + if !c.HideHelp { + _ = ShowCommandHelp(cCtx, c.Name) + } return cerr } diff --git a/vendor/modules.txt b/vendor/modules.txt index 379277d05..beee4ae71 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -119,7 +119,7 @@ github.com/stretchr/testify/mock # github.com/termie/go-shutil v0.0.0-20140729215957-bcacb06fecae ## explicit github.com/termie/go-shutil -# github.com/urfave/cli/v2 v2.11.1 +# github.com/urfave/cli/v2 v2.11.2 ## explicit; go 1.18 github.com/urfave/cli/v2 # github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 From 1eefa33938b3325568563c237e281f2f40e0c63c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 1 Sep 2022 11:22:56 +0300 Subject: [PATCH 20/34] move auto_complete tests to another file, add zsh&bash variants for some cases --- e2e/app_test.go | 152 ------------------------------ e2e/auto_complete_test.go | 192 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 152 deletions(-) create mode 100644 e2e/auto_complete_test.go diff --git a/e2e/app_test.go b/e2e/app_test.go index 1cc417b1c..428d44d82 100644 --- a/e2e/app_test.go +++ b/e2e/app_test.go @@ -8,7 +8,6 @@ import ( "testing" "gotest.tools/v3/assert" - "gotest.tools/v3/fs" "gotest.tools/v3/icmd" ) @@ -273,154 +272,3 @@ func TestAppEndpointShouldHaveScheme(t *testing.T) { }) } } - -func TestCompletionFlag(t *testing.T) { - flag := "--generate-bash-completion" - bucket := s3BucketFromTestName(t) - - testcases := []struct { - name string - precedingArgs []string - arg string - remoteFiles []string - expected []string - shell string - }{ - { - name: "cp complete empty string", - precedingArgs: []string{"cp"}, - arg: "", - expected: []string{}, - shell: "/bin/bash", - }, - { - name: "cp complete bucket names", - precedingArgs: []string{"cp"}, - arg: "s3://", - expected: []string{"s3://" + bucket + "/"}, - shell: "/bin/pwsh", - }, - { - name: "cp complete bucket keys pwsh", - precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/", - remoteFiles: []string{ - "file0.txt", - "file1.txt", - "filedir/child.txt", - "dir/child.txt", - }, - expected: keysToS3URL("s3://", bucket, - "file0.txt", - "file1.txt", - "filedir/", - "dir/", - ), - shell: "/bin/pwsh", - }, { - name: "cp complete bucket keys zsh", - precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/", - remoteFiles: []string{ - "file0.txt", - "file1.txt", - "filedir/child.txt", - "dir/child.txt", - }, - expected: keysToS3URL("s3\\://", bucket, - "file0.txt", - "file1.txt", - "filedir/", - "dir/", - ), - shell: "/bin/zsh", - }, { - name: "cp complete keys with colon", - precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/co:lo", - remoteFiles: []string{ - "co:lon:in:key", - "co:lonized", - }, - expected: append( - keysToS3URL("s3://", bucket, "co:lon:in:key", "co:lonized"), - "lon:in:key", "lonized"), - shell: "/bin/bash", - }, { - name: "cp complete keys with asterisk", - precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/as*", - remoteFiles: []string{ - "as*terisk", - "as*oburiks", - }, - expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), - shell: "/bin/pwsh", - }, - /* Question marks are thought to be wildcard so they cannot be properly handled yet - { - name: "cp complete keys with question mark", - precedingArgs: []string{"cp", "--raw"}, - arg: "s3://" + bucket + "/qu?", - remoteFiles: []string{ - "qu?estion", - "qu?vestion", - }, - expected: keysToS3URL("s3://", bucket, - "qu?estion", "qu?vestion"), - shell: "/bin/pwsh", - }, - */ - { - name: "cp complete keys with backslash", - precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/back\\", - remoteFiles: []string{ - `back\slash`, - `backback`, - }, - expected: keysToS3URL("s3://", bucket, - `back\slash`), - shell: "/bin/pwsh", - }, - } - - workdir := fs.NewDir(t, "completionTest", - fs.WithFiles( - map[string]string{ - "dif": "content", - "root1": "content", - "root2": "content", - }, - ), - fs.WithDir("dir", fs.WithFiles( - map[string]string{ - "root1": "content", - "root2": "content", - }, - )), - ) - - for _, tc := range testcases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - os.Setenv("SHELL", tc.shell) - - s3client, s5cmd := setup(t) - - // prepare remote bucket content - createBucket(t, s3client, bucket) - - for _, f := range tc.remoteFiles { - putFile(t, s3client, bucket, f, "content") - } - - cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) - result := icmd.RunCmd(cmd, withWorkingDir(workdir), withEnv("SHELL", tc.shell)) - - assertLines(t, result.Stdout(), expectedSliceToEqualsMap(tc.expected, true), sortInput(true)) - - }) - } -} diff --git a/e2e/auto_complete_test.go b/e2e/auto_complete_test.go new file mode 100644 index 000000000..db4e13a93 --- /dev/null +++ b/e2e/auto_complete_test.go @@ -0,0 +1,192 @@ +package e2e + +import ( + "testing" + + "gotest.tools/v3/icmd" +) + +func TestCompletionFlag(t *testing.T) { + flag := "--generate-bash-completion" + bucket := s3BucketFromTestName(t) + + testcases := []struct { + name string + precedingArgs []string + arg string + remoteFiles []string + expected []string + shell string + }{ + { + name: "cp complete empty string", + precedingArgs: []string{"cp"}, + arg: "", + // local file completions are prepared by the shell + expected: []string{}, + shell: "/bin/bash", + }, + { + name: "cp complete bucket names in pwsh", + precedingArgs: []string{"cp"}, + arg: "s3://", + expected: []string{"s3://" + bucket + "/"}, + shell: "/bin/pwsh", + }, + { + name: "cp complete bucket names in zsh", + precedingArgs: []string{"cp"}, + arg: "s3://", + expected: []string{"s3\\://" + bucket + "/"}, + shell: "/bin/zsh", + }, + { + name: "cp complete bucket names in bash", + precedingArgs: []string{"cp"}, + arg: "s3://", + expected: []string{"s3://" + bucket + "/", "//" + bucket + "/"}, + shell: "/bin/bash", + }, + { + name: "cp complete bucket keys pwsh", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/", + remoteFiles: []string{ + "file0.txt", + "file1.txt", + "filedir/child.txt", + "dir/child.txt", + }, + expected: keysToS3URL("s3://", bucket, + "file0.txt", + "file1.txt", + "filedir/", + "dir/", + ), + shell: "/bin/pwsh", + }, + { + name: "cp complete bucket keys bash", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/", + remoteFiles: []string{ + "file0.txt", + "file1.txt", + "filedir/child.txt", + "dir/child.txt", + }, + expected: append( + keysToS3URL("s3://", bucket, + "file0.txt", + "file1.txt", + "filedir/", + "dir/"), + keysToS3URL("//", bucket, + "file0.txt", + "file1.txt", + "filedir/", + "dir/")...), + shell: "/bin/bash", + }, + { + name: "cp complete bucket keys zsh", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/", + remoteFiles: []string{ + "file0.txt", + "file1.txt", + "filedir/child.txt", + "dir/child.txt", + }, + expected: keysToS3URL("s3\\://", bucket, + "file0.txt", + "file1.txt", + "filedir/", + "dir/", + ), + shell: "/bin/zsh", + }, + { + name: "cp complete keys with colon bash", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/co:lo", + remoteFiles: []string{ + "co:lon:in:key", + "co:lonized", + }, + expected: append( + keysToS3URL("s3://", bucket, "co:lon:in:key", "co:lonized"), + "lon:in:key", "lonized"), + shell: "/bin/bash", + }, + { + name: "cp complete keys with colon zsh", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/co:lo", + remoteFiles: []string{ + "co:lon:in:key", + "co:lonized", + }, + expected: keysToS3URL("s3\\://", bucket, "co\\:lon\\:in\\:key", "co\\:lonized"), + shell: "/bin/zsh", + }, + { + name: "cp complete keys with asterisk", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/as*", + remoteFiles: []string{ + "as*terisk", + "as*oburiks", + }, + expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), + shell: "/bin/pwsh", + }, + /* Question marks are thought to be wildcard by the s5cmd so they cannot be properly handled yet + { + name: "cp complete keys with question mark", + precedingArgs: []string{"cp", "--raw"}, + arg: "s3://" + bucket + "/qu?", + remoteFiles: []string{ + "qu?estion", + "qu?vestion", + }, + expected: keysToS3URL("s3://", bucket, + "qu?estion", "qu?vestion"), + shell: "/bin/pwsh", + }, + */ + { + name: "cp complete keys with backslash", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/back\\", + remoteFiles: []string{ + `back\slash`, + `backback`, + }, + expected: keysToS3URL("s3://", bucket, + `back\slash`), + shell: "/bin/pwsh", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + s3client, s5cmd := setup(t) + + // prepare remote bucket content + createBucket(t, s3client, bucket) + + for _, f := range tc.remoteFiles { + putFile(t, s3client, bucket, f, "content") + } + + cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) + result := icmd.RunCmd(cmd, withEnv("SHELL", tc.shell)) + + assertLines(t, result.Stdout(), expectedSliceToEqualsMap(tc.expected, true), sortInput(true)) + }) + } +} From 0e5033b1bc325bd6b36923fc4a9e9d49ad4eee8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 1 Sep 2022 12:47:43 +0300 Subject: [PATCH 21/34] hide the script comments from output --- command/auto_complete.go | 45 ++++++++++++++++++++-------------------- e2e/app_test.go | 1 + 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 9b9c97f9f..fd2422c46 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -33,34 +33,35 @@ compdef _s5cmd_cli_zsh_autocomplete s5cmd const bash = `# prepare autocompletion suggestions for s5cmd and save them to COMPREPLY array _s5cmd_cli_bash_autocomplete() { - if [[ "${COMP_WORDS[0]}" != "source" ]]; then COMPREPLY=() - local opts cur cmd - - # get current word (cur) and prepare command (cmd) + local opts cur cmd` + + // get current word (cur) and prepare command (cmd) + ` cur="${COMP_WORDS[COMP_CWORD]}" - cmd="${COMP_LINE:0:$COMP_POINT}" - - # if we want to complete the second argurment and we didn't started writing - # yet then we should pass an empty string as another argument. Otherwise - # the white spaces will be discarded and the program will make suggestions - # as if it is completing the first argument. - # shellcheck disable=SC2089,SC2090 - # Beware that the we want to pass empty string so we intentionally write - # as it is. Fixes of SC2089 and SC2090 are not what we want. - # see also https://www.shellcheck.net/wiki/SC2090 + cmd="${COMP_LINE:0:$COMP_POINT}"` + + + // if we want to complete the second argurment and we didn't started writing + // yet then we should pass an empty string as another argument. Otherwise + // the white spaces will be discarded and the program will make suggestions + // as if it is completing the first argument. + // Beware that the we want to pass empty string so we intentionally write + // as it is. Fixes of SC2089 and SC2090 are not what we want. + // see also https://www.shellcheck.net/wiki/SC2090 + ` [ "${COMP_LINE:COMP_POINT-1:$COMP_POINT}" == " " ] \ - && [ "${COMP_LINE:COMP_POINT-2:$COMP_POINT}" != '\ ' ] \ - && cmd="${cmd} \"\"" + && cmd="${cmd} \"\"" ` + + + // execute the command with '--generate-bash-completion' flag to obtain + // possible completion values for current word. + // ps. SC2090 is not wanted. + ` + opts=$($cmd --generate-bash-completion)` + - # execute the command with '--generate-bash-completion' flag to obtain - # possible completion values for current word. - # shellcheck disable=SC2090 - opts=$($cmd --generate-bash-completion) + // prepare completion array with possible values and filter those does not + // start with cur. if no completion is found then fallback to default completion of shell. + ` - # prepare completion array with possible values and filter those does not - # start with cur. if no completion is found then fallback to default completion of shell. while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -o bashdefault -o default -o nospace -W "${opts}" -- "${cur}") return 0 diff --git a/e2e/app_test.go b/e2e/app_test.go index 428d44d82..e86cd9deb 100644 --- a/e2e/app_test.go +++ b/e2e/app_test.go @@ -269,6 +269,7 @@ func TestAppEndpointShouldHaveScheme(t *testing.T) { assertLines(t, result.Stderr(), map[int]compareFunc{ 0: equals("%v", tc.expectedError), }) + }) } } From d2913e2d27eed2261521a4d77bfe7166fdf3ed7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 1 Sep 2022 16:20:43 +0300 Subject: [PATCH 22/34] correct install-completion description various improvements --- command/app.go | 2 +- command/auto_complete.go | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/command/app.go b/command/app.go index df958fbd6..b604e3952 100644 --- a/command/app.go +++ b/command/app.go @@ -60,7 +60,7 @@ var app = &cli.App{ }, &cli.BoolFlag{ Name: "install-completion", - Usage: "install completion for your shell (only avialble for bash, zsh, and fish)", + Usage: "get completion installation instructions for your shell (only available for bash, pwsh, and zsh)", }, &cli.BoolFlag{ Name: "dry-run", diff --git a/command/auto_complete.go b/command/auto_complete.go index fd2422c46..b8b89497b 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -58,8 +58,8 @@ _s5cmd_cli_bash_autocomplete() { ` opts=$($cmd --generate-bash-completion)` + - // prepare completion array with possible values and filter those does not - // start with cur. if no completion is found then fallback to default completion of shell. + // prepare completion array with possible values and filter those do not start with cur. + // if no completion is found then fallback to default completion of shell. ` while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -o bashdefault -o default -o nospace -W "${opts}" -- "${cur}") @@ -108,8 +108,6 @@ func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(c if strings.HasPrefix(arg, "-") { cli.DefaultCompleteWithFlags(cmd)(ctx) return - } else if !strings.HasPrefix(arg, "s3://") { - arg = "s3://" } printS3Suggestions(ctx, arg, isOnlyBucket) return @@ -173,14 +171,13 @@ func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argTo } func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int, argToBeCompleted string) { - abs := u.Absolute() if u.IsBucket() { - abs = abs + "/" - } - u, err := url.New(abs) - if err != nil { - fmt.Fprintln(os.Stderr, err) - return + var err error + u, err = url.New(u.Absolute() + "/") + if err != nil { + fmt.Fprintln(os.Stderr, err) + return + } } i := 0 @@ -230,12 +227,12 @@ func formatSuggestionForShell(suggestion, argToBeCompleted string) string { baseShell := filepath.Base(os.Getenv("SHELL")) if i := strings.LastIndex(argToBeCompleted, ":"); i >= 0 && baseShell == "bash" { - // write the original suggestion in case that the argToBeCompleted was quoted. + // write the original suggestion in case that COMP_WORDBREAKS does not contain : + // or that the argToBeCompleted was quoted. // Bash doesn't split on : when argument is quoted even if : is in COMP_WORDBREAKS fmt.Println(suggestion) prefix = argToBeCompleted[0 : i+1] } - suggestion = strings.TrimPrefix(suggestion, prefix) // replace every colon : with \: if shell is zsh From 70d05b91171cde93135baeef2ae51a50ab1eb725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 1 Sep 2022 16:44:35 +0300 Subject: [PATCH 23/34] add autoocompletion tests for the commands that only accepts remote or only bucket argument --- e2e/auto_complete_test.go | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/e2e/auto_complete_test.go b/e2e/auto_complete_test.go index db4e13a93..6392c8076 100644 --- a/e2e/auto_complete_test.go +++ b/e2e/auto_complete_test.go @@ -26,6 +26,52 @@ func TestCompletionFlag(t *testing.T) { expected: []string{}, shell: "/bin/bash", }, + { + name: "cat complete empty string", + precedingArgs: []string{"cat"}, + arg: "", + expected: []string{"s3://" + bucket + "/"}, + shell: "/bin/pwsh", + }, + { + name: "mb complete empty string", + precedingArgs: []string{"mb"}, + arg: "", + expected: []string{"s3://"}, + shell: "/bin/pwsh", + }, + { + name: "mb complete bucket", + precedingArgs: []string{"mb"}, + arg: "s3://bu", + expected: []string{"s3://bu"}, + shell: "/bin/pwsh", + }, + { + name: "rb complete empty string", + precedingArgs: []string{"rb"}, + arg: "", + expected: []string{"s3://" + bucket + "/"}, + shell: "/bin/pwsh", + }, + { + name: "rb should not complete keys string", + precedingArgs: []string{"rb"}, + arg: "s3://" + bucket + "/f", + expected: []string{"s3://" + bucket + "/"}, + remoteFiles: []string{ + "file.txt", + "fdir/child.txt", + }, + shell: "/bin/pwsh", + }, + { + name: "select complete empty string", + precedingArgs: []string{"select"}, + arg: "", + expected: []string{"s3://" + bucket + "/"}, + shell: "/bin/pwsh", + }, { name: "cp complete bucket names in pwsh", precedingArgs: []string{"cp"}, From 6c72db57f381567952601ece8b6771c4b5c85fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Mon, 5 Sep 2022 14:19:15 +0300 Subject: [PATCH 24/34] don't print the errors encountered during autocompletion --- command/auto_complete.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index b8b89497b..0750418be 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -147,7 +147,6 @@ func printS3Suggestions(ctx *cli.Context, arg string, isOnlyBucket bool) { client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) if err != nil { - fmt.Fprintln(os.Stderr, err) return } @@ -161,7 +160,6 @@ func printS3Suggestions(ctx *cli.Context, arg string, isOnlyBucket bool) { func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argToBeCompleted string) { buckets, err := client.ListBuckets(ctx, u.Bucket) if err != nil { - fmt.Fprintln(os.Stderr, err) return } @@ -175,7 +173,6 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR var err error u, err = url.New(u.Absolute() + "/") if err != nil { - fmt.Fprintln(os.Stderr, err) return } } @@ -186,7 +183,6 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR break } if obj.Err != nil { - fmt.Fprintln(os.Stderr, obj.Err) return } fmt.Println(formatSuggestionForShell(obj.URL.Absolute(), argToBeCompleted)) From f395b819273300ce83d66199a5e71e52fc045400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Fri, 9 Sep 2022 14:20:42 +0300 Subject: [PATCH 25/34] apply suggestions from code review Co-Authored-By: Aykut Farsak --- command/auto_complete.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 0750418be..b70d4d2c0 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -41,11 +41,11 @@ _s5cmd_cli_bash_autocomplete() { cur="${COMP_WORDS[COMP_CWORD]}" cmd="${COMP_LINE:0:$COMP_POINT}"` + - // if we want to complete the second argurment and we didn't started writing + // if we want to complete the second argument and we didn't start writing // yet then we should pass an empty string as another argument. Otherwise // the white spaces will be discarded and the program will make suggestions // as if it is completing the first argument. - // Beware that the we want to pass empty string so we intentionally write + // Beware that we want to pass an empty string so we intentionally write // as it is. Fixes of SC2089 and SC2090 are not what we want. // see also https://www.shellcheck.net/wiki/SC2090 ` @@ -77,9 +77,9 @@ $name = $fn -replace "(.*)\.ps1$", '$1' Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { param($commandName, $wordToComplete, $cursorPosition) $other = "$wordToComplete --generate-bash-completion" - Invoke-Expression $other | ForEach-Object { + Invoke-Expression $other | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) - } + } } ` From a4e6ce7914a7b9d01c67371c2b1d2c1f8d5a0002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Mon, 12 Sep 2022 15:25:35 +0300 Subject: [PATCH 26/34] apply suggestions from code review --- command/app.go | 3 +-- command/auto_complete.go | 17 +++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/command/app.go b/command/app.go index b604e3952..fde508aac 100644 --- a/command/app.go +++ b/command/app.go @@ -155,8 +155,7 @@ var app = &cli.App{ Action: func(c *cli.Context) error { if c.Bool("install-completion") { shell := os.Getenv("SHELL") - installCompletionHelp(shell) - + printAutocompletionInstructions(shell) return nil } args := c.Args() diff --git a/command/auto_complete.go b/command/auto_complete.go index b70d4d2c0..5f53cdd8c 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -62,7 +62,11 @@ _s5cmd_cli_bash_autocomplete() { // if no completion is found then fallback to default completion of shell. ` - while IFS='' read -r line; do COMPREPLY+=("$line"); done < <(compgen -o bashdefault -o default -o nospace -W "${opts}" -- "${cur}") + while IFS='' read -r line; + do + COMPREPLY+=("$line"); + done \ + < <(compgen -o bashdefault -o default -o nospace -W "${opts}" -- "${cur}") return 0 fi @@ -190,25 +194,26 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR } } -func installCompletionHelp(shell string) { +func printAutocompletionInstructions(shell string) { var script string baseShell := filepath.Base(shell) instructions := "# To enable autocompletion you should add the following script" + " to startup scripts of your shell.\n" + "# It is probably located at ~/." + baseShell + "rc" - if baseShell == "zsh" { + switch baseShell { + case "zsh": script = zsh - } else if baseShell == "bash" { + case "bash": script = bash - } else if baseShell == "pwsh" { + case "pwsh": script = pwsh instructions = "# To enable autocompletion you should save the following" + " script to a file named \"s5cmd.ps1\" and execute it.\n# To persist it" + " you should add the path of \"s5cmd.ps1\" file to profile file " + "(which you can locate with $profile) to automatically execute \"s5cmd.ps1\"" + " on every shell start up." - } else { + default: instructions = "# We couldn't recognize your SHELL \"" + baseShell + "\".\n" + "# Shell completion is supported only for bash, pwsh and zsh." + "# Make sure that your SHELL environment variable is set accurately." From 7e37c98170c76e9816b44bbf8a7c0790916f0b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Tue, 13 Sep 2022 15:45:55 +0300 Subject: [PATCH 27/34] apply suggestions from code review format method switch seperate PR for filter regex see storage/url: add nil check for URL.filterRegex in URL.Match #511 partially seperate completion from cli package pass shell variable through methods --- command/auto_complete.go | 131 ++++++++++++++++++++------------------ command/mb.go | 2 +- e2e/auto_complete_test.go | 52 +++++++++------ storage/url/url.go | 4 -- 4 files changed, 100 insertions(+), 89 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 5f53cdd8c..9a536c8df 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -50,7 +50,7 @@ _s5cmd_cli_bash_autocomplete() { // see also https://www.shellcheck.net/wiki/SC2090 ` [ "${COMP_LINE:COMP_POINT-1:$COMP_POINT}" == " " ] \ - && cmd="${cmd} \"\"" ` + + && cmd="${cmd} ''" ` + // execute the command with '--generate-bash-completion' flag to obtain // possible completion values for current word. @@ -90,70 +90,50 @@ Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(ctx *cli.Context) { isOnlyRemote = isOnlyRemote || isOnlyBucket return func(ctx *cli.Context) { - var arg string - args := ctx.Args() - l := args.Len() - - if l > 0 { - arg = args.Get(l - 1) - } - - // argument may start with a quotation mark, in this case we want to trim that before - // checking if it has prefix s3:// - // Beware that we only want to trim the first char, not all of the leading - // quotation marks, because those quotation marks may be actual charactes. - if strings.HasPrefix(arg, "'") { - arg = strings.TrimPrefix(arg, "'") - } else { - arg = strings.TrimPrefix(arg, "\"") - } + arg := parseArgumentToComplete(ctx) if isOnlyRemote || strings.HasPrefix(arg, "s3://") { if strings.HasPrefix(arg, "-") { cli.DefaultCompleteWithFlags(cmd)(ctx) return } - printS3Suggestions(ctx, arg, isOnlyBucket) + + u, err := url.New(arg) + if err != nil { + u = &url.URL{Type: 0, Scheme: "s3"} + } + + c := ctx.Context + client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) + if err != nil { + return + } + + printS3Suggestions(c, client, u, arg, isOnlyBucket) return } - - cli.DefaultCompleteWithFlags(cmd)(ctx) } } // it returns a complete function which prints the argument, itself, which is to be completed. // If the argument is empty string it uses the defaultCompletions to make suggestions. -func ineffectiveCompleteFnWithDefault(cmd *cli.Command, defaultCompletions ...string) func(ctx *cli.Context) { +func constantCompleteFnWithDefault(cmd *cli.Command, defaultCompletions ...string) func(ctx *cli.Context) { return func(ctx *cli.Context) { - var arg string - args := ctx.Args() - if args.Len() > 0 { - arg = args.Get(args.Len() - 1) - } + arg := parseArgumentToComplete(ctx) + baseShell := filepath.Base(os.Getenv("SHELL")) if arg == "" { for _, str := range defaultCompletions { - fmt.Println(formatSuggestionForShell(str, str)) + fmt.Println(formatSuggestionForShell(baseShell, str, str)) } } else if strings.HasPrefix(arg, "-") { cli.DefaultCompleteWithFlags(cmd)(ctx) } else { - fmt.Println(formatSuggestionForShell(arg, arg)) + fmt.Println(formatSuggestionForShell(baseShell, arg, arg)) } } } -func printS3Suggestions(ctx *cli.Context, arg string, isOnlyBucket bool) { - c := ctx.Context - u, err := url.New(arg) - if err != nil { - u = &url.URL{Type: 0, Scheme: "s3"} - } - - client, err := storage.NewRemoteClient(c, u, NewStorageOpts(ctx)) - if err != nil { - return - } - +func printS3Suggestions(c context.Context, client *storage.S3, u *url.URL, arg string, isOnlyBucket bool) { if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) || isOnlyBucket { printListBuckets(c, client, u, arg) } else { @@ -167,8 +147,9 @@ func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argTo return } + baseShell := filepath.Base(os.Getenv("SHELL")) for _, bucket := range buckets { - fmt.Println(formatSuggestionForShell("s3://"+bucket.Name+"/", argToBeCompleted)) + fmt.Println(formatSuggestionForShell(baseShell, "s3://"+bucket.Name+"/", argToBeCompleted)) } } @@ -181,6 +162,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR } } + baseShell := filepath.Base(os.Getenv("SHELL")) i := 0 for obj := range (*client).List(ctx, u, false) { if i > count { @@ -189,7 +171,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR if obj.Err != nil { return } - fmt.Println(formatSuggestionForShell(obj.URL.Absolute(), argToBeCompleted)) + fmt.Println(formatSuggestionForShell(baseShell, obj.URL.Absolute(), argToBeCompleted)) i++ } } @@ -223,30 +205,53 @@ func printAutocompletionInstructions(shell string) { fmt.Println(script) } -func formatSuggestionForShell(suggestion, argToBeCompleted string) string { - var prefix string - baseShell := filepath.Base(os.Getenv("SHELL")) - - if i := strings.LastIndex(argToBeCompleted, ":"); i >= 0 && baseShell == "bash" { - // write the original suggestion in case that COMP_WORDBREAKS does not contain : - // or that the argToBeCompleted was quoted. - // Bash doesn't split on : when argument is quoted even if : is in COMP_WORDBREAKS - fmt.Println(suggestion) - prefix = argToBeCompleted[0 : i+1] - } - suggestion = strings.TrimPrefix(suggestion, prefix) - - // replace every colon : with \: if shell is zsh - // colons are used as a seperator for the autocompletion script - // so "literal colons in completion must be quoted with a backslash" - // see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B - if baseShell == "zsh" { - suggestion = escapeColon(suggestion) +func formatSuggestionForShell(baseShell, suggestion, argToBeCompleted string) string { + switch baseShell { + case "bash": + var prefix string + suggestions := make([]string, 0, 2) + if i := strings.LastIndex(argToBeCompleted, ":"); i >= 0 && baseShell == "bash" { + // include the original suggestion in case that COMP_WORDBREAKS does not contain : + // or that the argToBeCompleted was quoted. + // Bash doesn't split on : when argument is quoted even if : is in COMP_WORDBREAKS + suggestions = append(suggestions, suggestion) + prefix = argToBeCompleted[0 : i+1] + } + suggestions = append(suggestions, strings.TrimPrefix(suggestion, prefix)) + return strings.Join(suggestions, "\n") + case "zsh": + // replace every colon : with \: if shell is zsh + // colons are used as a seperator for the autocompletion script + // so "literal colons in completion must be quoted with a backslash" + // see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B + return escapeColon(suggestion) + default: + return suggestion } - return suggestion } // replace every colon : with \: func escapeColon(str ...interface{}) string { return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) } + +func parseArgumentToComplete(ctx *cli.Context) string { + var arg string + args := ctx.Args() + l := args.Len() + + if l > 0 { + arg = args.Get(l - 1) + } + + // argument may start with a quotation mark, in this case we want to trim that before + // checking if it has prefix s3:// + // Beware that we only want to trim the first char, not all of the leading + // quotation marks, because those quotation marks may be actual charactes. + if strings.HasPrefix(arg, "'") { + arg = strings.TrimPrefix(arg, "'") + } else { + arg = strings.TrimPrefix(arg, "\"") + } + return arg +} diff --git a/command/mb.go b/command/mb.go index 06bb0387b..654b4f69f 100644 --- a/command/mb.go +++ b/command/mb.go @@ -51,7 +51,7 @@ func NewMakeBucketCommand() *cli.Command { }.Run(c.Context) }, } - cmd.BashComplete = ineffectiveCompleteFnWithDefault(cmd, "s3://") + cmd.BashComplete = constantCompleteFnWithDefault(cmd, "s3://") return cmd } diff --git a/e2e/auto_complete_test.go b/e2e/auto_complete_test.go index 6392c8076..fc4bb24cc 100644 --- a/e2e/auto_complete_test.go +++ b/e2e/auto_complete_test.go @@ -176,6 +176,18 @@ func TestCompletionFlag(t *testing.T) { expected: keysToS3URL("s3\\://", bucket, "co\\:lon\\:in\\:key", "co\\:lonized"), shell: "/bin/zsh", }, + { + name: "cp complete keys with backslash", + precedingArgs: []string{"cp"}, + arg: "s3://" + bucket + "/back\\", + remoteFiles: []string{ + `back\slash`, + `backback`, + }, + expected: keysToS3URL("s3://", bucket, + `back\slash`), + shell: "/bin/pwsh", + }, { name: "cp complete keys with asterisk", precedingArgs: []string{"cp"}, @@ -183,36 +195,34 @@ func TestCompletionFlag(t *testing.T) { remoteFiles: []string{ "as*terisk", "as*oburiks", + // "asNotTerisk", }, expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), shell: "/bin/pwsh", }, - /* Question marks are thought to be wildcard by the s5cmd so they cannot be properly handled yet { - name: "cp complete keys with question mark", - precedingArgs: []string{"cp", "--raw"}, - arg: "s3://" + bucket + "/qu?", - remoteFiles: []string{ - "qu?estion", - "qu?vestion", - }, - expected: keysToS3URL("s3://", bucket, - "qu?estion", "qu?vestion"), - shell: "/bin/pwsh", - }, - */ - { - name: "cp complete keys with backslash", + name: "cp complete keys with asterisk", precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/back\\", + arg: "s3://" + bucket + "/as*", remoteFiles: []string{ - `back\slash`, - `backback`, + "as*terisk", + "as*oburiks", }, - expected: keysToS3URL("s3://", bucket, - `back\slash`), - shell: "/bin/pwsh", + expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), + shell: "/bin/pwsh", }, + /* + Question marks and asterisk are thought to be wildcard (special charactes) + by the s5cmd so when they're given s5cmd's behaviour changes. + + When asterisk is given s5cmd also matches the keys with literal '*' as well as + all keys that match the URL'S regexp. So the completions with '*' accidentally include the + keys that contains '*' while the shell scripts filter out those that does not have '*'s. + + On the other hand when the question mark is given then s5cmd do not list keys + if it is the last character. Because the ? represent a single character and + it is not expanded to complete remaining of the key. + */ } for _, tc := range testcases { diff --git a/storage/url/url.go b/storage/url/url.go index f0b66b4ad..6dcb3cd85 100644 --- a/storage/url/url.go +++ b/storage/url/url.go @@ -326,10 +326,6 @@ func (u *URL) SetRelative(base *URL) { // Match reports whether if given key matches with the object. func (u *URL) Match(key string) bool { - if u.filterRegex == nil { - return false - } - if !u.filterRegex.MatchString(key) { return false } From f7bb79d353e09204ff55bc9ff0ad341962671f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Tue, 13 Sep 2022 16:22:22 +0300 Subject: [PATCH 28/34] e2e: show expected condition of testcases explicitly --- e2e/auto_complete_test.go | 144 +++++++++++++++++++++----------------- e2e/util_test.go | 24 ------- 2 files changed, 80 insertions(+), 88 deletions(-) diff --git a/e2e/auto_complete_test.go b/e2e/auto_complete_test.go index fc4bb24cc..cea368387 100644 --- a/e2e/auto_complete_test.go +++ b/e2e/auto_complete_test.go @@ -15,7 +15,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs []string arg string remoteFiles []string - expected []string + expected map[int]compareFunc shell string }{ { @@ -23,42 +23,52 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"cp"}, arg: "", // local file completions are prepared by the shell - expected: []string{}, + expected: map[int]compareFunc{}, shell: "/bin/bash", }, { name: "cat complete empty string", precedingArgs: []string{"cat"}, arg: "", - expected: []string{"s3://" + bucket + "/"}, - shell: "/bin/pwsh", + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/"), + }, + shell: "/bin/pwsh", }, { name: "mb complete empty string", precedingArgs: []string{"mb"}, arg: "", - expected: []string{"s3://"}, - shell: "/bin/pwsh", + expected: map[int]compareFunc{ + 0: equals("s3://"), + }, + shell: "/bin/pwsh", }, { name: "mb complete bucket", precedingArgs: []string{"mb"}, arg: "s3://bu", - expected: []string{"s3://bu"}, - shell: "/bin/pwsh", + expected: map[int]compareFunc{ + 0: equals("s3://bu"), + }, + shell: "/bin/pwsh", }, { name: "rb complete empty string", precedingArgs: []string{"rb"}, arg: "", - expected: []string{"s3://" + bucket + "/"}, - shell: "/bin/pwsh", + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/"), + }, + shell: "/bin/pwsh", }, { name: "rb should not complete keys string", precedingArgs: []string{"rb"}, arg: "s3://" + bucket + "/f", - expected: []string{"s3://" + bucket + "/"}, + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/"), + }, remoteFiles: []string{ "file.txt", "fdir/child.txt", @@ -69,29 +79,38 @@ func TestCompletionFlag(t *testing.T) { name: "select complete empty string", precedingArgs: []string{"select"}, arg: "", - expected: []string{"s3://" + bucket + "/"}, - shell: "/bin/pwsh", + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/"), + }, + shell: "/bin/pwsh", }, { name: "cp complete bucket names in pwsh", precedingArgs: []string{"cp"}, arg: "s3://", - expected: []string{"s3://" + bucket + "/"}, - shell: "/bin/pwsh", + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/"), + }, + shell: "/bin/pwsh", }, { name: "cp complete bucket names in zsh", precedingArgs: []string{"cp"}, arg: "s3://", - expected: []string{"s3\\://" + bucket + "/"}, - shell: "/bin/zsh", + expected: map[int]compareFunc{ + 0: equals("s3\\://test-completion-flag/"), + }, + shell: "/bin/zsh", }, { name: "cp complete bucket names in bash", precedingArgs: []string{"cp"}, arg: "s3://", - expected: []string{"s3://" + bucket + "/", "//" + bucket + "/"}, - shell: "/bin/bash", + expected: map[int]compareFunc{ + 0: equals("//test-completion-flag/"), + 1: equals("s3://test-completion-flag/"), + }, + shell: "/bin/bash", }, { name: "cp complete bucket keys pwsh", @@ -103,12 +122,12 @@ func TestCompletionFlag(t *testing.T) { "filedir/child.txt", "dir/child.txt", }, - expected: keysToS3URL("s3://", bucket, - "file0.txt", - "file1.txt", - "filedir/", - "dir/", - ), + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/dir/"), + 1: equals("s3://test-completion-flag/file0.txt"), + 2: equals("s3://test-completion-flag/file1.txt"), + 3: equals("s3://test-completion-flag/filedir/"), + }, shell: "/bin/pwsh", }, { @@ -121,17 +140,16 @@ func TestCompletionFlag(t *testing.T) { "filedir/child.txt", "dir/child.txt", }, - expected: append( - keysToS3URL("s3://", bucket, - "file0.txt", - "file1.txt", - "filedir/", - "dir/"), - keysToS3URL("//", bucket, - "file0.txt", - "file1.txt", - "filedir/", - "dir/")...), + expected: map[int]compareFunc{ + 0: equals("//test-completion-flag/dir/"), + 1: equals("//test-completion-flag/file0.txt"), + 2: equals("//test-completion-flag/file1.txt"), + 3: equals("//test-completion-flag/filedir/"), + 4: equals("s3://test-completion-flag/dir/"), + 5: equals("s3://test-completion-flag/file0.txt"), + 6: equals("s3://test-completion-flag/file1.txt"), + 7: equals("s3://test-completion-flag/filedir/"), + }, shell: "/bin/bash", }, { @@ -144,12 +162,12 @@ func TestCompletionFlag(t *testing.T) { "filedir/child.txt", "dir/child.txt", }, - expected: keysToS3URL("s3\\://", bucket, - "file0.txt", - "file1.txt", - "filedir/", - "dir/", - ), + expected: map[int]compareFunc{ + 0: equals("s3\\://test-completion-flag/dir/"), + 1: equals("s3\\://test-completion-flag/file0.txt"), + 2: equals("s3\\://test-completion-flag/file1.txt"), + 3: equals("s3\\://test-completion-flag/filedir/"), + }, shell: "/bin/zsh", }, { @@ -160,9 +178,12 @@ func TestCompletionFlag(t *testing.T) { "co:lon:in:key", "co:lonized", }, - expected: append( - keysToS3URL("s3://", bucket, "co:lon:in:key", "co:lonized"), - "lon:in:key", "lonized"), + expected: map[int]compareFunc{ + 0: equals("lon:in:key"), + 1: equals("lonized"), + 2: equals("s3://test-completion-flag/co:lon:in:key"), + 3: equals("s3://test-completion-flag/co:lonized"), + }, shell: "/bin/bash", }, { @@ -173,8 +194,11 @@ func TestCompletionFlag(t *testing.T) { "co:lon:in:key", "co:lonized", }, - expected: keysToS3URL("s3\\://", bucket, "co\\:lon\\:in\\:key", "co\\:lonized"), - shell: "/bin/zsh", + expected: map[int]compareFunc{ + 0: equals("s3\\://test-completion-flag/co\\:lon\\:in\\:key"), + 1: equals("s3\\://test-completion-flag/co\\:lonized"), + }, + shell: "/bin/zsh", }, { name: "cp complete keys with backslash", @@ -184,8 +208,9 @@ func TestCompletionFlag(t *testing.T) { `back\slash`, `backback`, }, - expected: keysToS3URL("s3://", bucket, - `back\slash`), + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/back\\slash"), + }, shell: "/bin/pwsh", }, { @@ -195,21 +220,12 @@ func TestCompletionFlag(t *testing.T) { remoteFiles: []string{ "as*terisk", "as*oburiks", - // "asNotTerisk", }, - expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), - shell: "/bin/pwsh", - }, - { - name: "cp complete keys with asterisk", - precedingArgs: []string{"cp"}, - arg: "s3://" + bucket + "/as*", - remoteFiles: []string{ - "as*terisk", - "as*oburiks", + expected: map[int]compareFunc{ + 0: equals("s3://test-completion-flag/as*oburiks"), + 1: equals("s3://test-completion-flag/as*terisk"), }, - expected: keysToS3URL("s3://", bucket, "as*terisk", "as*oburiks"), - shell: "/bin/pwsh", + shell: "/bin/pwsh", }, /* Question marks and asterisk are thought to be wildcard (special charactes) @@ -240,9 +256,9 @@ func TestCompletionFlag(t *testing.T) { } cmd := s5cmd(append(tc.precedingArgs, tc.arg, flag)...) - result := icmd.RunCmd(cmd, withEnv("SHELL", tc.shell)) + res := icmd.RunCmd(cmd, withEnv("SHELL", tc.shell)) - assertLines(t, result.Stdout(), expectedSliceToEqualsMap(tc.expected, true), sortInput(true)) + assertLines(t, res.Stdout(), tc.expected, sortInput(true)) }) } } diff --git a/e2e/util_test.go b/e2e/util_test.go index ae33ce2ee..dcbb9745b 100644 --- a/e2e/util_test.go +++ b/e2e/util_test.go @@ -682,30 +682,6 @@ func (l *fixedTimeSource) Advance(by time.Duration) { l.time = l.time.Add(by) } -func keysToS3URL(scheme, bucket string, keys ...string) []string { - l := len(keys) - res := make([]string, 0, l) - - for _, k := range keys { - res = append(res, scheme+bucket+"/"+k) - } - - return res -} - -func expectedSliceToEqualsMap(expected []string, shouldSort bool) map[int]compareFunc { - m := make(map[int]compareFunc) - - if shouldSort { - sort.Strings(expected) - } - - for i, e := range expected { - m[i] = equals(e) - } - return m -} - func indexSlice(slice []string, target string, fn func(str, target string) bool) int { for i, str := range slice { if fn(str, target) { From 3278f1a754539bef9958758100046dd333feb41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Tue, 13 Sep 2022 16:39:24 +0300 Subject: [PATCH 29/34] small bug-fix and improvements --- command/app.go | 3 +- command/auto_complete.go | 10 +++---- e2e/auto_complete_test.go | 62 +++++++++++++++++++-------------------- storage/url/url.go | 2 +- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/command/app.go b/command/app.go index fde508aac..283d843aa 100644 --- a/command/app.go +++ b/command/app.go @@ -154,8 +154,7 @@ var app = &cli.App{ }, Action: func(c *cli.Context) error { if c.Bool("install-completion") { - shell := os.Getenv("SHELL") - printAutocompletionInstructions(shell) + printAutocompletionInstructions(os.Getenv("SHELL")) return nil } args := c.Args() diff --git a/command/auto_complete.go b/command/auto_complete.go index 9a536c8df..5bd3fe9ae 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -92,12 +92,12 @@ func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(c return func(ctx *cli.Context) { arg := parseArgumentToComplete(ctx) - if isOnlyRemote || strings.HasPrefix(arg, "s3://") { - if strings.HasPrefix(arg, "-") { - cli.DefaultCompleteWithFlags(cmd)(ctx) - return - } + if strings.HasPrefix(arg, "-") { + cli.DefaultCompleteWithFlags(cmd)(ctx) + return + } + if isOnlyRemote || strings.HasPrefix(arg, "s3://") { u, err := url.New(arg) if err != nil { u = &url.URL{Type: 0, Scheme: "s3"} diff --git a/e2e/auto_complete_test.go b/e2e/auto_complete_test.go index cea368387..a0da0cee6 100644 --- a/e2e/auto_complete_test.go +++ b/e2e/auto_complete_test.go @@ -31,7 +31,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"cat"}, arg: "", expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/"), + 0: equals("s3://%s/", bucket), }, shell: "/bin/pwsh", }, @@ -58,7 +58,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"rb"}, arg: "", expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/"), + 0: equals("s3://%s/", bucket), }, shell: "/bin/pwsh", }, @@ -67,7 +67,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"rb"}, arg: "s3://" + bucket + "/f", expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/"), + 0: equals("s3://%s/", bucket), }, remoteFiles: []string{ "file.txt", @@ -80,7 +80,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"select"}, arg: "", expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/"), + 0: equals("s3://%s/", bucket), }, shell: "/bin/pwsh", }, @@ -89,7 +89,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"cp"}, arg: "s3://", expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/"), + 0: equals("s3://%s/", bucket), }, shell: "/bin/pwsh", }, @@ -98,7 +98,7 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"cp"}, arg: "s3://", expected: map[int]compareFunc{ - 0: equals("s3\\://test-completion-flag/"), + 0: equals("s3\\://%s/", bucket), }, shell: "/bin/zsh", }, @@ -107,8 +107,8 @@ func TestCompletionFlag(t *testing.T) { precedingArgs: []string{"cp"}, arg: "s3://", expected: map[int]compareFunc{ - 0: equals("//test-completion-flag/"), - 1: equals("s3://test-completion-flag/"), + 0: equals("//%s/", bucket), + 1: equals("s3://%s/", bucket), }, shell: "/bin/bash", }, @@ -123,10 +123,10 @@ func TestCompletionFlag(t *testing.T) { "dir/child.txt", }, expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/dir/"), - 1: equals("s3://test-completion-flag/file0.txt"), - 2: equals("s3://test-completion-flag/file1.txt"), - 3: equals("s3://test-completion-flag/filedir/"), + 0: equals("s3://%s/dir/", bucket), + 1: equals("s3://%s/file0.txt", bucket), + 2: equals("s3://%s/file1.txt", bucket), + 3: equals("s3://%s/filedir/", bucket), }, shell: "/bin/pwsh", }, @@ -141,14 +141,14 @@ func TestCompletionFlag(t *testing.T) { "dir/child.txt", }, expected: map[int]compareFunc{ - 0: equals("//test-completion-flag/dir/"), - 1: equals("//test-completion-flag/file0.txt"), - 2: equals("//test-completion-flag/file1.txt"), - 3: equals("//test-completion-flag/filedir/"), - 4: equals("s3://test-completion-flag/dir/"), - 5: equals("s3://test-completion-flag/file0.txt"), - 6: equals("s3://test-completion-flag/file1.txt"), - 7: equals("s3://test-completion-flag/filedir/"), + 0: equals("//%s/dir/", bucket), + 1: equals("//%s/file0.txt", bucket), + 2: equals("//%s/file1.txt", bucket), + 3: equals("//%s/filedir/", bucket), + 4: equals("s3://%s/dir/", bucket), + 5: equals("s3://%s/file0.txt", bucket), + 6: equals("s3://%s/file1.txt", bucket), + 7: equals("s3://%s/filedir/", bucket), }, shell: "/bin/bash", }, @@ -163,10 +163,10 @@ func TestCompletionFlag(t *testing.T) { "dir/child.txt", }, expected: map[int]compareFunc{ - 0: equals("s3\\://test-completion-flag/dir/"), - 1: equals("s3\\://test-completion-flag/file0.txt"), - 2: equals("s3\\://test-completion-flag/file1.txt"), - 3: equals("s3\\://test-completion-flag/filedir/"), + 0: equals("s3\\://%s/dir/", bucket), + 1: equals("s3\\://%s/file0.txt", bucket), + 2: equals("s3\\://%s/file1.txt", bucket), + 3: equals("s3\\://%s/filedir/", bucket), }, shell: "/bin/zsh", }, @@ -181,8 +181,8 @@ func TestCompletionFlag(t *testing.T) { expected: map[int]compareFunc{ 0: equals("lon:in:key"), 1: equals("lonized"), - 2: equals("s3://test-completion-flag/co:lon:in:key"), - 3: equals("s3://test-completion-flag/co:lonized"), + 2: equals("s3://%s/co:lon:in:key", bucket), + 3: equals("s3://%s/co:lonized", bucket), }, shell: "/bin/bash", }, @@ -195,8 +195,8 @@ func TestCompletionFlag(t *testing.T) { "co:lonized", }, expected: map[int]compareFunc{ - 0: equals("s3\\://test-completion-flag/co\\:lon\\:in\\:key"), - 1: equals("s3\\://test-completion-flag/co\\:lonized"), + 0: equals("s3\\://%s/co\\:lon\\:in\\:key", bucket), + 1: equals("s3\\://%s/co\\:lonized", bucket), }, shell: "/bin/zsh", }, @@ -209,7 +209,7 @@ func TestCompletionFlag(t *testing.T) { `backback`, }, expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/back\\slash"), + 0: equals("s3://%s/back\\slash", bucket), }, shell: "/bin/pwsh", }, @@ -222,8 +222,8 @@ func TestCompletionFlag(t *testing.T) { "as*oburiks", }, expected: map[int]compareFunc{ - 0: equals("s3://test-completion-flag/as*oburiks"), - 1: equals("s3://test-completion-flag/as*terisk"), + 0: equals("s3://%s/as*oburiks", bucket), + 1: equals("s3://%s/as*terisk", bucket), }, shell: "/bin/pwsh", }, diff --git a/storage/url/url.go b/storage/url/url.go index 6dcb3cd85..22ab37df7 100644 --- a/storage/url/url.go +++ b/storage/url/url.go @@ -103,7 +103,7 @@ func New(s string, opts ...Option) (*URL, error) { url := &URL{ Type: remoteObject, - Scheme: scheme, + Scheme: "s3", Bucket: bucket, Path: key, } From 1994ab780227bb0c0e602003cf37cdb6f6c2c574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Tue, 13 Sep 2022 17:22:36 +0300 Subject: [PATCH 30/34] add autocompletion notes to changelog and readme --- CHANGELOG.md | 2 +- README.md | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44e108b73..6effb3710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ - Allow adjacent slashes to be used as keys when uploading to remote. ([#459](https://github.com/peak/s5cmd/pull/459)) - Debian packages are provided on [releases page](https://github.com/peak/s5cmd/releases) ([#380](https://github.com/peak/s5cmd/issues/380)) - Upgraded minimum required Go version to 1.17. - +- Improve auto-completion support of s5cmd for `zsh` and `bash`, start supporting `pwsh` and stop the support for `fish`. Now s5cmd can complete bucket names, s3 keys in a bucket and the local files. However, `install-completion` flag no longer _installs_ the completion script to `*rc` files instead it merely gives instructions to install autocompletion and provides the autocompletion script ([#500](https://github.com/peak/s5cmd/pull/500)). #### Bugfixes - Fixed a bug where (`--stat`) prints unnecessarily when used with help and version commands ([#452](https://github.com/peak/s5cmd/issues/452)) diff --git a/README.md b/README.md index f4fc7f776..07ff32501 100644 --- a/README.md +++ b/README.md @@ -435,14 +435,21 @@ While executing the commands, `s5cmd` detects the region according to the follow ### Shell auto-completion -Shell completion is supported for bash, zsh and fish. +Shell completion is supported for bash, pwsh (PowerShell) and zsh. -To enable auto-completion, run: +Run `s5cmd --install-completion` to obtain the appropriate auto-completion script for your shell, note that `install-completion` does not install the auto-completion but merely gives the instructions to install. The name is kept as it is for backward compatibility. - s5cmd --install-completion +To actually enable auto-completion: +#### in bash and zsh: + you should add auto-completion script to `.bashrc` and `.zshrc` file. +#### in pwsh: +you should save the autocompletion script to a file named `s5cmd.ps1` and add the full path of "s5cmd.ps1" file to profile file (which you can locate with `$profile`) -This will add a few lines to your shell configuration file. After installation, -restart your shell to activate the changes. + +Finally, restart your shell to activate the changes. + +> **Note** +The environment variable `SHELL` must be accurate for the autocompletion to function properly. That is it should point to `bash` binary in bash, to `zsh` binary in zsh and to `pwsh` binary in PowerShell. ### Google Cloud Storage support From 578f64c79aa9d76206c5fe90e8435a6afad91fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Tue, 13 Sep 2022 17:54:32 +0300 Subject: [PATCH 31/34] seperate the logic of constantCompleteWithDefault from urfave/clicli --- command/auto_complete.go | 19 +++++++------------ command/mb.go | 10 +++++++++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 5bd3fe9ae..3800733bd 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -117,19 +117,14 @@ func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(c // it returns a complete function which prints the argument, itself, which is to be completed. // If the argument is empty string it uses the defaultCompletions to make suggestions. -func constantCompleteFnWithDefault(cmd *cli.Command, defaultCompletions ...string) func(ctx *cli.Context) { - return func(ctx *cli.Context) { - arg := parseArgumentToComplete(ctx) - baseShell := filepath.Base(os.Getenv("SHELL")) - if arg == "" { - for _, str := range defaultCompletions { - fmt.Println(formatSuggestionForShell(baseShell, str, str)) - } - } else if strings.HasPrefix(arg, "-") { - cli.DefaultCompleteWithFlags(cmd)(ctx) - } else { - fmt.Println(formatSuggestionForShell(baseShell, arg, arg)) +func constantCompleteWithDefault(arg string, defaultCompletions ...string) { + baseShell := filepath.Base(os.Getenv("SHELL")) + if arg == "" { + for _, str := range defaultCompletions { + fmt.Println(formatSuggestionForShell(baseShell, str, arg)) } + } else { + fmt.Println(formatSuggestionForShell(baseShell, arg, arg)) } } diff --git a/command/mb.go b/command/mb.go index 654b4f69f..dd988d8fc 100644 --- a/command/mb.go +++ b/command/mb.go @@ -3,6 +3,7 @@ package command import ( "context" "fmt" + "strings" "github.com/urfave/cli/v2" @@ -51,7 +52,14 @@ func NewMakeBucketCommand() *cli.Command { }.Run(c.Context) }, } - cmd.BashComplete = constantCompleteFnWithDefault(cmd, "s3://") + cmd.BashComplete = func(ctx *cli.Context) { + arg := parseArgumentToComplete(ctx) + if strings.HasPrefix(arg, "-") { + cli.DefaultCompleteWithFlags(cmd)(ctx) + } else { + constantCompleteWithDefault(arg, "s3://") + } + } return cmd } From 00f37c59d1b843be56025ad03c43e5096718a9dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Wed, 14 Sep 2022 14:15:36 +0300 Subject: [PATCH 32/34] document tested shell versions, move os.Getenv calls to out of the autocompletion functions --- README.md | 7 +++++++ command/auto_complete.go | 44 +++++++++++++++++----------------------- command/mb.go | 5 ++++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 07ff32501..db5626a40 100644 --- a/README.md +++ b/README.md @@ -451,6 +451,13 @@ Finally, restart your shell to activate the changes. > **Note** The environment variable `SHELL` must be accurate for the autocompletion to function properly. That is it should point to `bash` binary in bash, to `zsh` binary in zsh and to `pwsh` binary in PowerShell. + +> **Note** +The autocompletion is tested with following versions of the shells: \ +***zsh*** 5.8.1 (x86_64-apple-darwin21.0) \ +GNU ***bash***, version 5.1.16(1)-release (x86_64-apple-darwin21.1.0) \ +***PowerShell*** 7.2.6 + ### Google Cloud Storage support `s5cmd` supports S3 API compatible services, such as GCS, Minio or your favorite diff --git a/command/auto_complete.go b/command/auto_complete.go index 3800733bd..854756028 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -109,7 +109,8 @@ func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(c return } - printS3Suggestions(c, client, u, arg, isOnlyBucket) + shell := filepath.Base(os.Getenv("SHELL")) + printS3Suggestions(c, shell, client, u, arg, isOnlyBucket) return } } @@ -117,38 +118,36 @@ func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(c // it returns a complete function which prints the argument, itself, which is to be completed. // If the argument is empty string it uses the defaultCompletions to make suggestions. -func constantCompleteWithDefault(arg string, defaultCompletions ...string) { - baseShell := filepath.Base(os.Getenv("SHELL")) +func constantCompleteWithDefault(shell, arg string, defaultCompletions ...string) { if arg == "" { for _, str := range defaultCompletions { - fmt.Println(formatSuggestionForShell(baseShell, str, arg)) + fmt.Println(formatSuggestionForShell(shell, str, arg)) } } else { - fmt.Println(formatSuggestionForShell(baseShell, arg, arg)) + fmt.Println(formatSuggestionForShell(shell, arg, arg)) } } -func printS3Suggestions(c context.Context, client *storage.S3, u *url.URL, arg string, isOnlyBucket bool) { +func printS3Suggestions(c context.Context, shell string, client *storage.S3, u *url.URL, arg string, isOnlyBucket bool) { if u.Bucket == "" || (u.IsBucket() && !strings.HasSuffix(arg, "/")) || isOnlyBucket { - printListBuckets(c, client, u, arg) + printListBuckets(c, shell, client, u, arg) } else { - printListNURLSuggestions(c, client, u, 20, arg) + printListNURLSuggestions(c, shell, client, u, 20, arg) } } -func printListBuckets(ctx context.Context, client *storage.S3, u *url.URL, argToBeCompleted string) { +func printListBuckets(ctx context.Context, shell string, client *storage.S3, u *url.URL, argToBeCompleted string) { buckets, err := client.ListBuckets(ctx, u.Bucket) if err != nil { return } - baseShell := filepath.Base(os.Getenv("SHELL")) for _, bucket := range buckets { - fmt.Println(formatSuggestionForShell(baseShell, "s3://"+bucket.Name+"/", argToBeCompleted)) + fmt.Println(formatSuggestionForShell(shell, "s3://"+bucket.Name+"/", argToBeCompleted)) } } -func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.URL, count int, argToBeCompleted string) { +func printListNURLSuggestions(ctx context.Context, shell string, client *storage.S3, u *url.URL, count int, argToBeCompleted string) { if u.IsBucket() { var err error u, err = url.New(u.Absolute() + "/") @@ -157,7 +156,6 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR } } - baseShell := filepath.Base(os.Getenv("SHELL")) i := 0 for obj := range (*client).List(ctx, u, false) { if i > count { @@ -166,7 +164,7 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR if obj.Err != nil { return } - fmt.Println(formatSuggestionForShell(baseShell, obj.URL.Absolute(), argToBeCompleted)) + fmt.Println(formatSuggestionForShell(shell, obj.URL.Absolute(), argToBeCompleted)) i++ } } @@ -174,9 +172,8 @@ func printListNURLSuggestions(ctx context.Context, client *storage.S3, u *url.UR func printAutocompletionInstructions(shell string) { var script string baseShell := filepath.Base(shell) - instructions := "# To enable autocompletion you should add the following script" + - " to startup scripts of your shell.\n" + - "# It is probably located at ~/." + baseShell + "rc" + instructions := `# To enable autocompletion you should add the following script to startup scripts of your shell. +# It is probably located at ~/.` + baseShell + "rc" switch baseShell { case "zsh": @@ -185,15 +182,12 @@ func printAutocompletionInstructions(shell string) { script = bash case "pwsh": script = pwsh - instructions = "# To enable autocompletion you should save the following" + - " script to a file named \"s5cmd.ps1\" and execute it.\n# To persist it" + - " you should add the path of \"s5cmd.ps1\" file to profile file " + - "(which you can locate with $profile) to automatically execute \"s5cmd.ps1\"" + - " on every shell start up." + instructions = `# To enable autocompletion you should save the following script to a file named "s5cmd.ps1" and execute it. +# To persist it you should add the path of "s5cmd.ps1" file to profile file (which you can locate with $profile) to automatically execute "s5cmd.ps1" on every shell start up.` default: - instructions = "# We couldn't recognize your SHELL \"" + baseShell + "\".\n" + - "# Shell completion is supported only for bash, pwsh and zsh." + - "# Make sure that your SHELL environment variable is set accurately." + instructions = `# We couldn't recognize your SHELL "` + baseShell + `". +# Shell completion is supported only for bash, pwsh and zsh. +# Make sure that your SHELL environment variable is set accurately.` } fmt.Println(instructions) diff --git a/command/mb.go b/command/mb.go index dd988d8fc..6b5eabe03 100644 --- a/command/mb.go +++ b/command/mb.go @@ -3,6 +3,8 @@ package command import ( "context" "fmt" + "os" + "path/filepath" "strings" "github.com/urfave/cli/v2" @@ -57,7 +59,8 @@ func NewMakeBucketCommand() *cli.Command { if strings.HasPrefix(arg, "-") { cli.DefaultCompleteWithFlags(cmd)(ctx) } else { - constantCompleteWithDefault(arg, "s3://") + shell := filepath.Base(os.Getenv("SHELL")) + constantCompleteWithDefault(shell, arg, "s3://") } } From 3a1220e23cab09bf5d58eddfdb322ff28d168a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= <32893303+Kucukaslan@users.noreply.github.com> Date: Thu, 15 Sep 2022 09:23:14 +0300 Subject: [PATCH 33/34] Apply suggestions from code review Co-authored-by: Selman Kayrancioglu --- command/auto_complete.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 854756028..8898e1ebb 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -116,7 +116,7 @@ func getBashCompleteFn(cmd *cli.Command, isOnlyRemote, isOnlyBucket bool) func(c } } -// it returns a complete function which prints the argument, itself, which is to be completed. +// constantCompleteWithDefault returns a complete function which prints the argument, itself, which is to be completed. // If the argument is empty string it uses the defaultCompletions to make suggestions. func constantCompleteWithDefault(shell, arg string, defaultCompletions ...string) { if arg == "" { @@ -233,10 +233,10 @@ func parseArgumentToComplete(ctx *cli.Context) string { arg = args.Get(l - 1) } - // argument may start with a quotation mark, in this case we want to trim that before - // checking if it has prefix s3:// + // argument may start with a quotation mark, in this case we want to trim + // that before checking if it has prefix 's3://'. // Beware that we only want to trim the first char, not all of the leading - // quotation marks, because those quotation marks may be actual charactes. + // quotation marks, because those quotation marks may be actual characters. if strings.HasPrefix(arg, "'") { arg = strings.TrimPrefix(arg, "'") } else { From 20c7a016aded3aef2edda24319dcef6ddc00eb9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Can=20K=C3=BC=C3=A7=C3=BCkaslan?= Date: Thu, 15 Sep 2022 09:25:49 +0300 Subject: [PATCH 34/34] move escapeColon into formatSuggestionForShell method --- command/auto_complete.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/command/auto_complete.go b/command/auto_complete.go index 8898e1ebb..7c37df72e 100644 --- a/command/auto_complete.go +++ b/command/auto_complete.go @@ -213,17 +213,12 @@ func formatSuggestionForShell(baseShell, suggestion, argToBeCompleted string) st // colons are used as a seperator for the autocompletion script // so "literal colons in completion must be quoted with a backslash" // see also https://zsh.sourceforge.io/Doc/Release/Completion-System.html#:~:text=This%20is%20followed,as%20name1%3B - return escapeColon(suggestion) + return strings.ReplaceAll(suggestion, ":", `\:`) default: return suggestion } } -// replace every colon : with \: -func escapeColon(str ...interface{}) string { - return strings.ReplaceAll(fmt.Sprint(str...), ":", `\:`) -} - func parseArgumentToComplete(ctx *cli.Context) string { var arg string args := ctx.Args()