From 6854ea47598f0e0ea1e169a0290d98023b686748 Mon Sep 17 00:00:00 2001 From: plastikfan Date: Tue, 5 Dec 2023 16:08:58 +0000 Subject: [PATCH] feat(proxy): validate sampler info (#48) --- src/app/command/bootstrap.go | 25 +++- src/app/command/root-cmd.go | 88 ++++++++++-- src/app/command/shrink-cmd.go | 45 +++++-- src/app/proxy/enter-shrink.go | 31 ++++- src/app/proxy/entry-base.go | 126 +++++++++--------- src/app/proxy/image-defs.go | 18 ++- src/app/proxy/sampler-config.go | 44 ++++++ src/app/proxy/sampler-runner_test.go | 69 +++++++++- src/i18n/messages-command.go | 56 ++++++++ .../{test-utilities.go => test-utils.go} | 5 + src/internal/helpers/utils.go | 26 ++++ test/data/configuration/pixa-test.yml | 13 +- 12 files changed, 440 insertions(+), 106 deletions(-) create mode 100644 src/app/proxy/sampler-config.go rename src/internal/helpers/{test-utilities.go => test-utils.go} (96%) create mode 100644 src/internal/helpers/utils.go diff --git a/src/app/command/bootstrap.go b/src/app/command/bootstrap.go index ff2033e..5fdc177 100644 --- a/src/app/command/bootstrap.go +++ b/src/app/command/bootstrap.go @@ -17,6 +17,7 @@ import ( "github.com/snivilised/extendio/xfs/utils" "github.com/snivilised/pixa/src/app/proxy" "github.com/snivilised/pixa/src/i18n" + "github.com/snivilised/pixa/src/internal/helpers" ) type LocaleDetector interface { @@ -41,7 +42,7 @@ func validatePositionalArgs(cmd *cobra.Command, args []string) error { return err } - directory := proxy.ResolvePath(args[0]) + directory := helpers.ResolvePath(args[0]) if !utils.Exists(directory) { return xi18n.NewPathNotFoundError("shrink directory", directory) @@ -63,7 +64,8 @@ type ConfigInfo struct { type Bootstrap struct { Container *assistant.CobraContainer optionsInfo ConfigureOptionsInfo - profiles proxy.ConfiguredProfiles + profiles proxy.ProfilesConfig + sampler proxy.SamplerConfig } type ConfigureOptionsInfo struct { @@ -123,7 +125,7 @@ func (b *Bootstrap) Root(options ...ConfigureOptionFn) *cobra.Command { fmt.Printf(" ===> ๐ŸŒท๐ŸŒท๐ŸŒท Root Command...\n") inputs := b.getRootInputs() - inputs.ParamSet.Native.Directory = proxy.ResolvePath(args[0]) + inputs.ParamSet.Native.Directory = helpers.ResolvePath(args[0]) if inputs.WorkerPoolFam.Native.CPU { inputs.WorkerPoolFam.Native.NoWorkers = 0 @@ -209,3 +211,20 @@ func handleLangSetting(config configuration.ViperConfig) { os.Exit(1) } } + +func (b *Bootstrap) viper() { + // Ideally, the ProfileParameterSet would perform a check against + // the config, but extendio is not aware of config, so it can't + // check. Instead, we can check here. + // + + nf := b.optionsInfo.Config.Viper.Get("no-files") + _ = nf + + b.profiles = b.optionsInfo.Config.Viper.GetStringMapStringSlice("profiles") + err := b.optionsInfo.Config.Viper.UnmarshalKey("sampler", &b.sampler) + + if err != nil { + panic(err) + } +} diff --git a/src/app/command/root-cmd.go b/src/app/command/root-cmd.go index 2458c0a..fc89be1 100644 --- a/src/app/command/root-cmd.go +++ b/src/app/command/root-cmd.go @@ -43,10 +43,80 @@ func (b *Bootstrap) buildRootCommand(container *assistant.CobraContainer) { ), Default: xi18n.DefaultLanguage.Get().String(), AlternativeFlagSet: rootCommand.PersistentFlags(), - }, ¶mSet.Native.Language, func(value string, _ *pflag.Flag) error { - _, err := language.Parse(value) - return err - }) + }, + ¶mSet.Native.Language, + func(value string, _ *pflag.Flag) error { + _, err := language.Parse(value) + return err + }) + + // --sample (pending: sampling-family) + // + paramSet.BindBool(&assistant.FlagInfo{ + Name: "sample", + Usage: i18n.LeadsWith( + "sample", + xi18n.Text(i18n.RootCmdSampleUsageTemplData{}), + ), + Default: false, + AlternativeFlagSet: rootCommand.PersistentFlags(), + }, + ¶mSet.Native.IsSampling, + ) + + const ( + defFSItems = uint(3) + minFSItems = uint(1) + maxFSItems = uint(128) + ) + + // --no-files (pending: sampling-family) + // + paramSet.BindValidatedUintWithin( + &assistant.FlagInfo{ + Name: "no-files", + Usage: i18n.LeadsWith( + "no-files", + xi18n.Text(i18n.RootCmdNoFilesUsageTemplData{}), + ), + Default: defFSItems, + AlternativeFlagSet: rootCommand.PersistentFlags(), + }, + ¶mSet.Native.NoFiles, + minFSItems, + maxFSItems, + ) + + // --no-folders (pending: sampling-family) + // + paramSet.BindValidatedUintWithin( + &assistant.FlagInfo{ + Name: "no-folders", + Usage: i18n.LeadsWith( + "no-folders", + xi18n.Text(i18n.RootCmdNoFoldersUsageTemplData{}), + ), + Default: defFSItems, + AlternativeFlagSet: rootCommand.PersistentFlags(), + }, + ¶mSet.Native.NoFolders, + minFSItems, + maxFSItems, + ) + + // --last (pending: sampling-family) + // + paramSet.BindBool(&assistant.FlagInfo{ + Name: "last", + Usage: i18n.LeadsWith( + "last", + xi18n.Text(i18n.RootCmdLastUsageTemplData{}), + ), + Default: false, + AlternativeFlagSet: rootCommand.PersistentFlags(), + }, + ¶mSet.Native.Last, + ) // family: preview [--dry-run(D)] // @@ -81,16 +151,6 @@ func (b *Bootstrap) buildRootCommand(container *assistant.CobraContainer) { container.MustRegisterParamSet(WorkerPoolFamName, workerPoolFam) container.MustRegisterParamSet(FoldersFamName, foldersFam) container.MustRegisterParamSet(ProfileFamName, profileFam) - - // This needs to be implemented in extendio, so we can't bind - // here with a validated binder, we just perform a check here - // after the bind as a temporary measure. Actually, this - // needs to be implemented inside cross field validation. - - // Ideally, the ProfileParameterSet would perform a check against - // the config, but extendio is not aware of config, so it can't - // check. Instead, we can put this check into - b.profiles = b.optionsInfo.Config.Viper.GetStringMapStringSlice("profiles") } func (b *Bootstrap) getRootInputs() *proxy.RootCommandInputs { diff --git a/src/app/command/shrink-cmd.go b/src/app/command/shrink-cmd.go index dfe659e..fa5b1eb 100644 --- a/src/app/command/shrink-cmd.go +++ b/src/app/command/shrink-cmd.go @@ -16,6 +16,7 @@ import ( "github.com/snivilised/pixa/src/app/proxy" "github.com/snivilised/pixa/src/i18n" + "github.com/snivilised/pixa/src/internal/helpers" ) // We define all the options here, even the ones inherited from the root @@ -99,8 +100,9 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob }); xvErr == nil { options := []string{} present := make(cobrass.SpecifiedFlagsCollection) + flagSet := cmd.Flags() - cmd.Flags().Visit(func(f *pflag.Flag) { + flagSet.Visit(func(f *pflag.Flag) { options = append(options, fmt.Sprintf("--%v=%v", f.Name, f.Value)) if isThirdPartyKnown(f.Name, shrinkPS.Native.ThirdPartySet.KnownBy) { @@ -113,16 +115,16 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob AppEmoji, ApplicationName, options, strings.Join(args, "/"), ) - if cmd.Flags().Changed("gaussian-blur") { + if flagSet.Changed("gaussian-blur") { fmt.Printf("๐Ÿ’  Blur defined with value: '%v'\n", cmd.Flag("gaussian-blur").Value) } - if cmd.Flags().Changed("sampling-factor") { + if flagSet.Changed("sampling-factor") { fmt.Printf("๐Ÿ’  Blur defined with value: '%v'\n", cmd.Flag("sampling-factor").Value) } inputs := b.getShrinkInputs() - inputs.RootInputs.ParamSet.Native.Directory = proxy.ResolvePath(args[0]) + inputs.RootInputs.ParamSet.Native.Directory = helpers.ResolvePath(args[0]) // validate the profile // @@ -131,6 +133,29 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob return err } + // validate the scheme + // + if err := b.sampler.Validate( + inputs.ParamSet.Native.Scheme, + &b.profiles, + ); err != nil { + return err + } + + // Apply fallbacks, ie user didn't specify flag on command line + // so fallback to one defined in config. This is supposed to + // work transparently with Viper, but this does work with + // custom locations; ie no-files is defined under sampler, but + // viper would expect to see it at the root. Even so, still found + // that viper would still fail to pick up this value, so implementing + // the fall back manually here. + // + if inputs.RootInputs.ParamSet.Native.IsSampling { + if !flagSet.Changed("no-files") { + inputs.RootInputs.ParamSet.Native.NoFiles = b.sampler.Files + } + } + appErr = proxy.EnterShrink( inputs, b.optionsInfo.Program, @@ -196,13 +221,11 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob // -- scheme(S) // - const ( defaultScheme = "" ) - samplers := b.optionsInfo.Config.Viper.Get("samplers") - _ = samplers + sampler := b.optionsInfo.Config.Viper.Get("sampler") // can we validate that it is present in the config? paramSet.BindValidatedString( @@ -212,9 +235,9 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob ), ¶mSet.Native.Scheme, func(s string, f *pflag.Flag) error { - return lo.TernaryF(samplers != nil, + return lo.TernaryF(sampler != nil, func() error { return nil }, - func() error { return fmt.Errorf("samplers not found (%v)", s) }, + func() error { return fmt.Errorf("sampler not found (%v)", s) }, ) }, ) @@ -334,7 +357,9 @@ func (b *Bootstrap) buildShrinkCommand(container *assistant.CobraContainer) *cob "quality": "q", } - // ๐Ÿ“ŒA note about cobra args validation: cmd.ValidArgs lets you define + b.viper() + + // ๐Ÿ“Œ A note about cobra args validation: cmd.ValidArgs lets you define // a list of all allowable tokens for positional args. Just define // ValidArgs, eg: // shrinkCommand.ValidArgs = []string{"foo", "bar", "baz"} diff --git a/src/app/proxy/enter-shrink.go b/src/app/proxy/enter-shrink.go index 8c8993c..08081ba 100644 --- a/src/app/proxy/enter-shrink.go +++ b/src/app/proxy/enter-shrink.go @@ -17,7 +17,6 @@ import ( type ShrinkEntry struct { EntryBase Inputs *ShrinkCommandInputs - jobs []string } func FilenameWithoutExtension(name string) string { @@ -51,6 +50,30 @@ func (e *ShrinkEntry) LookAheadOptionsFn(o *nav.TraverseOptions) { return nil }, } + + switch { + case e.Inputs.FilesFam.Native.FilesGlob != "": + pattern := e.Inputs.FilesFam.Native.FilesGlob + o.Store.FilterDefs = &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeGlobEn, + Description: fmt.Sprintf("--files-gb(G): '%v'", pattern), + Pattern: pattern, + Scope: nav.ScopeFileEn, + }, + } + + case e.Inputs.FilesFam.Native.FilesRexEx != "": + pattern := e.Inputs.FilesFam.Native.FilesRexEx + o.Store.FilterDefs = &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeRegexEn, + Description: fmt.Sprintf("--files-rx(X): '%v'", pattern), + Pattern: pattern, + Scope: nav.ScopeFileEn, + }, + } + } } func (e *ShrinkEntry) PrincipalOptionsFn(o *nav.TraverseOptions) { @@ -142,8 +165,10 @@ func (e *ShrinkEntry) resumeFn(item *nav.TraverseItem) error { return runner.OnNewItem(item, clif.Expand(positional, e.ThirdPartyCL)...) } -func (e *ShrinkEntry) run(config configuration.ViperConfig) error { - _ = config +func (e *ShrinkEntry) run(_ configuration.ViperConfig) error { + if err := e.viper(); err != nil { + return err + } e.ThirdPartyCL = cobrass.Evaluate( e.Inputs.ParamSet.Native.ThirdPartySet.Present, diff --git a/src/app/proxy/entry-base.go b/src/app/proxy/entry-base.go index 1aa2791..0613a32 100644 --- a/src/app/proxy/entry-base.go +++ b/src/app/proxy/entry-base.go @@ -3,8 +3,6 @@ package proxy import ( "context" "fmt" - "os" - "path/filepath" "time" "github.com/samber/lo" @@ -37,6 +35,17 @@ type EntryBase struct { ThirdPartyCL cobrass.ThirdPartyCommandLine Options *nav.TraverseOptions Registry *RunnerRegistry + ProfilesCFG ProfilesConfig + SamplerCFG SamplerConfig +} + +func (e *EntryBase) viper() error { + // Ideally, the ProfileParameterSet would perform a check against + // the config, but extendio is not aware of config, so it can't + // check. Instead, we can check here. + // + e.ProfilesCFG = e.Config.GetStringMapStringSlice("profiles") + return e.Config.UnmarshalKey("sampler", &e.SamplerCFG) } func (e *EntryBase) ConfigureOptions(o *nav.TraverseOptions) { @@ -47,48 +56,65 @@ func (e *EntryBase) ConfigureOptions(o *nav.TraverseOptions) { // filter. // - switch { - case e.Inputs.FoldersFam.Native.FoldersGlob != "": - pattern := e.Inputs.FoldersFam.Native.FoldersGlob - o.Store.FilterDefs = &nav.FilterDefinitions{ - Node: nav.FilterDef{ - Type: nav.FilterTypeGlobEn, - Description: fmt.Sprintf("--folders-gb(Z): '%v'", pattern), - Pattern: pattern, - Scope: nav.ScopeFileEn, - }, + if o.Store.FilterDefs == nil { + switch { + case e.Inputs.FoldersFam.Native.FoldersGlob != "": + pattern := e.Inputs.FoldersFam.Native.FoldersGlob + o.Store.FilterDefs = &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeGlobEn, + Description: fmt.Sprintf("--folders-gb(Z): '%v'", pattern), + Pattern: pattern, + Scope: nav.ScopeFolderEn, + }, + } + + case e.Inputs.FoldersFam.Native.FoldersRexEx != "": + pattern := e.Inputs.FoldersFam.Native.FoldersRexEx + o.Store.FilterDefs = &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: nav.FilterTypeRegexEn, + Description: fmt.Sprintf("--folders-rx(Y): '%v'", pattern), + Pattern: pattern, + Scope: nav.ScopeFolderEn, + }, + } + + default: + // TODO: there is still confusion here. Why do we need to set up + // a default image filter in base, when base is only interested in folders? + // shouldn't this default just be in shrink, which is interested in files. + filterType := nav.FilterTypeRegexEn + description := "Default image types supported by pixa" + pattern := "\\.(jpe?g|png)$" + + o.Store.FilterDefs = &nav.FilterDefinitions{ + Node: nav.FilterDef{ + Type: filterType, + Description: description, + Pattern: pattern, + }, + Children: nav.CompoundFilterDef{ + Type: filterType, + Description: description, + Pattern: pattern, + }, + } } + } - case e.Inputs.FoldersFam.Native.FoldersRexEx != "": - pattern := e.Inputs.FoldersFam.Native.FoldersRexEx - o.Store.FilterDefs = &nav.FilterDefinitions{ - Node: nav.FilterDef{ - Type: nav.FilterTypeRegexEn, - Description: fmt.Sprintf("--folders-rx(Y): '%v'", pattern), - Pattern: pattern, - Scope: nav.ScopeFileEn, - }, + // setup sampling (sampling params needs to be defined on a new family in store) + // + if e.Inputs.ParamSet.Native.IsSampling { + o.Store.Sampling.SampleType = nav.SampleTypeFilterEn + o.Store.Sampling.SampleInReverse = e.Inputs.ParamSet.Native.Last + + if e.Inputs.ParamSet.Native.NoFiles > 0 { + o.Store.Sampling.NoOf.Files = e.Inputs.ParamSet.Native.NoFiles } - default: - // TODO: there is still confusion here. Why do we need to set up - // a default image filter in base, when base is only interested in folders? - // shouldn't this default just be in shrink, which is interested in files. - filterType := nav.FilterTypeRegexEn - description := "Default image types supported by pixa" - pattern := "\\.(jpe?g|png)$" - - o.Store.FilterDefs = &nav.FilterDefinitions{ - Node: nav.FilterDef{ - Type: filterType, - Description: description, - Pattern: pattern, - }, - Children: nav.CompoundFilterDef{ - Type: filterType, - Description: description, - Pattern: pattern, - }, + if e.Inputs.ParamSet.Native.NoFolders > 0 { + o.Store.Sampling.NoOf.Folders = e.Inputs.ParamSet.Native.NoFolders } } @@ -101,26 +127,6 @@ func (e *EntryBase) ConfigureOptions(o *nav.TraverseOptions) { }) } -func ResolvePath(path string) string { - if path == "" { - return path - } - - result := path - - if result[0] == '~' { - if h, err := os.UserHomeDir(); err == nil { - result = filepath.Join(h, result[1:]) - } - } else { - if absolute, absErr := filepath.Abs(path); absErr == nil { - result = absolute - } - } - - return result -} - func (e *EntryBase) readProfile3rdPartyFlags() cobrass.ThirdPartyCommandLine { profile := e.Inputs.ProfileFam.Native.Profile fmt.Printf("------> ๐Ÿ’ฆ๐Ÿ’ฆ๐Ÿ’ฆ readProfile3rdPartyFlags: '%v'\n", profile) diff --git a/src/app/proxy/image-defs.go b/src/app/proxy/image-defs.go index 831bf96..4fbe4b9 100644 --- a/src/app/proxy/image-defs.go +++ b/src/app/proxy/image-defs.go @@ -9,16 +9,20 @@ import ( ) type RootParameterSet struct { // should contain RootCommandInputs - Directory string - Language string // TODO: move this to family store + Directory string + IsSampling bool + NoFiles uint + NoFolders uint + Last bool + Language string // TODO: move this to family store } -type ConfiguredProfiles map[string][]string +type ProfilesConfig map[string][]string -func (ps ConfiguredProfiles) Validate(profile string) error { - if profile != "" { - if _, found := ps[profile]; !found { - return fmt.Errorf("no such profile: '%v'", profile) +func (ps ProfilesConfig) Validate(name string) error { + if name != "" { + if _, found := ps[name]; !found { + return fmt.Errorf("no such profile: '%v'", name) } } diff --git a/src/app/proxy/sampler-config.go b/src/app/proxy/sampler-config.go new file mode 100644 index 0000000..aa51c3a --- /dev/null +++ b/src/app/proxy/sampler-config.go @@ -0,0 +1,44 @@ +package proxy + +import ( + "fmt" +) + +type ( + SchemeConfig struct { + Profiles []string `mapstructure:"profiles"` + } + + SamplerSchemesConfig map[string]SchemeConfig + + SamplerConfig struct { + Files uint `mapstructure:"files"` + Folders uint `mapstructure:"folders"` + Schemes SamplerSchemesConfig `mapstructure:"schemes"` + } +) + +func (cs SamplerConfig) Validate(name string, profiles *ProfilesConfig) error { + if name == "" { + return nil + } + + var ( + found bool + scheme SchemeConfig + ) + + if scheme, found = cs.Schemes[name]; !found { + return fmt.Errorf("scheme: '%v' not found in config", name) + } + + for _, p := range scheme.Profiles { + if _, found := (*profiles)[p]; !found { + return fmt.Errorf("profile(referenced by scheme: '%v'): '%v' not found in config", + name, p, + ) + } + } + + return nil +} diff --git a/src/app/proxy/sampler-runner_test.go b/src/app/proxy/sampler-runner_test.go index 1a39ac9..c95b717 100644 --- a/src/app/proxy/sampler-runner_test.go +++ b/src/app/proxy/sampler-runner_test.go @@ -95,7 +95,6 @@ var _ = Describe("SamplerRunner", Ordered, func() { // "--sample-folders", "3" "--dry-run", "--mode", "tidy", - "--scheme", entry.scheme, } bootstrap := command.Bootstrap{} @@ -115,6 +114,10 @@ var _ = Describe("SamplerRunner", Ordered, func() { Expect(err).Error().To(BeNil(), fmt.Sprintf("should pass validation due to all flag being valid (%v)", err), ) + + // eventually, we should assert on files created in the virtual + // file system. + // }, func(entry *samplerTE) string { return fmt.Sprintf("๐Ÿงช ===> given: '%v', should: '%v'", @@ -124,13 +127,73 @@ var _ = Describe("SamplerRunner", Ordered, func() { Entry(nil, &samplerTE{ runnerTE: runnerTE{ - given: "scheme", - should: "bar", + given: "profile", + should: "sample(first) with glob filter using the defined profile", + relative: "nasa/interstellar/Dark Energy Explorers/sessions/scan-01", + args: []string{ + "--sample", + "--no-files", "4", + "--files-gb", "*Energy-Explorers*", + "--profile", "adaptive", + }, + }, + }), + + Entry(nil, &samplerTE{ + runnerTE: runnerTE{ + given: "profile", + should: "sample(last) with glob filter using the defined profile", + relative: "nasa/interstellar/Dark Energy Explorers/sessions/scan-02", + args: []string{ + "--sample", + "--last", + "--no-files", "4", + "--files-gb", "*Energy-Explorers*", + "--profile", "adaptive", + }, + }, + }), + + Entry(nil, &samplerTE{ + runnerTE: runnerTE{ + given: "profile without no-files in args", + should: "sample(first) with glob filter, using no-files from config", + relative: "nasa/interstellar/Dark Energy Explorers/sessions/scan-01", + args: []string{ + "--sample", + "--files-gb", "*Energy-Explorers*", + "--profile", "adaptive", + }, + }, + }), + + XEntry(nil, &samplerTE{ + runnerTE: runnerTE{ + given: "profile", + should: "sample with regex filter using the defined profile", relative: "nasa/interstellar/Dark Energy Explorers/sessions/scan-01", args: []string{ "--strip", "--interlace", "plane", "--quality", "85", "--profile", "adaptive", }, }, }), + + // override config with explicitly defined args on command line + // ie they should be present in args as opposed to relying on presence + // in config. Here we are testing that command line overrides config + // ... + + // === + + Entry(nil, &samplerTE{ + runnerTE: runnerTE{ + given: "scheme", + should: "sample all profiles in the scheme", + relative: "nasa/interstellar/Dark Energy Explorers/sessions/scan-01", + args: []string{ + "--strip", "--interlace", "plane", "--quality", "85", "--scheme", "blur-sf", + }, + }, + }), ) }) diff --git a/src/i18n/messages-command.go b/src/i18n/messages-command.go index a4d7a0b..7bc81f6 100644 --- a/src/i18n/messages-command.go +++ b/src/i18n/messages-command.go @@ -70,6 +70,62 @@ func (td RootCmdLangUsageTemplData) Message() *i18n.Message { } } +// RootCmdSampleUsageTemplData +// ๐ŸงŠ +type RootCmdSampleUsageTemplData struct { + pixaTemplData +} + +func (td RootCmdSampleUsageTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "root-command-sample.param-usage", + Description: "root command sample usage; activates sampling", + Other: "sample is a flag that activates sampling", + } +} + +// RootCmdNoFilesUsageTemplData +// ๐ŸงŠ +type RootCmdNoFilesUsageTemplData struct { + pixaTemplData +} + +func (td RootCmdNoFilesUsageTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "root-command-no-files.param-usage", + Description: "root command files usage; no of files in sample set", + Other: "files specifies the number of files to sample", + } +} + +// RootCmdNoFoldersUsageTemplData +// ๐ŸงŠ +type RootCmdNoFoldersUsageTemplData struct { + pixaTemplData +} + +func (td RootCmdNoFoldersUsageTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "root-command-no-folders.param-usage", + Description: "root command folders usage; no of folders in sample set", + Other: "folders specifies the number of folders to sample", + } +} + +// RootCmdSampleUsageTemplData +// ๐ŸงŠ +type RootCmdLastUsageTemplData struct { + pixaTemplData +} + +func (td RootCmdLastUsageTemplData) Message() *i18n.Message { + return &i18n.Message{ + ID: "root-command-last.param-usage", + Description: "root command last usage; indicates which n items are to be sampled", + Other: "last is a flag that indicates which n items are to be sampled", + } +} + // RootCmdFolderRexExParamUsageTemplData // ๐ŸงŠ type RootCmdFolderRexExParamUsageTemplData struct { diff --git a/src/internal/helpers/test-utilities.go b/src/internal/helpers/test-utils.go similarity index 96% rename from src/internal/helpers/test-utilities.go rename to src/internal/helpers/test-utils.go index b46e3a5..067c1ca 100644 --- a/src/internal/helpers/test-utilities.go +++ b/src/internal/helpers/test-utils.go @@ -90,3 +90,8 @@ func (e *ExecutorStub) Look() (string, error) { func (e *ExecutorStub) Execute(_ ...string) error { return nil } + +type DirectoryQuantities struct { + Files uint + Folders uint +} diff --git a/src/internal/helpers/utils.go b/src/internal/helpers/utils.go new file mode 100644 index 0000000..78ff80f --- /dev/null +++ b/src/internal/helpers/utils.go @@ -0,0 +1,26 @@ +package helpers + +import ( + "os" + "path/filepath" +) + +func ResolvePath(path string) string { + if path == "" { + return path + } + + result := path + + if result[0] == '~' { + if h, err := os.UserHomeDir(); err == nil { + result = filepath.Join(h, result[1:]) + } + } else { + if absolute, absErr := filepath.Abs(path); absErr == nil { + result = absolute + } + } + + return result +} diff --git a/test/data/configuration/pixa-test.yml b/test/data/configuration/pixa-test.yml index fd2b2ec..2e7d191 100644 --- a/test/data/configuration/pixa-test.yml +++ b/test/data/configuration/pixa-test.yml @@ -1,6 +1,6 @@ profiles: blur: - ["--dry-run", "--strip", "--interlace", "plane", "--gaussian-blur", "0.05"] + ["--strip", "--interlace", "plane", "--gaussian-blur", "0.05"] sf: [ "--dry-run", @@ -12,7 +12,6 @@ profiles: ] adaptive: [ - "--dry-run", "--strip", "--interlace", "plane", @@ -21,11 +20,13 @@ profiles: "--adaptive-resize", "60", ] -samplers: +sampler: + files: 2 + folders: 1 schemes: - - foo: + blur-sf: profiles: ["blur", "sf"] - - bar: + adaptive-sf: profiles: ["adaptive", "sf"] - - baz: + adaptive-blur: profiles: ["adaptive", "blur"]