Skip to content

Commit

Permalink
Merge pull request #1 from fredbi/feat/experimental
Browse files Browse the repository at this point in the history
continue experimenting
  • Loading branch information
fredbi authored Sep 22, 2023
2 parents 908c940 + 72d6695 commit 13bd74a
Show file tree
Hide file tree
Showing 21 changed files with 532 additions and 65 deletions.
27 changes: 3 additions & 24 deletions .github/workflows/01-golang-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,17 @@ on:
pull_request:
permissions:
contents: read
# Optional: allow read access to pull request. Use with `only-new-issues` option.
# pull-requests: read
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: 1.19
stable: true
check-latest: true
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest

# Optional: working directory, useful for monorepos
# working-directory: somedir

# Optional: golangci-lint command line arguments.
# args: --issues-exit-code=0

# Optional: show only new issues if it's a pull request. The default value is `false`.
# only-new-issues: true

# Optional: if set to true then the all caching functionality will be complete disabled,
# takes precedence over all other caching options.
# skip-cache: true

# Optional: if set to true then the action don't cache or restore ~/go/pkg.
# skip-pkg-cache: true

# Optional: if set to true then the action don't cache or restore ~/.cache/go-build.
# skip-build-cache: true
13 changes: 9 additions & 4 deletions .github/workflows/02-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ on:
- master
jobs:
test_config:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
go_version: ['1.19','1.20','1.21']
os: [ubuntu-latest]

runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: ${{ matrix.go_version }}
stable: true
check-latest: true
- uses: actions/checkout@v3
- name: Test config package
run: |
Expand Down
18 changes: 18 additions & 0 deletions .github/workflows/03-govulncheck.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: govulncheck
on:
push:
tags:
- v*
branches:
- master
- main
pull_request:
permissions:
contents: read
jobs:
govulncheck:
name: govulncheck
runs-on: ubuntu-latest
steps:
- name: govulncheck
uses: golang/govulncheck-action@v1
56 changes: 47 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,68 @@
![Lint](https://github.com/fredbi/gflag/actions/workflows/01-golang-lint.yaml/badge.svg)
![CI](https://github.com/fredbi/gflag/actions/workflows/02-test.yaml/badge.svg)
[![Coverage Status](https://coveralls.io/repos/github/fredbi/gflag/badge.svg)](https://coveralls.io/github/fredbi/gflag)
![Vulnerability Check](https://github.com/fredbi/gflag/actions/workflows/03-govulncheck.yaml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/fredbi/gflag)](https://goreportcard.com/report/github.com/fredbi/gflag)

![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/fredbi/gflag)
[![Go Reference](https://pkg.go.dev/badge/github.com/fredbi/gflag.svg)](https://pkg.go.dev/github.com/fredbi/gflag)
[![license](http://img.shields.io/badge/license/License-Apache-yellow.svg)](https://raw.githubusercontent.com/fredbi/go-cli/master/LICENSE.md)

# gflag

`pflags` with generic types.

> Yet another CLI flags library that reuses the great package `github.com/spf13/pflag`, with an interface built on go generics.
> This is not a fork, but an extension of the `pflag` functionality.
> This is yet another CLI flags library...
>
> Let's not reinvent the wheel: this module reuses the great package `github.com/spf13/pflag`,
> with an interface built on go generics.
>
> The main idea is to simplify the pflag interface, with less things to remember about flag types.
>
> So this is not a fork or drop-in replacement, but an extension of the `pflag` functionality.
## Usage

This package is designed to be used with `pflag`. All types created by `gflag` implement the `pflag.Value` interface.
This package is designed to be used together with `github.com/spf13/pflag`.
All types created by `gflag` implement the `pflag.Value` interface.

```go
var flagVal int
fs := pflag.NewFlagSet("", pflag.ContinueOnError)
intFlag := gflag.NewFlagValue(&flagVal, 1) // infer flag from underlying type int, with a default value
import (
"fmt"

"github.com/fredbi/gflag"
"github.com/spf13/pflag"
)

var flagVal int

fs := pflag.NewFlagSet("", pflag.ContinueOnError)
intFlag := gflag.NewFlagValue(&flagVal, 1) // infer flag from underlying type int, with a default value

fs.Var(intFlag, "integer", "integer value") // register the flag in pflag flagset

_ = fs.Parse([]string{"--integer", 10}) // parse command line arguments

fmt.Println(intFlag.GetValue())
```

With `pflag` this piece of code would look like:
```go
import (
"fmt"

"github.com/spf13/pflag"
)

var flagVal int

fs := pflag.NewFlagSet("", pflag.ContinueOnError)

fs.Var(intFlag, "integer", "integer value") // register the flag in pflag flagset
fs.IntVar(&flagVal, "integer", "integer value") // register the flag in pflag flagset

_ = fs.Parse([]string{"--integer", 10}) // parse command line arguments
_ = fs.Parse([]string{"--integer", 10}) // parse command line arguments

fmt.Println(intFlag.GetValue())
fmt.Println(fs.GetInt("integer"))
```

You may take a look at [more examples](example_values_test.go), with slices and maps.
Expand Down
70 changes: 69 additions & 1 deletion example_values_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,24 @@ import (

// Joint usage with pflag, for a simple bool flag
func ExampleValue() {
// variable to store the value of the flag
var flagVal bool

// Custom pflag.FlagSet.
// Simple CLIs may just use the default pre-baked package-level FlagSet for the command line.
name := "verbose"
short := name[:1]
fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

// declare a new generic flag: type is inferred from the provided default value
verboseFlag := gflag.NewFlagValue(&flagVal, false)

// register this flag into the FlagSet
fl := fs.VarPF(verboseFlag, name, short, "verbose output")
fl.NoOptDefVal = verboseFlag.GetNoOptDefVal() // allow no argument passed to the flag

// parse args from the command line.
// Simple CLIs may just use the default, with pflag.Parse() from the command line arguments.
if err := fs.Parse([]string{"--verbose"}); err != nil {
log.Fatalln("parsing error:", err)
}
Expand All @@ -34,7 +43,7 @@ func ExampleValue() {

// various ways to retrieve the parsed value

// using underlying value
// using the variable used for storing the value
fmt.Println(flagVal)

// using GetValue[bool]()
Expand All @@ -49,9 +58,65 @@ func ExampleValue() {
// true
}

// Simple int flag
func ExampleAddValueFlag() {
const name = "integer"
short := name[:1]
const usage = "an integer flag"

fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)

// add flag without a preallocated variable: all interaction is performed via the flag
gfl, fl := gflag.AddValueFlag(
fs,
gflag.NewFlagValue(nil, 5), // create a generic flag of type integer
name, short, usage, // the usual specification for the flag
)

if err := fs.Parse([]string{"--integer", "12"}); err != nil {
log.Fatalln("parsing error:", err)
}

// retrieve parsed values from name
flag := fs.Lookup(name)
fmt.Println(flag.Name)
fmt.Println(flag.Value)

// retrieve parsed value from short name
flag = fs.ShorthandLookup(short)
fmt.Println(flag.Name)
fmt.Println(flag.Value)

// various ways to retrieve the parsed value

// using underlying value
fmt.Println(fl.Value)

// using FlagSet.GetInt() (old way)
val, err := fs.GetInt(name)
if err != nil {
log.Fatalln("flag type error:", err)
}
fmt.Printf("%v (%T)\n", val, val)

// using GetValue[int]() (new way)
val2 := gfl.GetValue()
fmt.Printf("%v (%T)\n", val2, val2)

// Output:
// integer
// 12
// integer
// 12
// 12
// 12 (int)
// 12 (int)
}

// Joint usage with pflag, for a string array flag
func ExampleSliceValue() {
var flagVal []string

name := "strings"
short := name[:1]
fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)
Expand Down Expand Up @@ -90,6 +155,7 @@ func ExampleSliceValue() {
// Joint usage with pflag, for a key=value map of integers
func ExampleMapValue() {
var flagVal map[string]int

name := "map"
short := name[:1]
fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)
Expand Down Expand Up @@ -126,9 +192,11 @@ func ExampleMapValue() {

}

// Example of using gflag options.
func ExampleOption() {
// example with int used with count semantics
var flagVal int

name := "count"
short := name[:1]
fs := pflag.NewFlagSet("Example", pflag.ContinueOnError)
Expand Down
11 changes: 11 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Complete examples

The examples in this folder illustrate the use of `gflag`
jointly with some other popular CLI-building lib.

[github.com/spf13/pflag](pflag/main.go)
[github.com/spf13/cobra](cobra/main.go)
[github.com/spf13/viper](viper/main.go)
[flag (standard library)](flag/main.go)
[github.com/jessevdk/go-flags](jessevdk/main.go)
[github.com/urfave/cli](urfave/main.go)
Empty file added examples/cobra/.gitkeep
Empty file.
Empty file added examples/flag/.gitkeep
Empty file.
100 changes: 100 additions & 0 deletions examples/flag/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//go:build examples

//nolint:forbidigo
package main

import (
"encoding/json"
"flag"
"fmt"
"net"
"time"

"github.com/fredbi/gflag"
)

type cmdFlags struct {
Verbose bool
Integer int
Counter int
Strings []string
IPs []net.IP
Durations map[string]time.Duration
}

var cliFlags cmdFlags

func registerFlags() {
gfl := gflag.NewFlagValue(&cliFlags.Verbose, false)
flag.Var(gfl, "verbose", "report verbose output")

flag.Var(gflag.NewFlagValue(&cliFlags.Integer, 1),
"integer", "sets a number",
)

// we can't do here with "flag" what we do with "pflag": a value will be required,
// e.g. -count=1
cfl := gflag.NewFlagValue(&cliFlags.Counter, 0, gflag.IntIsCount(true))
flag.Var(cfl, "count", "increments a counter")

flag.Var(gflag.NewFlagSliceValue(&cliFlags.Strings, []string{"x"}),
"strings", "sets some strings",
)
flag.Var(gflag.NewFlagSliceValue(&cliFlags.IPs, []net.IP{net.ParseIP("127.0.0.1")}),
"ips", "sets some ip addresses",
)
flag.Var(gflag.NewFlagMapValue(&cliFlags.Durations, map[string]time.Duration{"sec": time.Second}),
"durations", "sets a map of durations to keys",
)
}

/*
Usage of /tmp/go-build1248450367/b001/exe/main:
-count
increments a counter (default 0)
-durations value
sets a map of durations to keys (default [sec=1s])
-integer
sets a number (default 1)
-ips value
sets some ip addresses (default [127.0.0.1])
-strings value
sets some strings (default [x])
-verbose
report verbose output (default false)
Full example:
go run main.go -count 1 -integer 12 -count 1 -ips "8.8.8.8,2.2.2.2" -strings "a,b" -durations "hour=1h,second=1s" -durations "day=24h" -verbose
flag values parsed
{
"Verbose": true,
"Integer": 12,
"Counter": 1,
"Strings": [
"a",
"b"
],
"IPs": [
"8.8.8.8",
"2.2.2.2"
],
"Durations": {
"day": 86400000000000,
"hour": 3600000000000,
"second": 1000000000
}
}
*/
func main() {
registerFlags()
// pflag.CommandLine.AddGoFlagSet(flag.CommandLine)

flag.Parse()

fmt.Println("flag values parsed")
bbb, _ := json.MarshalIndent(cliFlags, "", " ")
fmt.Printf("%s\n", string(bbb))
}
Empty file added examples/jessevdk/.gitkeep
Empty file.
Loading

0 comments on commit 13bd74a

Please sign in to comment.