From db44b0f28f54fd7d013218ddc4f4ee33463b1ba8 Mon Sep 17 00:00:00 2001 From: Uwe Krueger Date: Tue, 1 Nov 2022 15:08:00 +0100 Subject: [PATCH] support of CLI option groups --- .../ocmcmds/common/inputs/inputtype.go | 4 +- cmds/ocm/commands/ocmcmds/common/resources.go | 33 +++-- docs/reference/ocm_add_references.md | 12 +- .../ocm_add_resource-configuration.md | 40 ++++-- docs/reference/ocm_add_resources.md | 40 ++++-- .../reference/ocm_add_source-configuration.md | 38 ++++-- docs/reference/ocm_add_sources.md | 38 ++++-- go.mod | 5 +- go.sum | 11 +- hack/generate-docs/md_docs.go | 29 +++- pkg/cobrautils/flag/labelledstring.go | 4 + pkg/cobrautils/flag/labelledvalue.go | 4 + pkg/cobrautils/flag/string_map.go | 126 ------------------ pkg/cobrautils/flag/string_map_test.go | 60 --------- pkg/cobrautils/flag/string_to_value.go | 99 ++++++++++++++ ...ue_map_test.go => string_to_value_test.go} | 12 +- pkg/cobrautils/flag/value_map.go | 87 ------------ pkg/cobrautils/flag/yaml.go | 4 + pkg/cobrautils/flagsets/configoptions.go | 12 ++ pkg/cobrautils/flagsets/configoptionset.go | 30 ++++- pkg/cobrautils/flagsets/types.go | 52 ++++++-- pkg/cobrautils/flagsets/utils.go | 13 ++ pkg/cobrautils/funcs.go | 8 ++ pkg/cobrautils/groups/flagusages.go | 86 ++++++++++++ pkg/cobrautils/template.go | 4 +- pkg/contexts/ocm/internal/accesstypes.go | 4 +- 26 files changed, 487 insertions(+), 368 deletions(-) delete mode 100644 pkg/cobrautils/flag/string_map.go delete mode 100644 pkg/cobrautils/flag/string_map_test.go create mode 100644 pkg/cobrautils/flag/string_to_value.go rename pkg/cobrautils/flag/{value_map_test.go => string_to_value_test.go} (81%) delete mode 100644 pkg/cobrautils/flag/value_map.go create mode 100644 pkg/cobrautils/groups/flagusages.go diff --git a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go index 6c536b682..0e4928242 100644 --- a/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go +++ b/cmds/ocm/commands/ocmcmds/common/inputs/inputtype.go @@ -152,7 +152,9 @@ type inputTypeScheme struct { func NewInputTypeScheme(defaultRepoDecoder runtime.TypedObjectDecoder) InputTypeScheme { var rt InputSpec scheme := runtime.MustNewDefaultScheme(&rt, &UnknownInputSpec{}, false, defaultRepoDecoder) - return &inputTypeScheme{scheme, flagsets.NewTypedConfigProvider("input", "blob input specification")} + prov := flagsets.NewTypedConfigProvider("input", "blob input specification") + prov.AddGroups("Input Specification Options") + return &inputTypeScheme{scheme, prov} } func (t *inputTypeScheme) AddKnownTypes(s InputTypeScheme) { diff --git a/cmds/ocm/commands/ocmcmds/common/resources.go b/cmds/ocm/commands/ocmcmds/common/resources.go index c91e64cd1..98c5a0324 100644 --- a/cmds/ocm/commands/ocmcmds/common/resources.go +++ b/cmds/ocm/commands/ocmcmds/common/resources.go @@ -14,6 +14,8 @@ import ( "github.com/mandelsoft/vfs/pkg/vfs" "github.com/spf13/pflag" + "golang.org/x/text/cases" + "golang.org/x/text/language" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/validation/field" @@ -138,17 +140,19 @@ type ResourceMetaDataSpecificationsProvider struct { } func NewResourceMetaDataSpecificationsProvider(name string, adder flagsets.ConfigAdder, types ...flagsets.ConfigOptionType) *ResourceMetaDataSpecificationsProvider { + meta := flagsets.NewPlainConfigProvider(name, flagsets.ComposedAdder(addMeta(name), adder), + append(types, + flagsets.NewYAMLOptionType(name, fmt.Sprintf("%s meta data (yaml)", name)), + flagsets.NewStringOptionType("name", fmt.Sprintf("%s name", name)), + flagsets.NewStringOptionType("version", fmt.Sprintf("%s version", name)), + flagsets.NewStringMapOptionType("extra", fmt.Sprintf("%s extra identity", name)), + flagsets.NewValueMapOptionType("label", fmt.Sprintf("%s label (leading * indicates signature relevant, optional version separated by @)", name)), + )..., + ) + meta.AddGroups(cases.Title(language.English).String(fmt.Sprintf("%s meta data options", name))) a := &ResourceMetaDataSpecificationsProvider{ - typename: name, - metaProvider: flagsets.NewPlainConfigProvider(name, flagsets.ComposedAdder(addMeta(name), adder), - append(types, - flagsets.NewYAMLOptionType(name, fmt.Sprintf("%s meta data (yaml)", name)), - flagsets.NewStringOptionType("name", fmt.Sprintf("%s name", name)), - flagsets.NewStringOptionType("version", fmt.Sprintf("%s version", name)), - flagsets.NewStringMapOptionType("extra", fmt.Sprintf("%s extra identity", name)), - flagsets.NewValueMapOptionType("label", fmt.Sprintf("%s label (leading * indicates signature relevant, optional version separated by @)", name)), - )..., - ), + typename: name, + metaProvider: meta, } a.metaOptions = a.metaProvider.CreateOptions() return a @@ -259,14 +263,19 @@ or input fields of the description file format. func (a *ContentResourceSpecificationsProvider) AddFlags(fs *pflag.FlagSet) { a.ResourceMetaDataSpecificationsProvider.AddFlags(fs) + acctypes := a.ctx.OCMContext().AccessMethods().ConfigTypeSetConfigProvider() + inptypes := inputs.For(a.ctx).ConfigTypeSetConfigProvider() + set := flagsets.NewConfigOptionSet("resources") - set.AddAll(a.ctx.OCMContext().AccessMethods().ConfigTypeSetConfigProvider()) - dup, err := set.AddAll(inputs.For(a.ctx).ConfigTypeSetConfigProvider()) + set.AddAll(acctypes) + dup, err := set.AddAll(inptypes) if err != nil { panic(err) } a.shared = dup a.options = set.CreateOptions() + a.options.AddTypeSetGroupsToOptions(acctypes) + a.options.AddTypeSetGroupsToOptions(inptypes) a.options.AddFlags(fs) } diff --git a/docs/reference/ocm_add_references.md b/docs/reference/ocm_add_references.md index 1d3ae0a94..d8dff644c 100644 --- a/docs/reference/ocm_add_references.md +++ b/docs/reference/ocm_add_references.md @@ -10,14 +10,20 @@ ocm add references [] { | =} ``` --addenv access environment for templating + -h, --help help for references + -s, --settings stringArray settings file with variable settings (yaml) + --templater string templater to use (subst, spiff, go) (default "subst") +``` + + +#### Reference Meta Data Options + +``` --component string component name --extra = reference extra identity (default []) - -h, --help help for references --label = reference label (leading * indicates signature relevant, optional version separated by @) --name string reference name --reference YAML reference meta data (yaml) - -s, --settings stringArray settings file with variable settings (yaml) - --templater string templater to use (subst, spiff, go) (default "subst") --version string reference version ``` diff --git a/docs/reference/ocm_add_resource-configuration.md b/docs/reference/ocm_add_resource-configuration.md index a17d07d0a..83a9dcf37 100644 --- a/docs/reference/ocm_add_resource-configuration.md +++ b/docs/reference/ocm_add_resource-configuration.md @@ -8,20 +8,37 @@ ocm add resource-configuration [] { | == resource extra identity (default []) --globalAccess YAML access specification for global access - -h, --help help for resource-configuration + --hint string (repository) hint for local artifacts + --mediaType string media type for artifact blob representation + --reference string reference name + --region string region name + --size int blob size +``` + + +#### Input Specification Options + +``` --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) --inputCompress compress option for input @@ -35,15 +52,18 @@ ocm add resource-configuration [] { | == resource label (leading * indicates signature relevant, optional version separated by @) --mediaType string media type for artifact blob representation +``` + + +#### Resource Meta Data Options + +``` + --external flag non-local resource + --extra = resource extra identity (default []) + --label = resource label (leading * indicates signature relevant, optional version separated by @) --name string resource name - --reference string reference name - --region string region name --resource YAML resource meta data (yaml) - -s, --settings stringArray settings file with variable settings (yaml) - --size int blob size - --templater string templater to use (subst, spiff, go) (default "none") --type string resource type --version string resource version ``` diff --git a/docs/reference/ocm_add_resources.md b/docs/reference/ocm_add_resources.md index 69eb3af05..0b3a753e6 100644 --- a/docs/reference/ocm_add_resources.md +++ b/docs/reference/ocm_add_resources.md @@ -8,20 +8,37 @@ ocm add resources [] { | =} ### Options +``` + --addenv access environment for templating + -h, --help help for resources + -s, --settings stringArray settings file with variable settings (yaml) + --templater string templater to use (subst, spiff, go) (default "subst") +``` + + +#### Access Specification Options + ``` --access YAML blob access specification (YAML) --accessHostname string hostname used for access --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification - --addenv access environment for templating --bucket string bucket name --commit string git commit id --digest string blob digest - --external flag non-local resource - --extra = resource extra identity (default []) --globalAccess YAML access specification for global access - -h, --help help for resources + --hint string (repository) hint for local artifacts + --mediaType string media type for artifact blob representation + --reference string reference name + --region string region name + --size int blob size +``` + + +#### Input Specification Options + +``` --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) --inputCompress compress option for input @@ -35,15 +52,18 @@ ocm add resources [] { | =} --inputValues YAML YAML based generic values for inputs --inputVariants stringArray (platform) variants for inputs --inputVersion stringArray version info for inputs - --label = resource label (leading * indicates signature relevant, optional version separated by @) --mediaType string media type for artifact blob representation +``` + + +#### Resource Meta Data Options + +``` + --external flag non-local resource + --extra = resource extra identity (default []) + --label = resource label (leading * indicates signature relevant, optional version separated by @) --name string resource name - --reference string reference name - --region string region name --resource YAML resource meta data (yaml) - -s, --settings stringArray settings file with variable settings (yaml) - --size int blob size - --templater string templater to use (subst, spiff, go) (default "subst") --type string resource type --version string resource version ``` diff --git a/docs/reference/ocm_add_source-configuration.md b/docs/reference/ocm_add_source-configuration.md index 291e5dfce..849dfeccc 100644 --- a/docs/reference/ocm_add_source-configuration.md +++ b/docs/reference/ocm_add_source-configuration.md @@ -8,19 +8,37 @@ ocm add source-configuration [] { | =} ### Options +``` + --addenv access environment for templating + -h, --help help for source-configuration + -s, --settings stringArray settings file with variable settings (yaml) + --templater string templater to use (subst, spiff, go) (default "none") +``` + + +#### Access Specification Options + ``` --access YAML blob access specification (YAML) --accessHostname string hostname used for access --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification - --addenv access environment for templating --bucket string bucket name --commit string git commit id --digest string blob digest - --extra = source extra identity (default []) --globalAccess YAML access specification for global access - -h, --help help for source-configuration + --hint string (repository) hint for local artifacts + --mediaType string media type for artifact blob representation + --reference string reference name + --region string region name + --size int blob size +``` + + +#### Input Specification Options + +``` --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) --inputCompress compress option for input @@ -34,15 +52,17 @@ ocm add source-configuration [] { | =} --inputValues YAML YAML based generic values for inputs --inputVariants stringArray (platform) variants for inputs --inputVersion stringArray version info for inputs - --label = source label (leading * indicates signature relevant, optional version separated by @) --mediaType string media type for artifact blob representation +``` + + +#### Source Meta Data Options + +``` + --extra = source extra identity (default []) + --label = source label (leading * indicates signature relevant, optional version separated by @) --name string source name - --reference string reference name - --region string region name - -s, --settings stringArray settings file with variable settings (yaml) - --size int blob size --source YAML source meta data (yaml) - --templater string templater to use (subst, spiff, go) (default "none") --type string source type --version string source version ``` diff --git a/docs/reference/ocm_add_sources.md b/docs/reference/ocm_add_sources.md index 10e4622e4..d95338a66 100644 --- a/docs/reference/ocm_add_sources.md +++ b/docs/reference/ocm_add_sources.md @@ -8,19 +8,37 @@ ocm add sources [] { | =} ### Options +``` + --addenv access environment for templating + -h, --help help for sources + -s, --settings stringArray settings file with variable settings (yaml) + --templater string templater to use (subst, spiff, go) (default "subst") +``` + + +#### Access Specification Options + ``` --access YAML blob access specification (YAML) --accessHostname string hostname used for access --accessRepository string repository URL --accessType string type of blob access specification --accessVersion string version for access specification - --addenv access environment for templating --bucket string bucket name --commit string git commit id --digest string blob digest - --extra = source extra identity (default []) --globalAccess YAML access specification for global access - -h, --help help for sources + --hint string (repository) hint for local artifacts + --mediaType string media type for artifact blob representation + --reference string reference name + --region string region name + --size int blob size +``` + + +#### Input Specification Options + +``` --hint string (repository) hint for local artifacts --input YAML blob input specification (YAML) --inputCompress compress option for input @@ -34,15 +52,17 @@ ocm add sources [] { | =} --inputValues YAML YAML based generic values for inputs --inputVariants stringArray (platform) variants for inputs --inputVersion stringArray version info for inputs - --label = source label (leading * indicates signature relevant, optional version separated by @) --mediaType string media type for artifact blob representation +``` + + +#### Source Meta Data Options + +``` + --extra = source extra identity (default []) + --label = source label (leading * indicates signature relevant, optional version separated by @) --name string source name - --reference string reference name - --region string region name - -s, --settings stringArray settings file with variable settings (yaml) - --size int blob size --source YAML source meta data (yaml) - --templater string templater to use (subst, spiff, go) (default "subst") --type string source type --version string source version ``` diff --git a/go.mod b/go.mod index 9dea27aaf..6376423f4 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module github.com/open-component-model/ocm go 1.18 -replace github.com/spf13/cobra => github.com/mandelsoft/cobra v1.4.1-0.20220816123314-fd3b703d78bf +replace ( + github.com/spf13/cobra => github.com/mandelsoft/cobra v1.4.1-0.20220816123314-fd3b703d78bf + github.com/spf13/pflag => github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5 +) require ( github.com/docker/cli v20.10.17+incompatible diff --git a/go.sum b/go.sum index eb3248834..621678c83 100644 --- a/go.sum +++ b/go.sum @@ -815,10 +815,10 @@ github.com/mandelsoft/cobra v1.4.1-0.20220816123314-fd3b703d78bf/go.mod h1:IOw/A github.com/mandelsoft/filepath v0.0.0-20200909114706-3df73d378d55/go.mod h1:n4xEiUD2HNHnn2w5ZKF0qgjDecHVCWAl5DxZ7+pcFU8= github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68 h1:99GWPlKVS110Cm+/YRVRaUJN5CpTeWFazWg2sXJdf70= github.com/mandelsoft/filepath v0.0.0-20220503095057-4432a2285b68/go.mod h1:n4xEiUD2HNHnn2w5ZKF0qgjDecHVCWAl5DxZ7+pcFU8= -github.com/mandelsoft/logging v0.0.0-20221008125016-927a97d3006c h1:ZQV4QUvXKbbScq83qg1q2EILSP2ht1JvcVEMOoMaJtc= -github.com/mandelsoft/logging v0.0.0-20221008125016-927a97d3006c/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= github.com/mandelsoft/logging v0.0.0-20221012190501-e17f7961076e h1:vhm0x0KCMwjyrm/WkFXYw68QCWCBVzL1HVnOk0uOJa0= github.com/mandelsoft/logging v0.0.0-20221012190501-e17f7961076e/go.mod h1:vxGpzOesdqLGRSWb4fz5DaH0ekJvkISst4qq6UZ2xFA= +github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5 h1:yt0I6oxpTzxAPh+lwUinBiWibbmGXvQJdGzdu0sNMAM= +github.com/mandelsoft/pflag v0.0.0-20221101220350-bbff7bc4f7b5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/mandelsoft/spiff v1.7.0-beta-3 h1:AvZldpnctpyfQqtAA5uxokD5rlCK52mGAXxg7tnW5Ag= github.com/mandelsoft/spiff v1.7.0-beta-3/go.mod h1:3Kg6qrggWO4oc1k++acd6xhcWisJ2YkzxpVZpuYONGg= github.com/mandelsoft/vfs v0.0.0-20201002080026-d03d33d5889a/go.mod h1:74aV7kulg9C434HiI3zNALN79QHc9IZMN+SI4UdLn14= @@ -1111,13 +1111,6 @@ github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1: github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= diff --git a/hack/generate-docs/md_docs.go b/hack/generate-docs/md_docs.go index ca95ca602..4ea853c78 100644 --- a/hack/generate-docs/md_docs.go +++ b/hack/generate-docs/md_docs.go @@ -14,26 +14,43 @@ import ( "strings" "time" + "github.com/open-component-model/ocm/pkg/cobrautils/groups" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/open-component-model/ocm/pkg/cobrautils" ) +func printOptionGroups(buf *bytes.Buffer, title string, flags *pflag.FlagSet) { + buf.WriteString(fmt.Sprintf("### %s\n\n", title)) + groups := groups.GroupedFlagUsagesWrapped(flags, 0) + if len(groups) > 1 { + for _, g := range groups { + if g.Title != "" { + buf.WriteString("\n#### " + g.Title + "\n\n") + } + buf.WriteString("```\n") + buf.WriteString(g.Usages) + buf.WriteString("```\n\n") + } + } else { + buf.WriteString("```\n") + buf.WriteString(groups[0].Usages) + buf.WriteString("```\n\n") + } +} + func printOptions(buf *bytes.Buffer, cmd *cobra.Command, name string) error { flags := cmd.NonInheritedFlags() flags.SetOutput(buf) if flags.HasAvailableFlags() { - buf.WriteString("### Options\n\n```\n") - flags.PrintDefaults() - buf.WriteString("```\n\n") + printOptionGroups(buf, "Options", flags) } parentFlags := cmd.InheritedFlags() parentFlags.SetOutput(buf) if parentFlags.HasAvailableFlags() { - buf.WriteString("### Options inherited from parent commands\n\n```\n") - parentFlags.PrintDefaults() - buf.WriteString("```\n\n") + printOptionGroups(buf, "Options inherited from parent commands", parentFlags) } return nil } diff --git a/pkg/cobrautils/flag/labelledstring.go b/pkg/cobrautils/flag/labelledstring.go index 371d165c9..e7ddaec09 100644 --- a/pkg/cobrautils/flag/labelledstring.go +++ b/pkg/cobrautils/flag/labelledstring.go @@ -47,3 +47,7 @@ func (i *labelledStringValue) Type() string { func LabelledStringVarP(flags *pflag.FlagSet, p *LabelledString, name, shorthand string, value LabelledString, usage string) { flags.VarP(NewLabelledStringValue(value, p), name, shorthand, usage) } + +func LabelledStringVarPF(flags *pflag.FlagSet, p *LabelledString, name, shorthand string, value LabelledString, usage string) *pflag.Flag { + return flags.VarPF(NewLabelledStringValue(value, p), name, shorthand, usage) +} diff --git a/pkg/cobrautils/flag/labelledvalue.go b/pkg/cobrautils/flag/labelledvalue.go index ed643d94b..aa1b4de53 100644 --- a/pkg/cobrautils/flag/labelledvalue.go +++ b/pkg/cobrautils/flag/labelledvalue.go @@ -58,3 +58,7 @@ func (i *LabelledValueValue) Type() string { func LabelledValueVarP(flags *pflag.FlagSet, p *LabelledValue, name, shorthand string, value LabelledValue, usage string) { flags.VarP(NewLabelledValueValue(value, p), name, shorthand, usage) } + +func LabelledValueVarPF(flags *pflag.FlagSet, p *LabelledValue, name, shorthand string, value LabelledValue, usage string) *pflag.Flag { + return flags.VarPF(NewLabelledValueValue(value, p), name, shorthand, usage) +} diff --git a/pkg/cobrautils/flag/string_map.go b/pkg/cobrautils/flag/string_map.go deleted file mode 100644 index 022de01e9..000000000 --- a/pkg/cobrautils/flag/string_map.go +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package flag - -import ( - "bytes" - "encoding/csv" - "fmt" - "strings" - - "github.com/spf13/pflag" -) - -type stringMapValue struct { - value *map[string]string - changed bool -} - -func newStringSliceValue(val map[string]string, p *map[string]string) *stringMapValue { - ssv := new(stringMapValue) - ssv.value = p - *ssv.value = val - return ssv -} - -func readAsKSPairs(val string) (map[string]string, error) { - r := map[string]string{} - if val == "" { - return r, nil - } - stringReader := strings.NewReader(val) - csvReader := csv.NewReader(stringReader) - list, err := csvReader.Read() - if err != nil { - return nil, err - } - for _, e := range list { - k, v, err := parseAssignment(e) - if err != nil { - return nil, err - } - r[k] = v - } - return r, nil -} - -func writeAsKSPairs(vals map[string]string) (string, error) { - if vals == nil { - return "", nil - } - - var list []string - for k, v := range vals { - list = append(list, fmt.Sprintf("%s=%s", k, v)) - } - b := &bytes.Buffer{} - w := csv.NewWriter(b) - err := w.Write(list) - if err != nil { - return "", err - } - w.Flush() - return strings.TrimSuffix(b.String(), "\n"), nil -} - -func (s *stringMapValue) Set(val string) error { - m, err := readAsKSPairs(val) - if err != nil { - return err - } - if !s.changed { - *s.value = m - } else { - if *s.value == nil { - *s.value = map[string]string{} - } - for k, v := range m { - (*s.value)[k] = v - } - } - s.changed = true - return nil -} - -func (s *stringMapValue) Type() string { - return "=" -} - -func (s *stringMapValue) String() string { - str, _ := writeAsKSPairs(*s.value) - return "[" + str + "]" -} - -func (s *stringMapValue) GetMap() map[string]string { - return *s.value -} - -func StringMapVar(f *pflag.FlagSet, p *map[string]string, name string, value map[string]string, usage string) { - f.VarP(newStringSliceValue(value, p), name, "", usage) -} - -func StringMapVarP(f *pflag.FlagSet, p *map[string]string, name, shorthand string, value map[string]string, usage string) { - f.VarP(newStringSliceValue(value, p), name, shorthand, usage) -} - -func StringMap(f *pflag.FlagSet, name string, value map[string]string, usage string) *map[string]string { - p := map[string]string{} - StringMapVarP(f, &p, name, "", value, usage) - return &p -} - -func StringMapP(f *pflag.FlagSet, name, shorthand string, value map[string]string, usage string) *map[string]string { - p := map[string]string{} - StringMapVarP(f, &p, name, shorthand, value, usage) - return &p -} - -func parseAssignment(s string) (string, string, error) { - idx := strings.Index(s, "=") - if idx <= 0 { - return "", "", fmt.Errorf("expected =") - } - return s[:idx], s[idx+1:], nil -} diff --git a/pkg/cobrautils/flag/string_map_test.go b/pkg/cobrautils/flag/string_map_test.go deleted file mode 100644 index 48e4692f1..000000000 --- a/pkg/cobrautils/flag/string_map_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package flag - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/spf13/pflag" - - "github.com/open-component-model/ocm/pkg/testutils" -) - -var _ = Describe("string maps", func() { - var flags *pflag.FlagSet - - BeforeEach(func() { - flags = pflag.NewFlagSet("test", pflag.ContinueOnError) - }) - - It("handles map content", func() { - var flag map[string]string - StringMapVarP(flags, &flag, "flag", "", nil, "test flag") - - value := `a=b` - - Expect(flags.Parse([]string{"--flag", value})).To(Succeed()) - Expect(flag).To(Equal(map[string]string{"a": "b"})) - }) - - It("shows default", func() { - var flag map[string]string - StringMapVarP(flags, &flag, "flag", "", map[string]string{"x": "y"}, "test flag") - - Expect(flags.FlagUsages()).To(testutils.StringEqualTrimmedWithContext("--flag = test flag (default [x=y])")) - }) - - It("handles replaces default content", func() { - var flag map[string]string - StringMapVarP(flags, &flag, "flag", "", map[string]string{"x": "y"}, "test flag") - - value := `a=b` - - Expect(flags.Parse([]string{"--flag", value})).To(Succeed()) - Expect(flag).To(Equal(map[string]string{"a": "b"})) - }) - - It("rejects invalid assignment", func() { - var flag map[string]string - StringMapVarP(flags, &flag, "flag", "", nil, "test flag") - - value := `a` - - err := flags.Parse([]string{"--flag", value}) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal("invalid argument \"a\" for \"--flag\" flag: expected =")) - }) -}) diff --git a/pkg/cobrautils/flag/string_to_value.go b/pkg/cobrautils/flag/string_to_value.go new file mode 100644 index 000000000..c1e639dae --- /dev/null +++ b/pkg/cobrautils/flag/string_to_value.go @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package flag + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/spf13/pflag" +) + +type valueStringToValue struct { + value *map[string]interface{} + changed bool +} + +func newStringToValueValue(val map[string]interface{}, p *map[string]interface{}) *valueStringToValue { + ssv := new(valueStringToValue) + ssv.value = p + *ssv.value = val + return ssv +} + +func (s *valueStringToValue) Set(val string) error { + k, v, err := parseAssignment(val) + if err != nil { + return err + } + y, err := parseValue(v) + if err != nil { + return err + } + if !s.changed { + *s.value = map[string]interface{}{k: y} + } else { + if *s.value == nil { + *s.value = map[string]interface{}{} + } + (*s.value)[k] = y + } + s.changed = true + return nil +} + +func (s *valueStringToValue) Type() string { + return "=" +} + +func (s *valueStringToValue) String() string { + if *s.value == nil { + return "" + } + var list []string + for k, v := range *s.value { + //nolint: errchkjson // initialized by unmarshal + s, _ := json.Marshal(v) + list = append(list, fmt.Sprintf("%s=%s", k, string(s))) + } + return "[" + strings.Join(list, ", ") + "]" +} + +func (s *valueStringToValue) GetMap() map[string]interface{} { + return *s.value +} + +func StringToValueVar(f *pflag.FlagSet, p *map[string]interface{}, name string, value map[string]interface{}, usage string) { + f.VarP(newStringToValueValue(value, p), name, "", usage) +} + +func StringToValueVarP(f *pflag.FlagSet, p *map[string]interface{}, name, shorthand string, value map[string]interface{}, usage string) { + f.VarP(newStringToValueValue(value, p), name, shorthand, usage) +} + +func StringToValueVarPF(f *pflag.FlagSet, p *map[string]interface{}, name, shorthand string, value map[string]interface{}, usage string) *pflag.Flag { + return f.VarPF(newStringToValueValue(value, p), name, shorthand, usage) +} + +func StringToValue(f *pflag.FlagSet, name string, value map[string]interface{}, usage string) *map[string]interface{} { + p := map[string]interface{}{} + StringToValueVarP(f, &p, name, "", value, usage) + return &p +} + +func StringToValueP(f *pflag.FlagSet, name, shorthand string, value map[string]interface{}, usage string) *map[string]interface{} { + p := map[string]interface{}{} + StringToValueVarP(f, &p, name, shorthand, value, usage) + return &p +} + +func parseAssignment(s string) (string, string, error) { + idx := strings.Index(s, "=") + if idx <= 0 { + return "", "", fmt.Errorf("expected =") + } + return s[:idx], s[idx+1:], nil +} diff --git a/pkg/cobrautils/flag/value_map_test.go b/pkg/cobrautils/flag/string_to_value_test.go similarity index 81% rename from pkg/cobrautils/flag/value_map_test.go rename to pkg/cobrautils/flag/string_to_value_test.go index 9e76ee0de..a304283a1 100644 --- a/pkg/cobrautils/flag/value_map_test.go +++ b/pkg/cobrautils/flag/string_to_value_test.go @@ -22,7 +22,7 @@ var _ = Describe("value map", func() { It("handles simple map content", func() { var flag map[string]interface{} - ValueMapVarP(flags, &flag, "flag", "", nil, "test flag") + StringToValueVarP(flags, &flag, "flag", "", nil, "test flag") value := `a=b` @@ -32,7 +32,7 @@ var _ = Describe("value map", func() { It("handles generic yaml content", func() { var flag map[string]interface{} - ValueMapVarP(flags, &flag, "flag", "", nil, "test flag") + StringToValueVarP(flags, &flag, "flag", "", nil, "test flag") value := `a={"a":"va"}` @@ -42,14 +42,14 @@ var _ = Describe("value map", func() { It("shows default", func() { var flag map[string]interface{} - ValueMapVarP(flags, &flag, "flag", "", map[string]interface{}{"x": map[string]interface{}{"a": "b"}}, "test flag") + StringToValueVarP(flags, &flag, "flag", "", map[string]interface{}{"x": map[string]interface{}{"a": "b"}}, "test flag") Expect(flags.FlagUsages()).To(testutils.StringEqualTrimmedWithContext(`--flag = test flag (default [x={"a":"b"}])`)) }) It("handles replaces default content", func() { var flag map[string]interface{} - ValueMapVarP(flags, &flag, "flag", "", map[string]interface{}{"x": "y"}, "test flag") + StringToValueVarP(flags, &flag, "flag", "", map[string]interface{}{"x": "y"}, "test flag") value := `a=b` @@ -59,7 +59,7 @@ var _ = Describe("value map", func() { It("rejects invalid value", func() { var flag map[string]interface{} - ValueMapVarP(flags, &flag, "flag", "", nil, "test flag") + StringToValueVarP(flags, &flag, "flag", "", nil, "test flag") value := `a={"a":"va"` @@ -70,7 +70,7 @@ var _ = Describe("value map", func() { It("rejects invalid assignment", func() { var flag map[string]interface{} - ValueMapVarP(flags, &flag, "flag", "", nil, "test flag") + StringToValueVarP(flags, &flag, "flag", "", nil, "test flag") value := `a` diff --git a/pkg/cobrautils/flag/value_map.go b/pkg/cobrautils/flag/value_map.go deleted file mode 100644 index 491f96599..000000000 --- a/pkg/cobrautils/flag/value_map.go +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. -// -// SPDX-License-Identifier: Apache-2.0 - -package flag - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/spf13/pflag" -) - -type valueMapValue struct { - value *map[string]interface{} - changed bool -} - -func newValueMapValue(val map[string]interface{}, p *map[string]interface{}) *valueMapValue { - ssv := new(valueMapValue) - ssv.value = p - *ssv.value = val - return ssv -} - -func (s *valueMapValue) Set(val string) error { - k, v, err := parseAssignment(val) - if err != nil { - return err - } - y, err := parseValue(v) - if err != nil { - return err - } - if !s.changed { - *s.value = map[string]interface{}{k: y} - } else { - if *s.value == nil { - *s.value = map[string]interface{}{} - } - (*s.value)[k] = y - } - s.changed = true - return nil -} - -func (s *valueMapValue) Type() string { - return "=" -} - -func (s *valueMapValue) String() string { - if *s.value == nil { - return "" - } - var list []string - for k, v := range *s.value { - //nolint: errchkjson // initialized by unmarshal - s, _ := json.Marshal(v) - list = append(list, fmt.Sprintf("%s=%s", k, string(s))) - } - return "[" + strings.Join(list, ", ") + "]" -} - -func (s *valueMapValue) GetMap() map[string]interface{} { - return *s.value -} - -func ValueMapVar(f *pflag.FlagSet, p *map[string]interface{}, name string, value map[string]interface{}, usage string) { - f.VarP(newValueMapValue(value, p), name, "", usage) -} - -func ValueMapVarP(f *pflag.FlagSet, p *map[string]interface{}, name, shorthand string, value map[string]interface{}, usage string) { - f.VarP(newValueMapValue(value, p), name, shorthand, usage) -} - -func ValueMap(f *pflag.FlagSet, name string, value map[string]interface{}, usage string) *map[string]interface{} { - p := map[string]interface{}{} - ValueMapVarP(f, &p, name, "", value, usage) - return &p -} - -func VaueSliceP(f *pflag.FlagSet, name, shorthand string, value map[string]interface{}, usage string) *map[string]interface{} { - p := map[string]interface{}{} - ValueMapVarP(f, &p, name, shorthand, value, usage) - return &p -} diff --git a/pkg/cobrautils/flag/yaml.go b/pkg/cobrautils/flag/yaml.go index eb2f974e9..d99526188 100644 --- a/pkg/cobrautils/flag/yaml.go +++ b/pkg/cobrautils/flag/yaml.go @@ -53,6 +53,10 @@ func YAMLVarP[T any](flags *pflag.FlagSet, p *T, name, shorthand string, value T flags.VarP(NewYAMLValue(value, p), name, shorthand, usage) } +func YAMLVarPF[T any](flags *pflag.FlagSet, p *T, name, shorthand string, value T, usage string) *pflag.Flag { + return flags.VarPF(NewYAMLValue(value, p), name, shorthand, usage) +} + func parseValue(s string) (interface{}, error) { var v interface{} err := yaml.Unmarshal([]byte(s), &v) diff --git a/pkg/cobrautils/flagsets/configoptions.go b/pkg/cobrautils/flagsets/configoptions.go index 138c1bb59..2efb22a69 100644 --- a/pkg/cobrautils/flagsets/configoptions.go +++ b/pkg/cobrautils/flagsets/configoptions.go @@ -14,12 +14,18 @@ type Option interface { Name() string AddFlags(fs *pflag.FlagSet) Value() interface{} + + Changed() bool + AddGroups(groups ...string) } type Filter func(name string) bool type ConfigOptions interface { + AddTypeSetGroupsToOptions(set ConfigOptionTypeSet) + AddFlags(fs *pflag.FlagSet) + Check(set ConfigOptionTypeSet, desc string) error GetValue(name string) (interface{}, bool) Changed(names ...string) bool @@ -42,6 +48,12 @@ func NewOptions(opts []Option) ConfigOptions { return &configOptions{options: opts} } +func (o *configOptions) AddTypeSetGroupsToOptions(set ConfigOptionTypeSet) { + for _, opt := range o.options { + set.AddGroupsToOption(opt) + } +} + func (o *configOptions) GetValue(name string) (interface{}, bool) { for _, opt := range o.options { if opt.Name() == name { diff --git a/pkg/cobrautils/flagsets/configoptionset.go b/pkg/cobrautils/flagsets/configoptionset.go index fc5beec65..bfc0ccfd0 100644 --- a/pkg/cobrautils/flagsets/configoptionset.go +++ b/pkg/cobrautils/flagsets/configoptionset.go @@ -22,6 +22,8 @@ type ConfigOptionType interface { } type ConfigOptionTypeSet interface { + AddGroups(groups ...string) + Name() string OptionTypes() []ConfigOptionType @@ -42,6 +44,7 @@ type ConfigOptionTypeSet interface { Align(parent ConfigOptionTypeSet) error CreateOptions() ConfigOptions + AddGroupsToOption(o Option) } type configOptionTypeSet struct { @@ -49,7 +52,8 @@ type configOptionTypeSet struct { name string options map[string]ConfigOptionType sets map[string]ConfigOptionTypeSet - shared map[string]struct{} + shared map[string][]ConfigOptionTypeSet + groups []string parent ConfigOptionTypeSet } @@ -59,7 +63,7 @@ func NewConfigOptionSet(name string, types ...ConfigOptionType) ConfigOptionType name: name, options: map[string]ConfigOptionType{}, sets: map[string]ConfigOptionTypeSet{}, - shared: map[string]struct{}{}, + shared: map[string][]ConfigOptionTypeSet{}, } for _, t := range types { set.AddOptionType(t) @@ -67,6 +71,10 @@ func NewConfigOptionSet(name string, types ...ConfigOptionType) ConfigOptionType return set } +func (s *configOptionTypeSet) AddGroups(groups ...string) { + s.groups = AddGroups(s.groups, groups...) +} + func (s *configOptionTypeSet) Align(parent ConfigOptionTypeSet) error { s.lock.Lock() defer s.lock.Unlock() @@ -208,7 +216,7 @@ func (s *configOptionTypeSet) AddTypeSet(set ConfigOptionTypeSet) error { if old == nil { s.options[o.Name()] = o } - s.shared[o.Name()] = struct{}{} + s.shared[o.Name()] = append(s.shared[o.Name()], set) } finalize.Finalize() if err := set.Align(s); err != nil { @@ -225,6 +233,18 @@ func (s *configOptionTypeSet) GetTypeSet(name string) ConfigOptionTypeSet { return s.sets[name] } +func (s *configOptionTypeSet) AddGroupsToOption(o Option) { + if !s.HasOptionType(o.Name()) { + return + } + if len(s.groups) > 0 { + o.AddGroups(s.groups...) + } + for _, set := range s.shared[o.Name()] { + set.AddGroupsToOption(o) + } +} + func (s *configOptionTypeSet) CreateOptions() ConfigOptions { s.lock.RLock() defer s.lock.RUnlock() @@ -232,7 +252,9 @@ func (s *configOptionTypeSet) CreateOptions() ConfigOptions { var opts []Option for n := range s.options { - opts = append(opts, s.getOptionType(n).Create()) + opt := s.getOptionType(n).Create() + s.AddGroupsToOption(opt) + opts = append(opts, opt) } return NewOptions(opts) } diff --git a/pkg/cobrautils/flagsets/types.go b/pkg/cobrautils/flagsets/types.go index 1cbb8c3f1..f865f4f94 100644 --- a/pkg/cobrautils/flagsets/types.go +++ b/pkg/cobrautils/flagsets/types.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/pflag" "github.com/open-component-model/ocm/pkg/cobrautils/flag" + "github.com/open-component-model/ocm/pkg/cobrautils/groups" ) type TypeOptionBase struct { @@ -28,25 +29,52 @@ func (b *TypeOptionBase) Description() string { //////////////////////////////////////////////////////////////////////////////// type OptionBase struct { - otyp ConfigOptionType + otyp ConfigOptionType + flag *pflag.Flag + groups []string } func NewOptionBase(otyp ConfigOptionType) OptionBase { return OptionBase{otyp: otyp} } -func (b OptionBase) Type() ConfigOptionType { +func (b *OptionBase) Type() ConfigOptionType { return b.otyp } -func (b OptionBase) Name() string { +func (b *OptionBase) Name() string { return b.otyp.Name() } -func (b OptionBase) Description() string { +func (b *OptionBase) Description() string { return b.otyp.Description() } +func (b *OptionBase) Changed() bool { + return b.flag.Changed +} + +func (b *OptionBase) AddGroups(groups ...string) { + b.groups = AddGroups(b.groups, groups...) + b.addGroups() +} + +func (b *OptionBase) addGroups() { + if len(b.groups) == 0 || b.flag == nil { + return + } + if b.flag.Annotations == nil { + b.flag.Annotations = map[string][]string{} + } + list := b.flag.Annotations[groups.FlagGroupAnnotation] + b.flag.Annotations[groups.FlagGroupAnnotation] = AddGroups(list, b.groups...) +} + +func (b *OptionBase) TweakFlag(f *pflag.Flag) { + b.flag = f + b.addGroups() +} + //////////////////////////////////////////////////////////////////////////////// type StringOptionType struct { @@ -67,7 +95,7 @@ type StringOption struct { var _ Option = (*StringOption)(nil) func (o *StringOption) AddFlags(fs *pflag.FlagSet) { - fs.StringVarP(&o.value, o.otyp.Name(), "", "", o.otyp.Description()) + o.TweakFlag(fs.StringVarPF(&o.value, o.otyp.Name(), "", "", o.otyp.Description())) } func (o *StringOption) Value() interface{} { @@ -104,7 +132,7 @@ type StringArrayOption struct { var _ Option = (*StringArrayOption)(nil) func (o *StringArrayOption) AddFlags(fs *pflag.FlagSet) { - fs.StringArrayVarP(&o.value, o.otyp.Name(), "", nil, o.otyp.Description()) + o.TweakFlag(fs.StringArrayVarPF(&o.value, o.otyp.Name(), "", nil, o.otyp.Description())) } func (o *StringArrayOption) Value() interface{} { @@ -141,7 +169,7 @@ type BoolOption struct { var _ Option = (*BoolOption)(nil) func (o *BoolOption) AddFlags(fs *pflag.FlagSet) { - fs.BoolVarP(&o.value, o.otyp.Name(), "", false, o.otyp.Description()) + o.TweakFlag(fs.BoolVarPF(&o.value, o.otyp.Name(), "", false, o.otyp.Description())) } func (o *BoolOption) Value() interface{} { @@ -178,7 +206,7 @@ type IntOption struct { var _ Option = (*IntOption)(nil) func (o *IntOption) AddFlags(fs *pflag.FlagSet) { - fs.IntVarP(&o.value, o.otyp.Name(), "", 0, o.otyp.Description()) + o.TweakFlag(fs.IntVarPF(&o.value, o.otyp.Name(), "", 0, o.otyp.Description())) } func (o *IntOption) Value() interface{} { @@ -215,7 +243,7 @@ type YAMLOption struct { var _ Option = (*YAMLOption)(nil) func (o *YAMLOption) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description()) + o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description())) } func (o *YAMLOption) Value() interface{} { @@ -252,7 +280,7 @@ type ValueMapYAMLOption struct { var _ Option = (*ValueMapYAMLOption)(nil) func (o *ValueMapYAMLOption) AddFlags(fs *pflag.FlagSet) { - flag.YAMLVarP(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description()) + o.TweakFlag(flag.YAMLVarPF(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description())) } func (o *ValueMapYAMLOption) Value() interface{} { @@ -289,7 +317,7 @@ type ValueMapOption struct { var _ Option = (*ValueMapOption)(nil) func (o *ValueMapOption) AddFlags(fs *pflag.FlagSet) { - flag.ValueMapVarP(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description()) + o.TweakFlag(flag.StringToValueVarPF(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description())) } func (o *ValueMapOption) Value() interface{} { @@ -326,7 +354,7 @@ type StringMapOption struct { var _ Option = (*StringMapOption)(nil) func (o *StringMapOption) AddFlags(fs *pflag.FlagSet) { - flag.StringMapVarP(fs, &o.value, o.otyp.Name(), "", nil, o.otyp.Description()) + o.TweakFlag(fs.StringToStringVarPF(&o.value, o.otyp.Name(), "", nil, o.otyp.Description())) } func (o *StringMapOption) Value() interface{} { diff --git a/pkg/cobrautils/flagsets/utils.go b/pkg/cobrautils/flagsets/utils.go index df71b7a32..28c7a5f21 100644 --- a/pkg/cobrautils/flagsets/utils.go +++ b/pkg/cobrautils/flagsets/utils.go @@ -118,3 +118,16 @@ func ComposedAdder(adders ...ConfigAdder) ConfigAdder { } } } + +func AddGroups(list []string, groups ...string) []string { +outer: + for _, g := range groups { + for _, f := range list { + if g == f { + continue outer + } + } + list = append(list, g) + } + return list +} diff --git a/pkg/cobrautils/funcs.go b/pkg/cobrautils/funcs.go index 15d6eba00..0dba4bab2 100644 --- a/pkg/cobrautils/funcs.go +++ b/pkg/cobrautils/funcs.go @@ -7,8 +7,11 @@ package cobrautils import ( "strings" + "github.com/spf13/pflag" "golang.org/x/text/cases" "golang.org/x/text/language" + + "github.com/open-component-model/ocm/pkg/cobrautils/groups" ) var templatefuncs = map[string]interface{}{ @@ -17,6 +20,11 @@ var templatefuncs = map[string]interface{}{ "soleCommand": soleCommand, "title": cases.Title(language.English).String, "substituteCommandLinks": substituteCommandLinks, + "flagUsages": flagUsages, +} + +func flagUsages(fs *pflag.FlagSet) string { + return groups.FlagUsagesWrapped(fs, 0) } func substituteCommandLinks(desc string) string { diff --git a/pkg/cobrautils/groups/flagusages.go b/pkg/cobrautils/groups/flagusages.go new file mode 100644 index 000000000..dad783616 --- /dev/null +++ b/pkg/cobrautils/groups/flagusages.go @@ -0,0 +1,86 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package groups + +import ( + "bytes" + "fmt" + "strings" + + "github.com/spf13/pflag" + + "github.com/open-component-model/ocm/pkg/utils" +) + +const FlagGroupAnnotation = "flag-group-annotation" + +// FlagUsagesWrapped returns a string containing the usage information +// for all flags in the FlagSet. Wrapped to `cols` columns (0 for no +// wrapping) +// It groups flags according to group annotation. +func FlagUsagesWrapped(f *pflag.FlagSet, cols int) string { + lines := DetermineGroups(f, cols) + + sep := "" + buf := new(bytes.Buffer) + for _, g := range utils.StringMapKeys(lines) { + if g != "" { + fmt.Fprintln(buf, sep+" "+g+":") + } + sep = "\n" + for _, line := range lines[g] { + fmt.Fprintln(buf, line) + } + } + + return buf.String() +} + +type UsageGroup struct { + Title string + Usages string +} + +func GroupedFlagUsagesWrapped(f *pflag.FlagSet, cols int) []UsageGroup { + lines := DetermineGroups(f, cols) + + var groups []UsageGroup + for _, g := range utils.StringMapKeys(lines) { + buf := new(bytes.Buffer) + for _, line := range lines[g] { + fmt.Fprintln(buf, line) + } + groups = append(groups, UsageGroup{ + Title: g, + Usages: buf.String(), + }) + } + + return groups +} + +func DetermineGroups(f *pflag.FlagSet, cols int) map[string][]string { + lines := map[string][]string{} + for _, line := range strings.Split(f.FlagUsagesWrapped(cols), "\n") { + i := strings.Index(line, "--") + if i < 0 { + continue + } + + name := line[i+2 : i+strings.Index(line[i:], " ")] + flag := f.Lookup(name) + groups := []string{""} + if flag.Annotations != nil { + g := flag.Annotations[FlagGroupAnnotation] + if len(g) > 0 { + groups = g + } + } + for _, g := range groups { + lines[g] = append(lines[g], line) + } + } + return lines +} diff --git a/pkg/cobrautils/template.go b/pkg/cobrautils/template.go index 7f296b39e..e426f1d7f 100644 --- a/pkg/cobrautils/template.go +++ b/pkg/cobrautils/template.go @@ -17,7 +17,7 @@ Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "he {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if and .HasAvailableLocalFlags .IsAvailableCommand}} Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if and .HasAvailableInheritedFlags .IsAvailableCommand}} +{{ flagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if and .HasAvailableInheritedFlags .IsAvailableCommand}} Global Flags: {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}} @@ -47,7 +47,7 @@ Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "he {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} +{{ flagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} Global Flags: {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} diff --git a/pkg/contexts/ocm/internal/accesstypes.go b/pkg/contexts/ocm/internal/accesstypes.go index 4b130f7de..2262492e7 100644 --- a/pkg/contexts/ocm/internal/accesstypes.go +++ b/pkg/contexts/ocm/internal/accesstypes.go @@ -83,7 +83,9 @@ type accessTypeScheme struct { func NewAccessTypeScheme() AccessTypeScheme { var at AccessSpec scheme := runtime.MustNewDefaultScheme(&at, &UnknownAccessSpec{}, true, nil) - return &accessTypeScheme{scheme, flagsets.NewTypedConfigProvider("access", "blob access specification")} + prov := flagsets.NewTypedConfigProvider("access", "blob access specification") + prov.AddGroups("Access Specification Options") + return &accessTypeScheme{scheme, prov} } func (t *accessTypeScheme) AddKnownTypes(s AccessTypeScheme) {