{"id":20608762,"url":"https://github.com/andeya/flagx","last_synced_at":"2025-04-15T04:24:23.293Z","repository":{"id":57505695,"uuid":"232224775","full_name":"andeya/flagx","owner":"andeya","description":"Standard flag package extension with more features, such as struct flag, app framework, etc.","archived":false,"fork":false,"pushed_at":"2021-02-02T03:25:06.000Z","size":149,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T15:51:48.894Z","etag":null,"topics":["flag","flag-app","flag-struct"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andeya.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-07T02:19:51.000Z","updated_at":"2022-02-27T17:30:19.000Z","dependencies_parsed_at":"2022-09-26T17:51:45.335Z","dependency_job_id":null,"html_url":"https://github.com/andeya/flagx","commit_stats":null,"previous_names":["henrylee2cn/flagx"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andeya%2Fflagx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andeya%2Fflagx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andeya%2Fflagx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andeya%2Fflagx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andeya","download_url":"https://codeload.github.com/andeya/flagx/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249004830,"owners_count":21196959,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flag","flag-app","flag-struct"],"created_at":"2024-11-16T10:11:48.224Z","updated_at":"2025-04-15T04:24:23.271Z","avatar_url":"https://github.com/andeya.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# flagx [![report card](https://goreportcard.com/badge/github.com/henrylee2cn/flagx?style=flat-square)](http://goreportcard.com/report/henrylee2cn/flagx) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](http://godoc.org/github.com/henrylee2cn/flagx)\n\nStandard flag package extension with more features, such as struct flag, app framework, etc.\n\n## Extension Feature\n\n- Add `const ContinueOnUndefined ErrorHandling`: ignore provided but undefined flags\n- Add `*FlagSet.StructVars`: define flags based on struct tags and bind to fields\n  - The list of supported types is consistent with the standard package:\n    - `string`\n    - `bool`\n    - `int`\n    - `int64`\n    - `uint`\n    - `uint64`\n    - `float64`\n    - `time.Duration`\n- Add `LookupArgs`: lookup the value corresponding to a name directly from arguments\n- Provide application framework\n- Support define non-flag\n    - Use `?{index}` (such as `?0`, `?1`, `?2`) in struct tag to define non-flag\n- For more features, please open the issue\n\n## Test Demo\n\n- Ignore provided but undefined flags\n\n```go\nfunc TestContinueOnUndefined(t *testing.T) {\n\tvar args = []string{\"test\", \"-x=1\", \"-y\"}\n\tfs := NewFlagSet(args[0], ContinueOnError)\n\tfs.String(\"x\", \"\", \"\")\n\terr := fs.Parse(args[1:])\n\tassert.EqualError(t, err, \"flag provided but not defined: -y\")\n\tfs.Usage()\n\n\tfs = NewFlagSet(args[0], ContinueOnError|ContinueOnUndefined)\n\tx := fs.String(\"x\", \"\", \"\")\n\terr = fs.Parse(args[1:])\n\tassert.NoError(t, err)\n\tassert.Equal(t, \"1\", *x)\n}\n```\n\n- Define flags based on struct tags and bind to fields\n\n```go\nfunc ExampleStructVars() {\n\tos.Args = []string{\n\t\t\"go test\",\n\t\t\"-test.timeout\", \"30s\",\n\t\t\"-test.v\",\n\t\t\"-test.count\", \"1\",\n\t\t\"-test.run\", \"^(TestStructVars)$\",\n\t\t\"flag_test.go\",\n\t}\n\ttype Args struct {\n\t\tRun     string        `flag:\"test.run; def=.*; usage=function name pattern\"`\n\t\tTimeout time.Duration `flag:\"test.timeout\"`\n\t\tV       bool          `flag:\"test.v\"`\n\t\tX       int           `flag:\"def=10\"`\n\t\tY       string        `flag:\"?0\"` // the first non-flag\n\t}\n\tvar args Args\n\terr := StructVars(\u0026args)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tParse()\n\tfmt.Printf(\"%+v\\n\", args)\n\t// Output:\n\t// {Run:^(TestStructVars)$ Timeout:30s V:true X:10 Y:flag_test.go}\n}\n```\n\n- Lookup the value corresponding to a name directly from arguments\n\n```go\nfunc TestLookupArgs(t *testing.T) {\n\tvar args = []string{\"-run\", \"abc\", \"-t\", \"5s\", \"-Cool\", \"-N=1\", \"-x\"}\n\n\tv, ok := LookupArgs(args, \"run\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"abc\", v)\n\n\tv, ok = LookupArgs(args, \"t\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"5s\", v)\n\n\tv, ok = LookupArgs(args, \"Cool\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"\", v)\n\n\tv, ok = LookupArgs(args, \"N\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"1\", v)\n\n\tv, ok = LookupArgs(args, \"x\")\n\tassert.True(t, ok)\n\tassert.Equal(t, \"\", v)\n\n\tv, ok = LookupArgs(args, \"???\")\n\tassert.False(t, ok)\n\tassert.Equal(t, \"\", v)\n}\n```\n\n- Aapplication\n\n```go\npackage flagx_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\tvd \"github.com/bytedance/go-tagexpr/v2/validator\"\n\t\"github.com/henrylee2cn/flagx\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc ExampleApp() {\n\tapp := flagx.NewApp()\n\tapp.SetCmdName(\"testapp\")\n\tapp.SetDescription(\"this is a app for testing\")\n\tapp.SetAuthors([]flagx.Author{{\n\t\tName:  \"henrylee2cn\",\n\t\tEmail: \"henrylee2cn@gmail.com\",\n\t}})\n\tapp.SetValidator(func(v interface{}) error {\n\t\treturn vd.Validate(v)\n\t})\n\tapp.AddFilter(new(Filter1))\n\t// cmd: testapp a\n\tapp.AddSubaction(\"a\", \"subcommand a\", new(Action1))\n\tb := app.AddSubcommand(\"b\", \"subcommand b\", flagx.FilterFunc(Filter2))\n\t{\n\t\t// cmd: testapp b c\n\t\tb.AddSubaction(\"c\", \"subcommand c\", new(Action2))\n\t\t// cmd: testapp b d\n\t\tb.AddSubaction(\"d\", \"subcommand d\", flagx.ActionFunc(Action3))\n\t}\n\tapp.SetNotFound(func(c *flagx.Context) {\n\t\tfmt.Printf(\"NotFound: cmd=%q, uasge=%s\\n\", c.CmdPathString(), c.UsageText())\n\t})\n\n\tfmt.Println(app.UsageText())\n\n\t// test: testapp\n\t// not found\n\tstat := app.Exec(context.TODO(), []string{\"-g=flagx\", \"false\"})\n\tif !stat.OK() {\n\t\tpanic(stat)\n\t}\n\n\t// test: testapp a\n\tstat = app.Exec(context.TODO(), []string{\"-g=henry\", \"true\", \"a\", \"-id\", \"1\", \"~/m/n\"})\n\tif !stat.OK() {\n\t\tpanic(stat)\n\t}\n\n\t// test: testapp b\n\tstat = app.Exec(context.TODO(), []string{\"-g=flagx\", \"false\", \"b\"})\n\tif !stat.OK() {\n\t\tpanic(stat)\n\t}\n\n\t// test: testapp b c\n\t// not found\n\tstat = app.Exec(context.TODO(), []string{\"-g=flagx\", \"false\", \"b\", \"c\", \"name=henry\"})\n\tif !stat.OK() {\n\t\tpanic(stat)\n\t}\n\n\t// test: testapp b d\n\tstat = app.Exec(context.TODO(), []string{\"-g=flagx\", \"false\", \"b\", \"d\"})\n\tif !stat.OK() {\n\t\tpanic(stat)\n\t}\n\n\t// Output:\n\t// testapp - v0.0.1\n\t//\n\t// this is a app for testing\n\t//\n\t// USAGE:\n\t//   -g string\n\t//     \tglobal param g\n\t//   ?0 bool\n\t//     \tparam view\n\t//   $testapp a\n\t//     subcommand a\n\t//     -id int\n\t//       \tparam id\n\t//     ?0 string\n\t//       \tparam path\n\t//   $testapp b ...\n\t//     subcommand b\n\t//   $testapp b c\n\t//     subcommand c\n\t//     -name string\n\t//       \tparam name\n\t//   $testapp b d\n\t//     subcommand d\n\t//\n\t// AUTHOR:\n\t//   henrylee2cn \u003chenrylee2cn@gmail.com\u003e\n\t//\n\t// NotFound: cmd=\"testapp\", uasge=-g string\n\t//   \tglobal param g\n\t// ?0 bool\n\t//   \tparam view\n\t// $testapp a\n\t//   subcommand a\n\t//   -id int\n\t//     \tparam id\n\t//   ?0 string\n\t//     \tparam path\n\t// $testapp b ...\n\t//   subcommand b\n\t// $testapp b c\n\t//   subcommand c\n\t//   -name string\n\t//     \tparam name\n\t// $testapp b d\n\t//   subcommand d\n\t//\n\t// Filter1 start: args=[-g=henry true a -id 1 ~/m/n], G=henry\n\t// Action1: args=[-g=henry true a -id 1 ~/m/n], path=\"testapp a\", object=\u0026{ID:1 Path:~/m/n}\n\t// Filter1 end: args=[-g=henry true a -id 1 ~/m/n]\n\t// NotFound: cmd=\"testapp b\", uasge=$testapp b ...\n\t//   subcommand b\n\t// $testapp b c\n\t//   subcommand c\n\t//   -name string\n\t//     \tparam name\n\t// $testapp b d\n\t//   subcommand d\n\t//\n\t// Filter1 start: args=[-g=flagx false b c name=henry], V=false\n\t// Filter2 start: args=[-g=flagx false b c name=henry], start at=2020-02-13 13:48:15 +0800 CST\n\t// Action2: args=[-g=flagx false b c name=henry], path=\"testapp b c\", object=\u0026{Name:}\n\t// Filter2 end: args=[-g=flagx false b c name=henry], cost time=1µs\n\t// Filter1 end: args=[-g=flagx false b c name=henry]\n\t// Filter1 start: args=[-g=flagx false b d], V=false\n\t// Filter2 start: args=[-g=flagx false b d], start at=2020-02-13 13:48:15 +0800 CST\n\t// Action3: args=[-g=flagx false b d], path=\"testapp b d\"\n\t// Filter2 end: args=[-g=flagx false b d], cost time=1µs\n\t// Filter1 end: args=[-g=flagx false b d]\n}\n\ntype Filter1 struct {\n\tG string `flag:\"g;usage=global param g\"`\n\tV bool   `flag:\"?0;usage=param view\"`\n}\n\nfunc (f *Filter1) Filter(c *flagx.Context, next flagx.ActionFunc) {\n\tif f.V {\n\t\tfmt.Printf(\"Filter1 start: args=%+v, G=%s\\n\", c.Args(), f.G)\n\t} else {\n\t\tfmt.Printf(\"Filter1 start: args=%+v, V=%v\\n\", c.Args(), f.V)\n\t}\n\tdefer fmt.Printf(\"Filter1 end: args=%+v\\n\", c.Args())\n\tnext(c)\n}\n\nfunc Filter2(c *flagx.Context, next flagx.ActionFunc) {\n\tt := time.Unix(1581572895, 0)\n\tfmt.Printf(\n\t\t\"Filter2 start: args=%+v, start at=%v\\n\",\n\t\tc.Args(), t,\n\t)\n\tdefer func() {\n\t\tfmt.Printf(\n\t\t\t\"Filter2 end: args=%+v, cost time=%v\\n\",\n\t\t\tc.Args(), time.Unix(1581572895, 1000).Sub(t),\n\t\t)\n\t}()\n\tnext(c)\n}\n\ntype Action1 struct {\n\tID   int    `flag:\"id;usage=param id\" vd:\"@:$!=0; msg:'empty ID'\"`\n\tPath string `flag:\"?0;usage=param path\"`\n}\n\nfunc (a *Action1) Execute(c *flagx.Context) {\n\tfmt.Printf(\"Action1: args=%+v, path=%q, object=%+v\\n\", c.Args(), c.CmdPathString(), a)\n}\n\ntype Action2 struct {\n\tName string `flag:\"name;usage=param name\"`\n}\n\nfunc (a *Action2) Execute(c *flagx.Context) {\n\tfmt.Printf(\"Action2: args=%+v, path=%q, object=%+v\\n\", c.Args(), c.CmdPathString(), a)\n}\n\nfunc Action3(c *flagx.Context) {\n\tfmt.Printf(\"Action3: args=%+v, path=%q\\n\", c.Args(), c.CmdPathString())\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandeya%2Fflagx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandeya%2Fflagx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandeya%2Fflagx/lists"}