diff --git a/.vscode/settings.json b/.vscode/settings.json index 754f664..f7f75ea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "bindnative", "bodyclose", "clif", + "cmds", "cobrass", "cogen", "colors", @@ -58,12 +59,14 @@ "rebinder", "refl", "repotoken", + "samber", "Selectf", "snivilised", "staticcheck", "structcheck", "stylecheck", "templatise", + "testcache", "thelper", "tmpl", "tparallel", @@ -72,7 +75,8 @@ "vactive", "Validatable", "varargs", - "varcheck" + "varcheck", + "watchv" ], "files.associations": { "**/*.tmpl": "go" diff --git a/Taskfile.yml b/Taskfile.yml index 97d93c3..71fe5fc 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -11,45 +11,61 @@ vars: DIST_DIR: ./dist tasks: + # === build ================================================ + b: cmds: - go build ./... - t: + clean: cmds: - - go test ./... + - go clean - dry: - cmds: - - ginkgo -v --dry-run ./... + # === test ================================================= - watchv: + t: cmds: - - ginkgo watch -v -r -p ./... + - go test ./... - watch: + clean-t: cmds: - - ginkgo watch -r -p ./... + - go clean -testcache - # initialise a test suite for a package. (only 1 per package) - boot: + dry: cmds: - - ginkgo bootstrap + - ginkgo -v --dry-run ./... # run tests suites recursive g: cmds: - ginkgo -r + # run tests suites recursive with verbose + gv: + cmds: + - ginkgo -r -v + # invoke as task gen -- gl: cmds: - ginkgo -r --label-filter={{.CLI_ARGS}} - # run tests suites recursive with verbose - gv: + # === watch ================================================ + + watchv: cmds: - - ginkgo -r -v + - ginkgo watch -v -r -p ./... + + watch: + cmds: + - ginkgo watch -r -p ./... + + # === ginkgo ================================================= + + # initialise a test suite for a package. (only 1 per package) + boot: + cmds: + - ginkgo bootstrap # generate a test file for the item provided (item_test.go) # invoke as task gen -- @@ -57,6 +73,8 @@ tasks: cmds: - ginkgo generate {{.CLI_ARGS}} + # === lint =================================================== + lint: cmds: - golangci-lint run @@ -99,7 +117,7 @@ tasks: cmds: - cobrass-gen -sign - # === build/deploy code generator =========================== + # === build/deploy code generator ============================ b-gen-linux: cmds: diff --git a/src/assistant/i18n/messages-command.go b/src/assistant/i18n/messages-command.go index 73fa146..50c74fa 100644 --- a/src/assistant/i18n/messages-command.go +++ b/src/assistant/i18n/messages-command.go @@ -28,13 +28,41 @@ func (td FilesRegExParamUsageTemplData) Message() *Message { } } -// FolderGlobParamUsageTemplData +// FilesExGlobParamUsageTemplData // 🧊 -type FolderGlobParamUsageTemplData struct { +type FilesExGlobParamUsageTemplData struct { CobrassTemplData } -func (td FolderGlobParamUsageTemplData) Message() *Message { +func (td FilesExGlobParamUsageTemplData) Message() *Message { + return &Message{ + ID: "files-ex-glob-filter.param-usage", + Description: "files extended glob filter (negate-able with leading !)", + Other: "files extended glob filter: | (negate-able with leading !)", + } +} + +// FoldersExGlobParamUsageTemplData +// 🧊 +type FoldersExGlobParamUsageTemplData struct { + CobrassTemplData +} + +func (td FoldersExGlobParamUsageTemplData) Message() *Message { + return &Message{ + ID: "folders-ex-glob-filter.param-usage", + Description: "folders extended glob filter (negate-able with leading !)", + Other: "folders extended glob filter: (negate-able with leading !)", + } +} + +// FoldersGlobParamUsageTemplData +// 🧊 +type FoldersGlobParamUsageTemplData struct { + CobrassTemplData +} + +func (td FoldersGlobParamUsageTemplData) Message() *Message { return &Message{ ID: "folders-glob-filter.param-usage", Description: "folders glob (negate-able with leading !)", @@ -42,13 +70,13 @@ func (td FolderGlobParamUsageTemplData) Message() *Message { } } -// FolderRexExParamUsageTemplData +// FoldersRexExParamUsageTemplData // 🧊 -type FolderRexExParamUsageTemplData struct { +type FoldersRexExParamUsageTemplData struct { CobrassTemplData } -func (td FolderRexExParamUsageTemplData) Message() *Message { +func (td FoldersRexExParamUsageTemplData) Message() *Message { return &Message{ ID: "folders-regex-filter.param-usage", Description: "folders regex filter (negate-able with leading !)", diff --git a/src/assistant/i18n/validation-messages.go b/src/assistant/i18n/messages-validation.go similarity index 90% rename from src/assistant/i18n/validation-messages.go rename to src/assistant/i18n/messages-validation.go index 210ad69..43b1dfb 100644 --- a/src/assistant/i18n/validation-messages.go +++ b/src/assistant/i18n/messages-validation.go @@ -4,6 +4,9 @@ import ( "github.com/snivilised/extendio/i18n" ) +// The code for these messages needs to be generated not hand coded. We +// should be able enter data into a static array and then generate the messages. + // These are user facing errors messages that occur due to // incorrect use of the cli application. They occur as a result // of validating the user provided options on the command line. @@ -467,3 +470,42 @@ func NewAtMostOptValidationError(flag string, value, threshold any) AtMostOptVal }, } } + +// ❌ InvalidExtendedGlobFilterTemplData + +// AtMostOptValidationTemplData +type InvalidExtendedGlobFilterTemplData struct { + CobrassTemplData + Delimiter string +} + +func (td InvalidExtendedGlobFilterTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "invalid-extended-glob-filter.cobrass", + Description: "Invalid extended glob filter definition (missing delimiter)", + Other: "option validation failed, glob filter definition missing delimiter '{{.Delimiter}}'", + } +} + +type InvalidExtendedGlobFilterBehaviourQuery interface { + error + IsInvalidExtendedGlobFilter() bool +} + +type InvalidExtendedGlobFilterValidation struct { + i18n.LocalisableError +} + +func (e InvalidExtendedGlobFilterValidation) IsInvalidExtendedGlobFilter() bool { + return true +} + +func NewInvalidExtendedGlobFilterValidationError(delimiter string) InvalidExtendedGlobFilterBehaviourQuery { + return &InvalidExtendedGlobFilterValidation{ + LocalisableError: i18n.LocalisableError{ + Data: InvalidExtendedGlobFilterTemplData{ + Delimiter: delimiter, + }, + }, + } +} diff --git a/src/store/families_test.go b/src/store/families_test.go index 7d114ea..a649f98 100644 --- a/src/store/families_test.go +++ b/src/store/families_test.go @@ -20,16 +20,14 @@ func reason(binder string, err error) string { ) } -const ( - shouldMessage = "🧪 should: bind all parameters without error" -) - +// --files(F) // --files-gb(G) // --files-rx(X) // --folders-gb(Z) -// --folders-rx(y) +// --folders-rx(Y) type familyTE struct { + given string familyType string persistent bool commandLine []string @@ -83,6 +81,14 @@ var _ = Describe("Families", Ordered, func() { DescribeTable("filter family", func(entry *familyTE) { + defer func() { + r := recover() + + if r != nil { + Fail(fmt.Sprintf("!!! 💥 %v\n", r)) + } + }() + switch entry.familyType { case "poly": { @@ -103,6 +109,7 @@ var _ = Describe("Families", Ordered, func() { ps.Native.BindAll(ps) } } + case "folders": { ps := assistant.NewParamSet[store.FoldersFilterParameterSet](rootCommand) @@ -112,16 +119,45 @@ var _ = Describe("Families", Ordered, func() { ps.Native.BindAll(ps) } } + + case "alloy": + { + ps := assistant.NewParamSet[store.AlloyFilterParameterSet](rootCommand) + if entry.persistent { + ps.Native.BindAll(ps, rootCommand.PersistentFlags()) + } else { + ps.Native.BindAll(ps) + } + } } execute(entry.commandLine) }, func(entry *familyTE) string { - return shouldMessage + return fmt.Sprintf("🧪 given: '%v', should: bind all parameters without error", entry.given) }, Entry( nil, &familyTE{ + given: "--files", + familyType: "files", + persistent: true, + commandLine: []string{"--files", "foo*"}, + }, + ), + Entry( + nil, + &familyTE{ + given: "-f", + familyType: "files", + commandLine: []string{"-X", "foo*"}, + }, + ), + // + Entry( + nil, + &familyTE{ + given: "--files-rx", familyType: "files", persistent: true, commandLine: []string{"--files-rx", "^foo"}, @@ -130,6 +166,7 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "--X", familyType: "files", commandLine: []string{"-X", "^foo"}, }, @@ -138,6 +175,7 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "--folders-gb", familyType: "folders", commandLine: []string{"--folders-gb", "bar*"}, }, @@ -145,6 +183,7 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "-Z", familyType: "folders", persistent: true, commandLine: []string{"-Z", "bar*"}, @@ -154,35 +193,72 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "--files --folders-gb", familyType: "poly", - commandLine: []string{"--files-rx", "^foo", "--folders-gb", "bar*"}, + commandLine: []string{"--files", "foo*", "--folders-gb", "bar*"}, }, ), Entry( nil, &familyTE{ + given: "-F -Z", familyType: "poly", - commandLine: []string{"-X", "^foo", "-Z", "bar*"}, + commandLine: []string{"-F", "foo*", "-Z", "bar*"}, }, ), Entry( nil, &familyTE{ + given: "--files --folders-rx", familyType: "poly", persistent: true, - commandLine: []string{"--files-gb", "foo*", "--folders-rx", "^bar"}, + commandLine: []string{"--files", "foo*", "--folders-rx", "^bar"}, }, ), Entry( nil, &familyTE{ + given: "-F -Y", familyType: "poly", persistent: true, - commandLine: []string{"-G", "foo*", "-Y", "^bar"}, + commandLine: []string{"-F", "foo*", "-Y", "^bar"}, }, ), // - + Entry( + nil, + &familyTE{ + given: "--files", + familyType: "alloy", + persistent: true, + commandLine: []string{"--files", "foo*|jpg,txt"}, + }, + ), + Entry( + nil, + &familyTE{ + given: "-F", + familyType: "alloy", + commandLine: []string{"-F", "foo*"}, + }, + ), + Entry( + nil, + &familyTE{ + given: "--folders-gb", + familyType: "alloy", + persistent: true, + commandLine: []string{"--folders-gb", "foo*"}, + }, + ), + Entry( + nil, + &familyTE{ + given: "-Z", + familyType: "alloy", + commandLine: []string{"-Z", "foo*"}, + }, + ), ) DescribeTable("worker pool family", @@ -197,11 +273,12 @@ var _ = Describe("Families", Ordered, func() { execute(entry.commandLine) }, func(entry *familyTE) string { - return shouldMessage + return fmt.Sprintf("🧪 given: '%v', should: bind all parameters without error", entry.given) }, Entry( nil, &familyTE{ + given: "--cpu", commandLine: []string{"--cpu"}, persistent: true, }, @@ -209,6 +286,7 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "--now", commandLine: []string{"--now", "4"}, persistent: true, }, @@ -227,17 +305,19 @@ var _ = Describe("Families", Ordered, func() { execute(entry.commandLine) }, func(entry *familyTE) string { - return shouldMessage + return fmt.Sprintf("🧪 given: '%v', should: bind all parameters without error", entry.given) }, Entry( nil, &familyTE{ + given: "--profile", commandLine: []string{"--profile", "foo"}, }, ), Entry( nil, &familyTE{ + given: "-P", commandLine: []string{"-P", "foo"}, persistent: true, }, @@ -245,12 +325,14 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "--scheme", commandLine: []string{"--scheme", "foo"}, }, ), Entry( nil, &familyTE{ + given: "-S", commandLine: []string{"-S", "foo"}, persistent: true, }, @@ -269,11 +351,12 @@ var _ = Describe("Families", Ordered, func() { execute(entry.commandLine) }, func(entry *familyTE) string { - return shouldMessage + return fmt.Sprintf("🧪 given: '%v', should: bind all parameters without error", entry.given) }, Entry( nil, &familyTE{ + given: "--dry-run", commandLine: []string{"--dry-run"}, persistent: true, }, @@ -281,6 +364,7 @@ var _ = Describe("Families", Ordered, func() { Entry( nil, &familyTE{ + given: "-D", commandLine: []string{"-D"}, }, ), @@ -298,11 +382,12 @@ var _ = Describe("Families", Ordered, func() { execute(entry.commandLine) }, func(entry *familyTE) string { - return shouldMessage + return fmt.Sprintf("🧪 given: '%v', should: bind all parameters without error", entry.given) }, Entry( nil, &familyTE{ + given: "--language", commandLine: []string{"--language", "en-GB"}, persistent: true, }, @@ -343,23 +428,26 @@ var _ = Describe("Families", Ordered, func() { execute(entry.commandLine) }, func(entry *familyTE) string { - return shouldMessage + return fmt.Sprintf("🧪 given: '%v', should: bind all parameters without error", entry.given) }, Entry( nil, &familyTE{ + given: "--depth", commandLine: []string{"--depth", "3"}, }, ), Entry( nil, &familyTE{ + given: "--skim", commandLine: []string{"--skim"}, }, ), Entry( nil, &familyTE{ + given: "-K", commandLine: []string{"-K"}, persistent: true, }, diff --git a/src/store/family-filter.go b/src/store/family-filter.go index 32539d3..aa59501 100644 --- a/src/store/family-filter.go +++ b/src/store/family-filter.go @@ -31,7 +31,13 @@ const ( // subscription type and in this case, --files-rx(x) and --files-gb(g) // are still free to be used without ambiguity. +// FilesFilterParameterSet represents a family of parameters that can be used +// to accept a file filter. files is considered the default as it is +// the most user friendly to use, as a glob is easier and more intuitive +// to use on the command line and supports (with te help of a delimiter) +// multiple extensions to be specified with a csv, in contrast to a regular glob. type FilesFilterParameterSet struct { + Files string FilesGlob string FilesRexEx string } @@ -40,6 +46,17 @@ func (f *FilesFilterParameterSet) BindAll( parent *assistant.ParamSet[FilesFilterParameterSet], flagSet ...*pflag.FlagSet, ) { + // --files(f) + // + parent.BindString( + resolveNewFlagInfo( + xi18n.Text(i18n.FilesExGlobParamUsageTemplData{}), + defaultFilterValue, + flagSet..., + ), + &parent.Native.Files, + ) + // --files-gb(G) // parent.BindString( @@ -66,9 +83,13 @@ func (f *FilesFilterParameterSet) BindAll( }, ) - parent.Command.MarkFlagsMutuallyExclusive("files-gb", "files-rx") + parent.Command.MarkFlagsMutuallyExclusive("files", "files-gb", "files-rx") } +// FoldersFilterParameterSet represents a family of parameters that can be used +// to accept a folder filter. In contrast to files, the folders family does +// not include an extended glob because folders do not contain extensions, +// so the regular glob will suffice. type FoldersFilterParameterSet struct { FoldersGlob string FoldersRexEx string @@ -82,7 +103,7 @@ func (f *FoldersFilterParameterSet) BindAll( // parent.BindString( resolveNewFlagInfo( - xi18n.Text(i18n.FolderGlobParamUsageTemplData{}), + xi18n.Text(i18n.FoldersGlobParamUsageTemplData{}), defaultFilterValue, flagSet..., ), @@ -93,7 +114,7 @@ func (f *FoldersFilterParameterSet) BindAll( // parent.BindValidatedString( resolveNewFlagInfo( - xi18n.Text(i18n.FolderRexExParamUsageTemplData{}), + xi18n.Text(i18n.FoldersRexExParamUsageTemplData{}), defaultFilterValue, flagSet..., ), @@ -107,8 +128,12 @@ func (f *FoldersFilterParameterSet) BindAll( parent.Command.MarkFlagsMutuallyExclusive("folders-gb", "folders-rx") } +// PolyFilterParameterSet represents a family of parameters that can be used +// to accept file and folder filters. This family is composed of files and +// filters. For files, either an extended glob or regex is supported. For +// folders, either a regular glob or regex is supported. type PolyFilterParameterSet struct { - FilesGlob string + Files string FilesRexEx string FoldersGlob string FoldersRexEx string @@ -118,19 +143,15 @@ func (f *PolyFilterParameterSet) BindAll( parent *assistant.ParamSet[PolyFilterParameterSet], flagSet ...*pflag.FlagSet, ) { - // argh, code smell here, because we're duplicating the functionality - // in FileFilterParameterSet and FoldersFilterParameterSet, but that can't - // be helped because of the paramSet instance is type specific. - // - // --files-gb(G) + // --files(f) // parent.BindString( resolveNewFlagInfo( - xi18n.Text(i18n.FilesGlobParamUsageTemplData{}), + xi18n.Text(i18n.FilesExGlobParamUsageTemplData{}), defaultFilterValue, flagSet..., ), - &parent.Native.FilesGlob, + &parent.Native.Files, ) // --files-rx(X) @@ -152,7 +173,7 @@ func (f *PolyFilterParameterSet) BindAll( // parent.BindString( resolveNewFlagInfo( - xi18n.Text(i18n.FolderGlobParamUsageTemplData{}), + xi18n.Text(i18n.FoldersGlobParamUsageTemplData{}), defaultFilterValue, flagSet..., ), @@ -163,7 +184,7 @@ func (f *PolyFilterParameterSet) BindAll( // parent.BindValidatedString( resolveNewFlagInfo( - xi18n.Text(i18n.FolderRexExParamUsageTemplData{}), + xi18n.Text(i18n.FoldersRexExParamUsageTemplData{}), defaultFilterValue, flagSet..., ), @@ -174,6 +195,41 @@ func (f *PolyFilterParameterSet) BindAll( }, ) - parent.Command.MarkFlagsMutuallyExclusive("files-gb", "files-rx") + parent.Command.MarkFlagsMutuallyExclusive("files", "files-rx") parent.Command.MarkFlagsMutuallyExclusive("folders-gb", "folders-rx") } + +// AlloyFilterParameterSet represents a family of parameters that can be used +// to accept file and folder filters. Files are represented by an extended glob +// and folders by a regular glob. +type AlloyFilterParameterSet struct { + Files string + FoldersGlob string +} + +func (f *AlloyFilterParameterSet) BindAll( + parent *assistant.ParamSet[AlloyFilterParameterSet], + flagSet ...*pflag.FlagSet, +) { + // --files(F) + // + parent.BindString( + resolveNewFlagInfo( + xi18n.Text(i18n.FilesExGlobParamUsageTemplData{}), + defaultFilterValue, + flagSet..., + ), + &parent.Native.Files, + ) + + // --folders-gb(Z) + // + parent.BindString( + resolveNewFlagInfo( + xi18n.Text(i18n.FoldersGlobParamUsageTemplData{}), + defaultFilterValue, + flagSet..., + ), + &parent.Native.FoldersGlob, + ) +} diff --git a/src/store/family.go b/src/store/family.go index a399fbd..132cd5f 100644 --- a/src/store/family.go +++ b/src/store/family.go @@ -25,6 +25,7 @@ var ShortFlags = FlagDefinitions{ // filter family // + "files": "F", "files-gb": "G", "files-rx": "X", "folders-gb": "Z",