├── scripts ├── cleanup-white-noise.sh ├── copyright │ └── copyright.go └── build-check-comments.sh ├── .gitignore ├── go.mod ├── .github └── workflows │ └── test.yaml ├── regexp.go ├── CHANGELOG.md ├── internal ├── timestamp │ └── timestamp.go └── camelcase │ ├── camelcase_test.go │ └── camelcase.go ├── testutil ├── testorbench_test.go ├── testorbench.go └── testutil.go ├── example ├── custom_type │ └── main.go ├── custom_help │ └── main.go └── main.go ├── timeorduration_test.go ├── go.sum ├── timeorduration.go ├── pathorcontent.go ├── Makefile ├── flagarize_test.go ├── LICENSE ├── README.md ├── flagarize.go └── flagarize_ext_test.go /scripts/cleanup-white-noise.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SED_BIN=${SED_BIN:-sed} 3 | 4 | ${SED_BIN} -i 's/[ \t]*$//' "$@" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | .bin -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bwplotka/flagarize 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect 7 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d 8 | github.com/davecgh/go-spew v1.1.1 // indirect 9 | github.com/kr/pretty v0.1.0 // indirect 10 | github.com/pkg/errors v0.9.1 11 | gopkg.in/alecthomas/kingpin.v2 v2.2.6 12 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 13 | gopkg.in/yaml.v2 v2.2.5 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | unit-tests: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Install Go. 14 | uses: actions/setup-go@v1 15 | with: 16 | go-version: 1.13.6 17 | - name: Check out code into the Go module directory 18 | uses: actions/checkout@v1 19 | - uses: actions/cache@v1 20 | with: 21 | path: ~/go/pkg/mod 22 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 23 | - name: Static Analysis 24 | run: make lint 25 | - name: Unit tests 26 | run: make test 27 | -------------------------------------------------------------------------------- /regexp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | package flagarize 5 | 6 | import ( 7 | "regexp" 8 | ) 9 | 10 | type Regexp struct { 11 | *regexp.Regexp 12 | } 13 | 14 | // Set registers Regexp flag. 15 | func (r *Regexp) Set(v string) (err error) { 16 | rg, err := regexp.Compile(v) 17 | if err != nil { 18 | return err 19 | } 20 | r.Regexp = rg 21 | return nil 22 | } 23 | 24 | type AnchoredRegexp struct { 25 | *regexp.Regexp 26 | } 27 | 28 | // Set registers anchored Regexp flag. 29 | func (r *AnchoredRegexp) Set(v string) (err error) { 30 | rg, err := regexp.Compile("^(?:" + v + ")$") 31 | if err != nil { 32 | return err 33 | } 34 | r.Regexp = rg 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | NOTE: As semantic versioning states all 0.y.z releases can contain breaking changes in API (flags, grpc API, any backward compatibility) 9 | 10 | We use *breaking* word for marking changes that are not backward compatible (relates only to v0.y.z releases.) 11 | 12 | ## [v0.9.0](https://github.com/bwplotka/flagarize/releases/tag/v0.9.0) - 2020.03.22 13 | 14 | Initial release 💪💪 💪 15 | 16 | Why 0.9.0? Well, because we plan to release 1.0 once we introduce this library to [Thanos](http://github.com/thanos-io/thanos) as the final test (: 17 | -------------------------------------------------------------------------------- /internal/timestamp/timestamp.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | // Taken from "github.com/prometheus/prometheus/pkg/timestamp" 5 | // 6 | // Copyright 2017 The Prometheus Authors 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | 19 | package timestamp 20 | 21 | import "time" 22 | 23 | // FromTime returns a new millisecond timestamp from a time. 24 | func FromTime(t time.Time) int64 { 25 | return t.Unix()*1000 + int64(t.Nanosecond())/int64(time.Millisecond) 26 | } 27 | 28 | // Time returns a new time.Time object from a millisecond timestamp. 29 | func Time(ts int64) time.Time { 30 | return time.Unix(ts/1000, (ts%1000)*int64(time.Millisecond)) 31 | } 32 | -------------------------------------------------------------------------------- /testutil/testorbench_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | // Taken from Thanos project. 5 | // 6 | // Copyright (c) The Thanos Authors. 7 | // Licensed under the Apache License 2.0. 8 | 9 | package testutil 10 | 11 | import "testing" 12 | 13 | func TestTestOrBench(t *testing.T) { 14 | tb := NewTB(t) 15 | tb.Run("1", func(tb TB) { testorbenchComplexTest(tb) }) 16 | tb.Run("2", func(tb TB) { testorbenchComplexTest(tb) }) 17 | } 18 | 19 | func BenchmarkTestOrBench(b *testing.B) { 20 | tb := NewTB(b) 21 | tb.Run("1", func(tb TB) { testorbenchComplexTest(tb) }) 22 | tb.Run("2", func(tb TB) { testorbenchComplexTest(tb) }) 23 | } 24 | 25 | func testorbenchComplexTest(tb TB) { 26 | tb.Run("a", func(tb TB) { 27 | tb.Run("aa", func(tb TB) { 28 | tb.ResetTimer() 29 | for i := 0; i < tb.N(); i++ { 30 | if !tb.IsBenchmark() { 31 | if tb.N() != 1 { 32 | tb.FailNow() 33 | } 34 | } 35 | } 36 | }) 37 | }) 38 | tb.SetBytes(120220) 39 | tb.Run("b", func(tb TB) { 40 | tb.Run("bb", func(tb TB) { 41 | tb.ResetTimer() 42 | for i := 0; i < tb.N(); i++ { 43 | if !tb.IsBenchmark() { 44 | if tb.N() != 1 { 45 | tb.FailNow() 46 | } 47 | } 48 | } 49 | }) 50 | }) 51 | 52 | } 53 | -------------------------------------------------------------------------------- /example/custom_type/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/bwplotka/flagarize" 13 | "gopkg.in/alecthomas/kingpin.v2" 14 | ) 15 | 16 | type YourCustomType string 17 | 18 | func (cst *YourCustomType) Set(s string) error { 19 | *cst = YourCustomType(fmt.Sprintf("AlwaysAddingPrefix%s", s)) 20 | return nil 21 | } 22 | 23 | func main() { 24 | a := kingpin.New(filepath.Base(os.Args[0]), "") 25 | 26 | type ConfigForCLI struct { 27 | Field1 string `flagarize:"name=flag1|help=Some help.|default=Some Value|envvar=FLAG1|short=f|placeholder="` 28 | Field2 YourCustomType `flagarize:"name=custom-type-flag|help=Custom Type always add prefix 'AlwaysAddingPrefix' to the given string value.|required=true|short=c|placeholder="` 29 | } 30 | 31 | // Flagarize your config! (Register flags from config). 32 | cfg := &ConfigForCLI{} 33 | if err := flagarize.Flagarize(a, cfg); err != nil { 34 | log.Fatal(err) 35 | } 36 | if _, err := a.Parse(os.Args[1:]); err != nil { 37 | log.Fatal(err) 38 | } 39 | fmt.Printf("Config Value after flagarizing & flag parsing: %+v\n", cfg) 40 | } 41 | -------------------------------------------------------------------------------- /timeorduration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | // Taken from Thanos project. 5 | // 6 | // Copyright (c) The Thanos Authors. 7 | // Licensed under the Apache License 2.0. 8 | 9 | package flagarize_test 10 | 11 | import ( 12 | "testing" 13 | "time" 14 | 15 | "github.com/bwplotka/flagarize" 16 | "github.com/bwplotka/flagarize/internal/timestamp" 17 | "github.com/bwplotka/flagarize/testutil" 18 | ) 19 | 20 | func TestTimeOrDuration(t *testing.T) { 21 | minTime := &flagarize.TimeOrDuration{} 22 | testutil.Ok(t, minTime.Set("10s")) 23 | maxTime := &flagarize.TimeOrDuration{} 24 | testutil.Ok(t, maxTime.Set("9999-12-31T23:59:59Z")) 25 | 26 | testutil.Equals(t, "10s", minTime.String()) 27 | testutil.Equals(t, "9999-12-31 23:59:59 +0000 UTC", maxTime.String()) 28 | 29 | prevTime := timestamp.FromTime(time.Now()) 30 | afterTime := timestamp.FromTime(time.Now().Add(15 * time.Second)) 31 | 32 | testutil.Assert(t, minTime.PrometheusTimestamp() > prevTime, "minTime prometheus timestamp is less than time now.") 33 | testutil.Assert(t, minTime.PrometheusTimestamp() < afterTime, "minTime prometheus timestamp is more than time now + 15s") 34 | 35 | testutil.Assert(t, maxTime.PrometheusTimestamp() == 253402300799000, "maxTime is not equal to 253402300799000") 36 | } 37 | -------------------------------------------------------------------------------- /example/custom_help/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | 12 | "github.com/bwplotka/flagarize" 13 | "gopkg.in/alecthomas/kingpin.v2" 14 | ) 15 | 16 | const prefix = "AlwaysAddingPrefix" 17 | 18 | type YourCustomType string 19 | 20 | func (cst *YourCustomType) Set(s string) error { 21 | *cst = YourCustomType(fmt.Sprintf("%s%s", prefix, s)) 22 | return nil 23 | } 24 | 25 | func main() { 26 | a := kingpin.New(filepath.Base(os.Args[0]), "") 27 | 28 | type ConfigForCLI struct { 29 | Field1 string `flagarize:"name=flag1|help=Some help.|default=Some Value|envvar=FLAG1|short=f|placeholder="` 30 | Field2 YourCustomType `flagarize:"name=custom-type-flag|required=true|short=c|placeholder="` 31 | Field2FlagarizeHelp string 32 | } 33 | 34 | // Flagarize your config! (Register flags from config). 35 | cfg := &ConfigForCLI{ 36 | Field2FlagarizeHelp: fmt.Sprintf("Custom Type always add prefix %q to the given string value.", prefix), 37 | } 38 | if err := flagarize.Flagarize(a, cfg); err != nil { 39 | log.Fatal(err) 40 | } 41 | if _, err := a.Parse(os.Args[1:]); err != nil { 42 | log.Fatal(err) 43 | } 44 | fmt.Printf("Config Value after flagarizing & flag parsing: %+v\n", cfg) 45 | } 46 | -------------------------------------------------------------------------------- /scripts/copyright/copyright.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "io/ioutil" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | ) 14 | 15 | var ( 16 | // license compatible for Go and Proto files. 17 | license = []byte(`// Copyright (c) Bartłomiej Płotka @bwplotka 18 | // Licensed under the Apache License 2.0. 19 | 20 | `) 21 | ) 22 | 23 | func applyLicenseToProtoAndGo() error { 24 | return filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 25 | if err != nil { 26 | return err 27 | } 28 | 29 | // Filter out stuff that does not need copyright. 30 | if info.IsDir() { 31 | switch path { 32 | case "vendor": 33 | return filepath.SkipDir 34 | } 35 | return nil 36 | } 37 | if strings.HasSuffix(path, ".pb.go") { 38 | return nil 39 | } 40 | if filepath.Ext(path) != ".proto" && filepath.Ext(path) != ".go" { 41 | return nil 42 | } 43 | 44 | b, err := ioutil.ReadFile(path) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | if !strings.HasPrefix(string(b), string(license)) { 50 | log.Println("file", path, "is missing Copyright header. Adding.") 51 | 52 | var bb bytes.Buffer 53 | _, _ = bb.Write(license) 54 | _, _ = bb.Write(b) 55 | if err = ioutil.WriteFile(path, bb.Bytes(), 0666); err != nil { 56 | return err 57 | } 58 | } 59 | return nil 60 | }) 61 | } 62 | 63 | func main() { 64 | if err := applyLicenseToProtoAndGo(); err != nil { 65 | log.Fatal(err) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) Bartłomiej Płotka @bwplotka 2 | // Licensed under the Apache License 2.0. 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "net/url" 10 | "os" 11 | "path/filepath" 12 | "time" 13 | 14 | "github.com/bwplotka/flagarize" 15 | "gopkg.in/alecthomas/kingpin.v2" 16 | ) 17 | 18 | func main() { 19 | a := kingpin.New(filepath.Base(os.Args[0]), "") 20 | 21 | type ComponentAOptions struct { 22 | Field1 []string `flagarize:"name=a.flag1|help=Help for field 1 in nested struct for component A."` 23 | } 24 | type ConfigForCLI struct { 25 | Field1 string `flagarize:"name=flag1|help=Help for field 1.|default=something"` 26 | Field2 *url.URL `flagarize:"name=flag2|help=Help for field 2.|placeholder="` 27 | Field3 int `flagarize:"name=flag3|help=Help for field 3.|default=2144"` 28 | Field4 flagarize.TimeOrDuration `flagarize:"name=flag4|help=Help for field 4.|default=1m|placeholder=