Skip to content

Commit

Permalink
up: cflag - add more unit tests and update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
inhere committed Sep 14, 2022
1 parent 3aa688f commit 4e38f78
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 31 deletions.
14 changes: 8 additions & 6 deletions cflag/README.md
Expand Up @@ -74,7 +74,7 @@ func main() {
}
```

## Bind and get arguments
### Bind and get arguments

Bind arguments:

Expand All @@ -90,7 +90,7 @@ Get arguments by name:
cliutil.Infoln("arg2 =", c.Arg("arg2").String())
```

## Set `required` and short options
### Set `required` and short options

Options can be set as `required`, and **short** option names can be set.

Expand All @@ -114,7 +114,7 @@ Options can be set as `required`, and **short** option names can be set.
c.StringVar(&opts.str1, "str1", "def-val", "this is a string option with default value;;s")
```

## Show help
### Show help

```shell
go run ./cflag/_example/cmd.go -h
Expand All @@ -124,7 +124,7 @@ go run ./cflag/_example/cmd.go -h

![cmd-help](_example/cmd-help.png)

## Run command
### Run command

```shell
go run ./cflag/_example/cmd.go --name inhere -a 12 --lo val ab cd
Expand All @@ -135,7 +135,7 @@ go run ./cflag/_example/cmd.go --name inhere -a 12 --lo val ab cd de fg

![cmd-run](_example/cmd-run.png)

## `required` Check
### `required` Check

```shell
go run ./cflag/_example/cmd.go -a 22
Expand All @@ -148,6 +148,8 @@ go run ./cflag/_example/cmd.go --name inhere

## Cli application

Use `cflag` to quickly build a multi-command application.

```go
package main

Expand Down Expand Up @@ -196,7 +198,7 @@ func main() {
}
```

### App help
### Show commands

```shell
go run ./cflag/_example/app.go -h
Expand Down
16 changes: 9 additions & 7 deletions cflag/README.zh-CN.md
Expand Up @@ -76,13 +76,13 @@ func main() {
}
```

## 设置必须和短选项
### 设置必须和短选项

可以设置选项为 `required` 必填项,并且支持设置 **短选项** 名称。

> TIPs: 通过扩展解析了选项的 `usage` 来实现 `required``shorts`
### `usage` 格式
#### `usage` 格式

- 默认:`desc`
- 格式1: `desc;required`
Expand All @@ -101,7 +101,7 @@ func main() {
c.StringVar(&opts.str1, "str1", "def-val", "this is a string option with default value;;s")
```

## 绑定和获取参数
### 绑定和获取参数

绑定参数信息

Expand All @@ -117,7 +117,7 @@ func main() {
cliutil.Infoln("arg2 =", c.Arg("arg2").String())
```

## 显示帮助信息
### 显示帮助信息

```shell
go run ./cflag/_example/cmd.go -h
Expand All @@ -127,7 +127,7 @@ go run ./cflag/_example/cmd.go -h

![cmd-help](_example/cmd-help.png)

## 运行命令
### 运行命令

```shell
go run ./cflag/_example/cmd.go --name inhere -a 12 --lo val ab cd
Expand All @@ -138,7 +138,7 @@ go run ./cflag/_example/cmd.go --name inhere -a 12 --lo val ab cd de fg

![cmd-run](_example/cmd-run.png)

## `required` 检查
### `required` 检查

```shell
go run ./cflag/_example/cmd.go -a 22
Expand All @@ -150,7 +150,9 @@ go run ./cflag/_example/cmd.go --name inhere
![cmd-required.png](_example/cmd-required.png)


## Cli application
## Cli 应用

使用 `cflag` 可以快速的构建一个支持多命令的应用.

```go
package main
Expand Down
48 changes: 32 additions & 16 deletions cflag/app.go
Expand Up @@ -3,6 +3,7 @@ package cflag
import (
"flag"
"fmt"
"io"
"os"
"path"
"sort"
Expand All @@ -16,14 +17,14 @@ import (

// App struct
type App struct {
cmds map[string]*Cmd
// names
cmds map[string]*Cmd
names []string

Name string
Desc string
// NameWidth max width for command name
NameWidth int
NameWidth int
HelpWriter io.Writer
// Version for app
Version string

Expand All @@ -35,8 +36,11 @@ type App struct {
func NewApp(fns ...func(app *App)) *App {
app := &App{
cmds: make(map[string]*Cmd),
// with default version
Version: "0.0.1",
// NameWidth default value
NameWidth: 12,
NameWidth: 12,
HelpWriter: os.Stdout,
}

for _, fn := range fns {
Expand Down Expand Up @@ -66,7 +70,7 @@ func (a *App) addCmd(c *Cmd) {
a.cmds[c.Name] = c
a.NameWidth = mathutil.MaxInt(a.NameWidth, ln)

// attach func
// attach handle func
if c.Func != nil {
c.CFlags.Func = func(_ *CFlags) error {
return c.Func(c)
Expand All @@ -78,12 +82,6 @@ func (a *App) addCmd(c *Cmd) {
}
}

func (a *App) init() {
if a.Name == "" {
a.Name = path.Base(os.Args[0])
}
}

// Run app by os.Args
func (a *App) Run() {
err := a.RunWithArgs(os.Args[1:])
Expand All @@ -106,17 +104,23 @@ func (a *App) RunWithArgs(args []string) error {
}

if name[0] == '-' {
return fmt.Errorf("provide undefined flag %s", name)
return fmt.Errorf("provide undefined flag option %q", name)
}

cmd, ok := a.findCmd(name)
if !ok {
return fmt.Errorf("input not exists command %s", name)
return fmt.Errorf("input not exists command %q", name)
}

return cmd.Parse(args[1:])
}

func (a *App) init() {
if a.Name == "" {
a.Name = path.Base(os.Args[0])
}
}

func (a *App) findCmd(name string) (*Cmd, bool) {
cmd, ok := a.cmds[name]
return cmd, ok
Expand All @@ -133,9 +137,9 @@ func (a *App) showHelp() error {

buf.QuietWritef("\n\n<comment>Usage:</> %s <green>COMMAND</> [--Options...] [...Arguments]\n", bin)

buf.QuietWriteln("<comment>Options:</>:")
buf.QuietWriteln("<comment>Options:</>")
buf.QuietWriteln(" <green>-h, --help</> Display application help")
buf.QuietWriteln("\n<comment>Commands</>:")
buf.QuietWriteln("\n<comment>Commands:</>")

sort.Strings(a.names)
for _, name := range a.names {
Expand All @@ -152,7 +156,11 @@ func (a *App) showHelp() error {
a.AfterHelpBuild(buf)
}

color.Print(buf.ResetAndGet())
if a.HelpWriter == nil {
a.HelpWriter = os.Stdout
}

color.Fprint(a.HelpWriter, buf.ResetAndGet())
return nil
}

Expand All @@ -176,3 +184,11 @@ func NewCmd(name, desc string) *Cmd {
CFlags: fs,
}
}

// Config the cmd
func (c *Cmd) Config(fn func(c *Cmd)) *Cmd {
if fn != nil {
fn(c)
}
return c
}
83 changes: 83 additions & 0 deletions cflag/app_test.go
@@ -1,8 +1,14 @@
package cflag_test

import (
"bytes"
"os"
"testing"

"github.com/gookit/goutil/cflag"
"github.com/gookit/goutil/dump"
"github.com/gookit/goutil/strutil"
"github.com/gookit/goutil/testutil/assert"
)

func ExampleNewApp() {
Expand Down Expand Up @@ -47,6 +53,83 @@ func ExampleNewApp() {
}

app.Add(c1, c2)
app.Run()
}

func TestApp_Run(t *testing.T) {
app := cflag.NewApp(func(app *cflag.App) {
app.Name = "myapp"
app.Desc = "this is my cli application"
app.Version = "1.0.2"
})

// test invalid
assert.Panics(t, func() {
app.Add(cflag.NewCmd("", "empty name"))
})

// add command
var c1Opts = struct {
age int
name string
}{}

c1 := cflag.NewCmd("demo", "this is a demo command")
c1.OnAdd = func(c *cflag.Cmd) {
c.IntVar(&c1Opts.age, "age", 0, "this is a int option;;a")
c.StringVar(&c1Opts.name, "name", "", "this is a string option and required;true")

c.AddArg("arg1", "this is arg1", true, nil)
c.AddArg("arg2", "this is arg2", false, nil)
}
c1.Config(func(c *cflag.Cmd) {
c.Func = func(c *cflag.Cmd) error {
dump.P(c1Opts, c.Args())
return nil
}
})

app.Add(c1)

// repeat name
assert.Panics(t, func() {
app.Add(c1)
})

// show help1
osArgs := os.Args
os.Args = []string{"./myapp"}
app.AfterHelpBuild = func(buf *strutil.Buffer) {
help := buf.String()
assert.StrContains(t, help, "-h, --help")
assert.StrContains(t, help, "demo")
assert.StrContains(t, help, "This is a demo command")
}
app.Run()
os.Args = osArgs

// show help2
buf := new(bytes.Buffer)
app.HelpWriter = buf
err := app.RunWithArgs([]string{"--help"})
assert.NoErr(t, err)

help := buf.String()
assert.StrContains(t, help, "-h, --help")
assert.StrContains(t, help, "demo")
assert.StrContains(t, help, "This is a demo command")

// run ... error
err = app.RunWithArgs([]string{"notExists"})
assert.ErrMsg(t, err, `input not exists command "notExists"`)

err = app.RunWithArgs([]string{"--invalid"})
assert.ErrMsg(t, err, `provide undefined flag option "--invalid"`)

// run
err = app.RunWithArgs([]string{"demo", "-a", "230", "--name", "inhere", "val1"})
assert.NoErr(t, err)
assert.Eq(t, 230, c1Opts.age)
assert.Eq(t, "inhere", c1Opts.name)
assert.Eq(t, "val1", c1.Arg("arg1").String())
}
10 changes: 9 additions & 1 deletion cflag/cflag_test.go
Expand Up @@ -113,10 +113,18 @@ func TestCFlags_Parse(t *testing.T) {
})
c.IntVar(&opts.int, "int", 0, "this is a int option;false;i")

assert.PanicsMsg(t, func() {
c.AddShortcuts("notExist", "d,e")
}, "cflag: option 'notExist' is not registered")

// assert.PanicsMsg(t, func() {
// c.AddShortcuts("int", "i,n")
// }, "cflag: option 'notExist' is not registered")

osArgs := os.Args
os.Args = []string{"./myapp", "ag1", "ag2"}

c.MustParse(nil)
c.QuickRun()
assert.Eq(t, "[ag1 ag2]", fmt.Sprint(c.RemainArgs()))

os.Args = osArgs
Expand Down
2 changes: 1 addition & 1 deletion cliutil/read_windows.go
Expand Up @@ -14,7 +14,7 @@ func ReadPassword(question ...string) string {
print("Enter Password: ")
}

// on windows, must convert 'syscall.Stdin' to int
// on Windows, must convert 'syscall.Stdin' to int
bs, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
return ""
Expand Down

0 comments on commit 4e38f78

Please sign in to comment.