diff --git a/director.go b/director.go index 862e005..19dba90 100644 --- a/director.go +++ b/director.go @@ -3,8 +3,8 @@ package tv import ( "io/fs" + "github.com/snivilised/traverse/internal/feat/filter" "github.com/snivilised/traverse/internal/feat/hiber" - "github.com/snivilised/traverse/internal/feat/refine" "github.com/snivilised/traverse/internal/feat/resume" "github.com/snivilised/traverse/internal/feat/sampling" "github.com/snivilised/traverse/internal/kernel" @@ -29,7 +29,7 @@ func features(o *pref.Options, mediator types.Mediator, // order. How can we decouple ourselves from this // requirement? => the cure is worse than the disease // - hiber.IfActive, refine.IfActive, sampling.IfActive, + hiber.IfActive, filter.IfActive, sampling.IfActive, } ) diff --git a/internal/feat/refine/filter-plugin.go b/internal/feat/filter/filter-plugin.go similarity index 96% rename from internal/feat/refine/filter-plugin.go rename to internal/feat/filter/filter-plugin.go index 7cdae08..f0f2dba 100644 --- a/internal/feat/refine/filter-plugin.go +++ b/internal/feat/filter/filter-plugin.go @@ -1,6 +1,6 @@ -package refine +package filter -// 📦 pkg: refine - defines filters +// 📦 pkg: filter - defines filters import ( "github.com/snivilised/traverse/core" diff --git a/internal/feat/refine/schemes.go b/internal/feat/filter/schemes.go similarity index 94% rename from internal/feat/refine/schemes.go rename to internal/feat/filter/schemes.go index 0126fd1..20c5529 100644 --- a/internal/feat/refine/schemes.go +++ b/internal/feat/filter/schemes.go @@ -1,10 +1,11 @@ -package refine +package filter import ( "io/fs" "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" + "github.com/snivilised/traverse/internal/filtering" "github.com/snivilised/traverse/internal/measure" "github.com/snivilised/traverse/internal/third/lo" "github.com/snivilised/traverse/internal/types" @@ -66,7 +67,7 @@ type nativeScheme struct { } func (f *nativeScheme) create() error { - filter, err := newNodeFilter(f.o.Filter.Node, &f.o.Filter) + filter, err := filtering.NewNodeFilter(f.o.Filter.Node, &f.o.Filter) if err != nil { return err } @@ -92,7 +93,7 @@ type childScheme struct { } func (f *childScheme) create() error { - filter, err := newChildFilter(f.o.Filter.Child) + filter, err := filtering.NewChildFilter(f.o.Filter.Child) if err != nil { return err @@ -138,7 +139,7 @@ type samplerScheme struct { } func (f *samplerScheme) create() error { - filter, err := newSampleFilter(f.o.Filter.Sample, &f.o.Sampling) + filter, err := filtering.NewSampleFilter(f.o.Filter.Sample, &f.o.Sampling) if err != nil { return err diff --git a/internal/feat/refine/base.go b/internal/feat/refine/base.go deleted file mode 100644 index 165107f..0000000 --- a/internal/feat/refine/base.go +++ /dev/null @@ -1,137 +0,0 @@ -package refine - -import ( - "io/fs" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/third/lo" - "github.com/snivilised/traverse/nfs" -) - -// Filter ===================================================================== - -// Filter base filter struct. -type Filter struct { - name string - pattern string - scope enums.FilterScope // defines which file system nodes the filter should be applied to - negate bool // select to define a negative match - ifNotApplicable bool -} - -// Description description of the filter -func (f *Filter) Description() string { - return f.name -} - -// Source text defining the filter -func (f *Filter) Source() string { - return f.pattern -} - -func (f *Filter) IsApplicable(node *core.Node) bool { - return (f.scope & node.Extension.Scope) > 0 -} - -func (f *Filter) Scope() enums.FilterScope { - return f.scope -} - -func (f *Filter) invert(result bool) bool { - return lo.Ternary(f.negate, !result, result) -} - -func (f *Filter) Validate() error { - if f.scope == enums.ScopeUndefined { - f.scope = enums.ScopeAll - } - - return nil -} - -// ChildFilter ================================================================ - -// ChildFilter filter used when subscription is FoldersWithFiles -type ChildFilter struct { - Name string - Pattern string - Negate bool -} - -func (f *ChildFilter) Description() string { - return f.Name -} - -func (f *ChildFilter) Validate() error { - return nil -} - -func (f *ChildFilter) Source() string { - return f.Pattern -} - -func (f *ChildFilter) invert(result bool) bool { - return lo.Ternary(f.Negate, !result, result) -} - -// BaseSampleFilter =========================================================== -type ( - candidates func(entries []fs.DirEntry) (wanted, others []fs.DirEntry) -) - -type SampleFilter struct { - Filter -} - -// NewSampleFilter only needs to be called explicitly when defining -// a custom sample filter. -func NewSampleFilter(scope enums.FilterScope) SampleFilter { - return SampleFilter{ - Filter: Filter{ - scope: scope, - }, - } -} - -func (f *SampleFilter) files(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { - wanted, others = nfs.Separate(entries) - return wanted, others -} - -func (f *SampleFilter) folders(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { - others, wanted = nfs.Separate(entries) - return wanted, others -} - -func (f *SampleFilter) all(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { - return entries, []fs.DirEntry{} -} - -func (f *SampleFilter) fn() candidates { - if f.scope.IsFolder() { - return f.folders - } - - if f.scope.IsFile() { - return f.files - } - - return f.all -} - -func (f *SampleFilter) fetch(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { - return f.fn()(entries) -} - -// GetMatching sampler func. -type GetMatching func(entry fs.DirEntry, index int) bool - -func (f *SampleFilter) Matching(children []fs.DirEntry, - get GetMatching, -) []fs.DirEntry { - filterable, bypass := f.fetch(children) - filtered := lo.Filter(filterable, get) - - return append(filtered, bypass...) -} diff --git a/internal/feat/refine/extended-glob.go b/internal/feat/refine/extended-glob.go deleted file mode 100644 index a038a76..0000000 --- a/internal/feat/refine/extended-glob.go +++ /dev/null @@ -1,93 +0,0 @@ -package refine - -import ( - "io/fs" - "path/filepath" - "strings" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/internal/third/lo" -) - -type ExtendedGlobFilter struct { - Filter - baseGlob string - suffixes []string - anyExtension bool - exclusion string -} - -func filterFileByExtendedGlob(name, base, exclusion string, - suffixes []string, anyExtension bool, -) bool { - extension := filepath.Ext(name) - baseName := strings.ToLower(strings.TrimSuffix(name, extension)) - - if baseMatch, _ := filepath.Match(base, baseName); !baseMatch { - return false - } - - if excluded, _ := filepath.Match(exclusion, baseName); excluded { - return false - } - - return lo.TernaryF(anyExtension, - func() bool { - return true - }, - func() bool { - return lo.TernaryF(extension == "", - func() bool { - return len(suffixes) == 0 - }, - func() bool { - return lo.Contains( - suffixes, strings.ToLower(strings.TrimPrefix(extension, ".")), - ) - }, - ) - }, - ) -} - -// IsMatch does this node match the filter -func (f *ExtendedGlobFilter) IsMatch(node *core.Node) bool { - if f.IsApplicable(node) { - result := lo.TernaryF(node.IsFolder(), - func() bool { - result, _ := filepath.Match(f.baseGlob, strings.ToLower(node.Extension.Name)) - - return result - }, - func() bool { - return filterFileByExtendedGlob( - node.Extension.Name, f.baseGlob, f.exclusion, f.suffixes, f.anyExtension, - ) - }, - ) - - return f.invert(result) - } - - return f.ifNotApplicable -} - -// ChildExtendedGlobFilter ========================================================== - -type ChildExtendedGlobFilter struct { - ChildFilter - baseGlob string - exclusion string - suffixes []string - anyExtension bool -} - -func (f *ChildExtendedGlobFilter) Matching(children []fs.DirEntry) []fs.DirEntry { - return lo.Filter(children, func(entry fs.DirEntry, _ int) bool { - name := entry.Name() - - return f.invert(filterFileByExtendedGlob( - name, f.baseGlob, f.exclusion, f.suffixes, f.anyExtension, - )) - }) -} diff --git a/internal/feat/refine/glob.go b/internal/feat/refine/glob.go deleted file mode 100644 index ed7a69f..0000000 --- a/internal/feat/refine/glob.go +++ /dev/null @@ -1,68 +0,0 @@ -package refine - -import ( - "io/fs" - "path/filepath" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/internal/third/lo" -) - -// GlobFilter wildcard filter. -type GlobFilter struct { - Filter -} - -// IsMatch does this node match the filter -func (f *GlobFilter) IsMatch(node *core.Node) bool { - if f.IsApplicable(node) { - matched, _ := filepath.Match(f.pattern, node.Extension.Name) - return f.invert(matched) - } - - return f.ifNotApplicable -} - -// ChildGlobFilter ============================================================ - -type ChildGlobFilter struct { - ChildFilter -} - -// Matching returns the collection of files contained within this -// node's folder that matches this filter. -func (f *ChildGlobFilter) Matching(children []fs.DirEntry) []fs.DirEntry { - return lo.Filter(children, - func(entry fs.DirEntry, _ int) bool { - matched, _ := filepath.Match(f.Pattern, entry.Name()) - return f.invert(matched) - }, - ) -} - -// SampleGlobFilter =========================================================== - -// SampleGlobFilter is a hybrid between a child filter and a node filter. It -// is used to filter on a compound basis but has some differences to ChildGlobFilter -// that necessitates its use. The biggest difference is that ChildGlobFilter is -// designed to only be applied to file directory entries, where as SampleGlobFilter -// can be applied to files or folders. It also possesses a scope field used to -// distinguish only between files and folders. -type SampleGlobFilter struct { - SampleFilter -} - -func (f *SampleGlobFilter) Matching(entries []fs.DirEntry) []fs.DirEntry { - filterable, bypass := f.fetch(entries) - - filtered := lo.Filter(filterable, - func(entry fs.DirEntry, _ int) bool { - matched, _ := filepath.Match(f.pattern, entry.Name()) - return f.invert(matched) - }, - ) - - filtered = append(filtered, bypass...) - - return filtered -} diff --git a/internal/feat/refine/new-filter.go b/internal/feat/refine/new-filter.go deleted file mode 100644 index d4aa41a..0000000 --- a/internal/feat/refine/new-filter.go +++ /dev/null @@ -1,294 +0,0 @@ -package refine - -import ( - "slices" - "strings" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/third/lo" - "github.com/snivilised/traverse/locale" - "github.com/snivilised/traverse/pref" -) - -func fromExtendedGlobPattern(pattern string) (segments, suffixes []string, err error) { - if !strings.Contains(pattern, "|") { - return []string{}, []string{}, - locale.NewInvalidExtGlobFilterMissingSeparatorError(pattern) - } - - segments = strings.Split(pattern, "|") - suffixes = strings.Split(segments[1], ",") - - suffixes = lo.Reject(suffixes, func(item string, _ int) bool { - return item == "" - }) - - return segments, suffixes, nil -} - -func newNodeFilter(def *core.FilterDef, - fo *pref.FilterOptions, -) (core.TraverseFilter, error) { - var ( - filter core.TraverseFilter - ifNotApplicable = true - err error - segments, suffixes []string - ) - - switch def.IfNotApplicable { - case enums.TriStateBoolTrue: - ifNotApplicable = true - - case enums.TriStateBoolFalse: - ifNotApplicable = false - - case enums.TriStateBoolUndefined: - } - - switch def.Type { - case enums.FilterTypeExtendedGlob: - if segments, suffixes, err = fromExtendedGlobPattern(def.Pattern); err != nil { - return nil, err - } - - base, exclusion := splitGlob(segments[0]) - - filter = &ExtendedGlobFilter{ - Filter: Filter{ - name: def.Description, - scope: def.Scope, - pattern: def.Pattern, - negate: def.Negate, - ifNotApplicable: ifNotApplicable, - }, - baseGlob: base, - suffixes: lo.Map(suffixes, func(s string, _ int) string { - return strings.ToLower(strings.TrimPrefix(strings.TrimSpace(s), ".")) - }), - anyExtension: slices.Contains(suffixes, "*"), - exclusion: exclusion, - } - - case enums.FilterTypeRegex: - filter = &RegexFilter{ - Filter: Filter{ - name: def.Description, - scope: def.Scope, - pattern: def.Pattern, - negate: def.Negate, - ifNotApplicable: ifNotApplicable, - }, - } - - case enums.FilterTypeGlob: - filter = &GlobFilter{ - Filter: Filter{ - name: def.Description, - scope: def.Scope, - pattern: def.Pattern, - negate: def.Negate, - ifNotApplicable: ifNotApplicable, - }, - } - - case enums.FilterTypeCustom: - if fo.Custom == nil { - return nil, locale.ErrMissingCustomFilterDefinition - } - filter = fo.Custom - - case enums.FilterTypePoly: - var polyE error - - if filter, polyE = newPolyFilter(fo.Node.Poly); polyE != nil { - return nil, polyE - } - - case enums.FilterTypeUndefined: - return nil, locale.ErrFilterMissingType - } - - if filter != nil { - err = filter.Validate() - } - - return filter, err -} - -func newPolyFilter(polyDef *core.PolyFilterDef) (core.TraverseFilter, error) { - // enforce the correct filter scopes - // - polyDef.File.Scope.Set(enums.ScopeFile) - polyDef.File.Scope.Clear(enums.ScopeFolder) - - polyDef.Folder.Scope.Set(enums.ScopeFolder) - polyDef.Folder.Scope.Clear(enums.ScopeFile) - - var ( - file, folder core.TraverseFilter - fileE, folderE error - ) - - if file, fileE = newNodeFilter(&polyDef.File, nil); fileE != nil { - return nil, fileE - } - - if folder, folderE = newNodeFilter(&polyDef.Folder, nil); folderE != nil { - return nil, folderE - } - - filter := &PolyFilter{ - File: file, - Folder: folder, - } - - return filter, nil -} - -const ( - exclusionDelim = "/" -) - -func splitGlob(baseGlob string) (base, exclusion string) { - base = strings.ToLower(baseGlob) - - if strings.Contains(base, exclusionDelim) { - constituents := strings.Split(base, exclusionDelim) - base = constituents[0] - exclusion = constituents[1] - } - - return base, exclusion -} - -func newChildFilter(def *core.ChildFilterDef) (core.ChildTraverseFilter, error) { - var ( - filter core.ChildTraverseFilter - ) - - if def == nil { - return nil, locale.ErrFilterIsNil - } - - switch def.Type { - case enums.FilterTypeExtendedGlob: - var ( - err error - segments, suffixes []string - ) - - if segments, suffixes, err = fromExtendedGlobPattern(def.Pattern); err != nil { - return nil, locale.NewInvalidIncaseFilterDefError(def.Pattern) - } - - base, exclusion := splitGlob(segments[0]) - - filter = &ChildExtendedGlobFilter{ - ChildFilter: ChildFilter{ - Name: def.Description, - Pattern: def.Pattern, - Negate: def.Negate, - }, - baseGlob: base, - suffixes: lo.Map(suffixes, func(s string, _ int) string { - return strings.ToLower(strings.TrimPrefix(strings.TrimSpace(s), ".")) - }), - anyExtension: slices.Contains(suffixes, "*"), - exclusion: exclusion, - } - - case enums.FilterTypeRegex: - filter = &ChildRegexFilter{ - ChildFilter: ChildFilter{ - Name: def.Description, - Pattern: def.Pattern, - Negate: def.Negate, - }, - } - - case enums.FilterTypeGlob: - filter = &ChildGlobFilter{ - ChildFilter: ChildFilter{ - Name: def.Description, - Pattern: def.Pattern, - Negate: def.Negate, - }, - } - - case enums.FilterTypeCustom: - return nil, locale.ErrFilterCustomNotSupported - - case enums.FilterTypeUndefined: - return nil, locale.ErrFilterUndefined - - case enums.FilterTypePoly: - } - - if filter != nil { - if err := filter.Validate(); err != nil { - return nil, err - } - } - - return filter, nil -} - -func newSampleFilter(def *core.SampleFilterDef, - so *pref.SamplingOptions, -) (core.SampleTraverseFilter, error) { - var ( - filter core.SampleTraverseFilter - ) - - if def == nil { - return nil, locale.ErrFilterIsNil - } - - base := SampleFilter{ - Filter: Filter{ - name: def.Description, - scope: def.Scope.Scrub(), - pattern: def.Pattern, - negate: def.Negate, - }, - } - - if base.scope.IsFile() && so.NoOf.Files == 0 { - return nil, locale.ErrInvalidFileSamplingSpecMissingFiles - } - - if base.scope.IsFolder() && so.NoOf.Folders == 0 { - return nil, locale.ErrInvalidFolderSamplingSpecMissingFolders - } - - switch def.Type { - case enums.FilterTypeExtendedGlob: - case enums.FilterTypeRegex: - filter = &SampleRegexFilter{ - SampleFilter: base, - } - case enums.FilterTypeGlob: - filter = &SampleGlobFilter{ - SampleFilter: base, - } - - case enums.FilterTypeCustom: - if def.Custom == nil { - return nil, locale.ErrFilterIsNil - } - filter = def.Custom - case enums.FilterTypePoly: - case enums.FilterTypeUndefined: - return nil, locale.ErrFilterMissingType - } - - if filter != nil { - if err := filter.Validate(); err != nil { - return nil, err - } - } - - return filter, nil -} diff --git a/internal/feat/refine/poly.go b/internal/feat/refine/poly.go deleted file mode 100644 index 020a2c3..0000000 --- a/internal/feat/refine/poly.go +++ /dev/null @@ -1,83 +0,0 @@ -package refine - -import ( - "fmt" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/enums" -) - -// PolyFilter is a dual filter that allows files and folders to be filtered -// independently. The Folder filter only applies when the current node -// is a file. This is because, filtering doesn't affect navigation, it only -// controls wether the client callback is invoked or not. That is to say, if -// a particular folder fails to pass a filter, the callback will not be -// invoked for that folder, but we still descend into it and navigate its -// children. This is the reason why the poly filter is only active when the -// the current node is a filter as the client callback will only be invoked -// for the file if its parent folder passes the poly folder filter and -// the file passes the poly file filter. -type PolyFilter struct { - // File is the filter that applies to a file. Note that the client does - // not have to set the File scope as this is enforced automatically as - // well as ensuring that the Folder scope has not been set. The client is - // still free to set other scopes. - File core.TraverseFilter - - // Folder is the filter that applies to a folder. Note that the client does - // not have to set the Folder scope as this is enforced automatically as - // well as ensuring that the File scope has not been set. The client is - // still free to set other scopes. - Folder core.TraverseFilter -} - -// Description -func (f *PolyFilter) Description() string { - return fmt.Sprintf("Poly - FILE: '%v', FOLDER: '%v'", - f.File.Description(), f.Folder.Description(), - ) -} - -// Validate ensures that both filters definition are valid, panics when invalid -func (f *PolyFilter) Validate() error { - if err := f.File.Validate(); err != nil { - return err - } - - return f.Folder.Validate() -} - -// Source returns the Sources of both the File and Folder filters separated -// by a '##' -func (f *PolyFilter) Source() string { - return fmt.Sprintf("%v##%v", - f.File.Source(), f.Folder.Source(), - ) -} - -// IsMatch returns true if the current node is a file and both the current -// file matches the poly file filter and the file's parent folder matches -// the poly folder filter. Returns true of the current node is a folder. -func (f *PolyFilter) IsMatch(node *core.Node) bool { - if !node.IsFolder() { - return f.Folder.IsMatch(node.Parent) && f.File.IsMatch(node) - } - - return true -} - -// IsApplicable returns the result of applying IsApplicable to -// the poly Filter filter if the current node is a file, returns false -// for folders. -func (f *PolyFilter) IsApplicable(node *core.Node) bool { - if !node.IsFolder() { - return f.File.IsApplicable(node) - } - - return false -} - -// Scope is a bitwise OR combination of both filters -func (f *PolyFilter) Scope() enums.FilterScope { - return f.File.Scope() | f.Folder.Scope() -} diff --git a/internal/feat/refine/regex.go b/internal/feat/refine/regex.go deleted file mode 100644 index 6144ea5..0000000 --- a/internal/feat/refine/regex.go +++ /dev/null @@ -1,100 +0,0 @@ -package refine - -import ( - "io/fs" - "regexp" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/internal/third/lo" -) - -// RegexFilter ================================================================ - -// RegexFilter regex filter. -type RegexFilter struct { - Filter - rex *regexp.Regexp -} - -// Validate ensures the filter definition is valid, panics when invalid -func (f *RegexFilter) Validate() error { - if err := f.Filter.Validate(); err != nil { - return err - } - - var ( - err error - ) - f.rex, err = regexp.Compile(f.pattern) - - return err -} - -// IsMatch -func (f *RegexFilter) IsMatch(node *core.Node) bool { - if f.IsApplicable(node) { - return f.invert(f.rex.MatchString(node.Extension.Name)) - } - - return f.ifNotApplicable -} - -// ChildRegexFilter =========================================================== - -type ChildRegexFilter struct { - ChildFilter - rex *regexp.Regexp -} - -func (f *ChildRegexFilter) Validate() error { - var ( - err error - ) - f.rex, err = regexp.Compile(f.Pattern) - - return err -} - -func (f *ChildRegexFilter) Matching(children []fs.DirEntry) []fs.DirEntry { - return lo.Filter(children, func(entry fs.DirEntry, _ int) bool { - return f.invert(f.rex.MatchString(entry.Name())) - }) -} - -// SampleRegexFilter ========================================================== - -// SampleRegexFilter is a hybrid between a child filter and a node filter. It -// is used to filter on a compound basis but has some differences to ChildRegexFilter -// that necessitates its use. The biggest difference is that ChildRegexFilter is -// designed to only be applied to file directory entries, where as SampleRegexFilter -// can be applied to files or folders. It also possesses a scope field used to -// distinguish only between files and folders. -type SampleRegexFilter struct { - SampleFilter - rex *regexp.Regexp -} - -// Validate ensures the filter definition is valid, panics when invalid -func (f *SampleRegexFilter) Validate() error { - if err := f.Filter.Validate(); err != nil { - return err - } - - var ( - err error - ) - f.rex, err = regexp.Compile(f.pattern) - - return err -} - -func (f *SampleRegexFilter) Matching(entries []fs.DirEntry) []fs.DirEntry { - filterable, bypass := f.fetch(entries) - filtered := lo.Filter(filterable, func(entry fs.DirEntry, _ int) bool { - return f.invert(f.rex.MatchString(entry.Name())) - }) - - filtered = append(filtered, bypass...) - - return filtered -} diff --git a/internal/feat/resume/resume-defs.go b/internal/feat/resume/resume-defs.go index b35ecbb..c034484 100644 --- a/internal/feat/resume/resume-defs.go +++ b/internal/feat/resume/resume-defs.go @@ -8,8 +8,8 @@ import ( "github.com/snivilised/traverse/pref" ) -// 📦 pkg: resume - depends on hiber, refine and persist. -// refine should also contain persistence concerns (actually +// 📦 pkg: resume - depends on hiber, filter and persist. +// filter should also contain persistence concerns (actually // these may be internal modules, eg internal/serial/JSON). const ( diff --git a/internal/filtering/base.go b/internal/filtering/base.go index 6a89fbd..26e8a35 100644 --- a/internal/filtering/base.go +++ b/internal/filtering/base.go @@ -8,8 +8,8 @@ import ( // Filter ===================================================================== -// Filter base filter struct. -type Filter struct { +// Base base filter struct. +type Base struct { name string pattern string scope enums.FilterScope // defines which file system nodes the filter should be applied to @@ -18,56 +18,31 @@ type Filter struct { } // Description description of the filter -func (f *Filter) Description() string { +func (f *Base) Description() string { return f.name } // Source text defining the filter -func (f *Filter) Source() string { +func (f *Base) Source() string { return f.pattern } -func (f *Filter) IsApplicable(node *core.Node) bool { +func (f *Base) IsApplicable(node *core.Node) bool { return (f.scope & node.Extension.Scope) > 0 } -func (f *Filter) Scope() enums.FilterScope { +func (f *Base) Scope() enums.FilterScope { return f.scope } -func (f *Filter) invert(result bool) bool { +func (f *Base) invert(result bool) bool { return lo.Ternary(f.negate, !result, result) } -func (f *Filter) Validate() error { +func (f *Base) Validate() error { if f.scope == enums.ScopeUndefined { f.scope = enums.ScopeAll } return nil } - -// ChildFilter ================================================================ - -// ChildFilter filter used when subscription is FoldersWithFiles -type ChildFilter struct { - Name string - Pattern string - Negate bool -} - -func (f *ChildFilter) Description() string { - return f.Name -} - -func (f *ChildFilter) Validate() error { - return nil -} - -func (f *ChildFilter) Source() string { - return f.Pattern -} - -func (f *ChildFilter) invert(result bool) bool { - return lo.Ternary(f.Negate, !result, result) -} diff --git a/internal/filtering/child.go b/internal/filtering/child.go new file mode 100644 index 0000000..847019e --- /dev/null +++ b/internal/filtering/child.go @@ -0,0 +1,30 @@ +package filtering + +import ( + "github.com/snivilised/traverse/internal/third/lo" +) + +// ChildFilter ================================================================ + +// Child filter used when subscription is FoldersWithFiles +type Child struct { + Name string + Pattern string + Negate bool +} + +func (f *Child) Description() string { + return f.Name +} + +func (f *Child) Validate() error { + return nil +} + +func (f *Child) Source() string { + return f.Pattern +} + +func (f *Child) invert(result bool) bool { + return lo.Ternary(f.Negate, !result, result) +} diff --git a/internal/filtering/constant.go b/internal/filtering/constant.go index 97aa613..0886768 100644 --- a/internal/filtering/constant.go +++ b/internal/filtering/constant.go @@ -7,7 +7,7 @@ import ( func NewPermissiveTraverseFilter(def *core.FilterDef) core.TraverseFilter { return &permissiveTraverseFilter{ - Filter: Filter{ + Base: Base{ name: def.Description, scope: enums.ScopeTop, }, @@ -15,7 +15,7 @@ func NewPermissiveTraverseFilter(def *core.FilterDef) core.TraverseFilter { } type permissiveTraverseFilter struct { - Filter + Base match bool } @@ -29,7 +29,7 @@ func (f *permissiveTraverseFilter) IsMatch(_ *core.Node) bool { func NewProhibitiveTraverseFilter(def *core.FilterDef) core.TraverseFilter { return &prohibitiveTraverseFilter{ - Filter: Filter{ + Base: Base{ name: def.Description, scope: enums.ScopeTop, }, @@ -37,7 +37,7 @@ func NewProhibitiveTraverseFilter(def *core.FilterDef) core.TraverseFilter { } type prohibitiveTraverseFilter struct { - Filter + Base match bool } diff --git a/internal/filtering/extended-glob.go b/internal/filtering/extended-glob.go index 14bce96..7c371d5 100644 --- a/internal/filtering/extended-glob.go +++ b/internal/filtering/extended-glob.go @@ -23,8 +23,8 @@ func createExtendedGlobFilter(def *core.FilterDef, base, exclusion := splitGlob(segments[0]) - filter := &ExtendedGlobFilter{ - Filter: Filter{ + filter := &ExtendedGlob{ + Base: Base{ name: def.Description, scope: def.Scope, pattern: def.Pattern, @@ -42,8 +42,8 @@ func createExtendedGlobFilter(def *core.FilterDef, return filter, nil } -type ExtendedGlobFilter struct { - Filter +type ExtendedGlob struct { + Base baseGlob string suffixes []string anyExtension bool @@ -51,7 +51,7 @@ type ExtendedGlobFilter struct { } // IsMatch does this node match the filter -func (f *ExtendedGlobFilter) IsMatch(node *core.Node) bool { +func (f *ExtendedGlob) IsMatch(node *core.Node) bool { if f.IsApplicable(node) { result := lo.TernaryF(node.IsFolder(), func() bool { @@ -75,7 +75,7 @@ func (f *ExtendedGlobFilter) IsMatch(node *core.Node) bool { // ChildExtendedGlobFilter ========================================================== type ChildExtendedGlobFilter struct { - ChildFilter + Child baseGlob string exclusion string suffixes []string diff --git a/internal/filtering/filtering-defs.go b/internal/filtering/filtering-defs.go index bae13b6..55790d4 100644 --- a/internal/filtering/filtering-defs.go +++ b/internal/filtering/filtering-defs.go @@ -1,14 +1,14 @@ package filtering // 📦 pkg: filtering - this package is required because filters are required -// not just but the refine plugin, but others too like hibernation. The filter -// required by hibernation could have been implemented by the refine plugin, +// not just but the filter plugin, but others too like hibernation. The filter +// required by hibernation could have been implemented by the filter plugin, // but doing so in this fashion would have mean introducing coupling of -// hibernation on refine; ie how to allow hibernation to access the filter(s) -// created by refine? +// hibernation on filter; ie how to allow hibernation to access the filter(s) +// created by filter? // Instead, we factor out the filter creation code to this package, so that // hibernation can create and apply filters as it needs, without depending on -// refine. So refine, now doesn't own the filter implementations, rather it's +// filter. So filter, now doesn't own the filter implementations, rather it's // simply responsible for the plugin aspects of filtering, not implementation // or creation. // diff --git a/internal/filtering/glob.go b/internal/filtering/glob.go index 2fc5224..b420271 100644 --- a/internal/filtering/glob.go +++ b/internal/filtering/glob.go @@ -11,8 +11,8 @@ import ( func createGlobFilter(def *core.FilterDef, ifNotApplicable bool, ) core.TraverseFilter { - return &GlobFilter{ - Filter: Filter{ + return &Glob{ + Base: Base{ name: def.Description, scope: def.Scope, pattern: def.Pattern, @@ -22,13 +22,13 @@ func createGlobFilter(def *core.FilterDef, } } -// GlobFilter wildcard filter. -type GlobFilter struct { - Filter +// Glob wildcard filter. +type Glob struct { + Base } // IsMatch does this node match the filter -func (f *GlobFilter) IsMatch(node *core.Node) bool { +func (f *Glob) IsMatch(node *core.Node) bool { if f.IsApplicable(node) { matched, _ := filepath.Match(f.pattern, node.Extension.Name) return f.invert(matched) @@ -39,13 +39,13 @@ func (f *GlobFilter) IsMatch(node *core.Node) bool { // ChildGlobFilter ============================================================ -type ChildGlobFilter struct { - ChildFilter +type ChildGlob struct { + Child } // Matching returns the collection of files contained within this // node's folder that matches this filter. -func (f *ChildGlobFilter) Matching(children []fs.DirEntry) []fs.DirEntry { +func (f *ChildGlob) Matching(children []fs.DirEntry) []fs.DirEntry { return lo.Filter(children, func(entry fs.DirEntry, _ int) bool { matched, _ := filepath.Match(f.Pattern, entry.Name()) @@ -56,17 +56,17 @@ func (f *ChildGlobFilter) Matching(children []fs.DirEntry) []fs.DirEntry { // SampleGlobFilter =========================================================== -// SampleGlobFilter is a hybrid between a child filter and a node filter. It +// SampleGlob is a hybrid between a child filter and a node filter. It // is used to filter on a compound basis but has some differences to ChildGlobFilter // that necessitates its use. The biggest difference is that ChildGlobFilter is -// designed to only be applied to file directory entries, where as SampleGlobFilter +// designed to only be applied to file directory entries, where as SampleGlob // can be applied to files or folders. It also possesses a scope field used to // distinguish only between files and folders. -type SampleGlobFilter struct { - SampleFilter +type SampleGlob struct { + Sample } -func (f *SampleGlobFilter) Matching(entries []fs.DirEntry) []fs.DirEntry { +func (f *SampleGlob) Matching(entries []fs.DirEntry) []fs.DirEntry { filterable, bypass := f.fetch(entries) filtered := lo.Filter(filterable, diff --git a/internal/filtering/new-filter.go b/internal/filtering/new-filter.go index c6258e6..14dc8c3 100644 --- a/internal/filtering/new-filter.go +++ b/internal/filtering/new-filter.go @@ -31,8 +31,8 @@ func NewNodeFilter(def *core.FilterDef, case enums.FilterTypeGlob: filter = createGlobFilter(def, ifNotApplicable) - // TODO: the way we access FilterOptions needs to be tidied up/improved - // Just feels way too fishy for custom and poly... + // TODO: issue #156: the way we access FilterOptions needs to be + // tidied up/improved. Just feels way too fishy for custom and poly... // perhaps we have extra NewCustomFilter/NewPolyFilter funcs // // TODO: createCustomFilter @@ -118,7 +118,7 @@ func NewChildFilter(def *core.ChildFilterDef) (core.ChildTraverseFilter, error) base, exclusion := splitGlob(segments[0]) filter = &ChildExtendedGlobFilter{ - ChildFilter: ChildFilter{ + Child: Child{ Name: def.Description, Pattern: def.Pattern, Negate: def.Negate, @@ -132,8 +132,8 @@ func NewChildFilter(def *core.ChildFilterDef) (core.ChildTraverseFilter, error) } case enums.FilterTypeRegex: - filter = &ChildRegexFilter{ - ChildFilter: ChildFilter{ + filter = &ChildRegex{ + Child: Child{ Name: def.Description, Pattern: def.Pattern, Negate: def.Negate, @@ -141,8 +141,8 @@ func NewChildFilter(def *core.ChildFilterDef) (core.ChildTraverseFilter, error) } case enums.FilterTypeGlob: - filter = &ChildGlobFilter{ - ChildFilter: ChildFilter{ + filter = &ChildGlob{ + Child: Child{ Name: def.Description, Pattern: def.Pattern, Negate: def.Negate, @@ -194,8 +194,8 @@ func newSampleFilter(def *core.SampleFilterDef, return nil, locale.ErrFilterIsNil } - base := SampleFilter{ - Filter: Filter{ + base := Sample{ + Base: Base{ name: def.Description, scope: def.Scope.Scrub(), pattern: def.Pattern, @@ -214,12 +214,12 @@ func newSampleFilter(def *core.SampleFilterDef, switch def.Type { case enums.FilterTypeExtendedGlob: case enums.FilterTypeRegex: - filter = &SampleRegexFilter{ - SampleFilter: base, + filter = &SampleRegex{ + Sample: base, } case enums.FilterTypeGlob: - filter = &SampleGlobFilter{ - SampleFilter: base, + filter = &SampleGlob{ + Sample: base, } case enums.FilterTypeCustom: diff --git a/internal/filtering/poly.go b/internal/filtering/poly.go index 221c3fe..dd8a31c 100644 --- a/internal/filtering/poly.go +++ b/internal/filtering/poly.go @@ -29,7 +29,7 @@ func createPolyFilter(polyDef *core.PolyFilterDef) (core.TraverseFilter, error) return nil, err } - filter := &PolyFilter{ + filter := &Poly{ File: file, Folder: folder, } @@ -37,7 +37,7 @@ func createPolyFilter(polyDef *core.PolyFilterDef) (core.TraverseFilter, error) return filter, nil } -// PolyFilter is a dual filter that allows files and folders to be filtered +// Poly is a dual filter that allows files and folders to be filtered // independently. The Folder filter only applies when the current node // is a file. This is because, filtering doesn't affect navigation, it only // controls wether the client callback is invoked or not. That is to say, if @@ -47,7 +47,7 @@ func createPolyFilter(polyDef *core.PolyFilterDef) (core.TraverseFilter, error) // the current node is a filter as the client callback will only be invoked // for the file if its parent folder passes the poly folder filter and // the file passes the poly file filter. -type PolyFilter struct { +type Poly struct { // File is the filter that applies to a file. Note that the client does // not have to set the File scope as this is enforced automatically as // well as ensuring that the Folder scope has not been set. The client is @@ -62,14 +62,14 @@ type PolyFilter struct { } // Description -func (f *PolyFilter) Description() string { +func (f *Poly) Description() string { return fmt.Sprintf("Poly - FILE: '%v', FOLDER: '%v'", f.File.Description(), f.Folder.Description(), ) } // Validate ensures that both filters definition are valid, panics when invalid -func (f *PolyFilter) Validate() error { +func (f *Poly) Validate() error { if err := f.File.Validate(); err != nil { return err } @@ -79,7 +79,7 @@ func (f *PolyFilter) Validate() error { // Source returns the Sources of both the File and Folder filters separated // by a '##' -func (f *PolyFilter) Source() string { +func (f *Poly) Source() string { return fmt.Sprintf("%v##%v", f.File.Source(), f.Folder.Source(), ) @@ -88,7 +88,7 @@ func (f *PolyFilter) Source() string { // IsMatch returns true if the current node is a file and both the current // file matches the poly file filter and the file's parent folder matches // the poly folder filter. Returns true of the current node is a folder. -func (f *PolyFilter) IsMatch(node *core.Node) bool { +func (f *Poly) IsMatch(node *core.Node) bool { if !node.IsFolder() { return f.Folder.IsMatch(node.Parent) && f.File.IsMatch(node) } @@ -99,7 +99,7 @@ func (f *PolyFilter) IsMatch(node *core.Node) bool { // IsApplicable returns the result of applying IsApplicable to // the poly Filter filter if the current node is a file, returns false // for folders. -func (f *PolyFilter) IsApplicable(node *core.Node) bool { +func (f *Poly) IsApplicable(node *core.Node) bool { if !node.IsFolder() { return f.File.IsApplicable(node) } @@ -108,6 +108,6 @@ func (f *PolyFilter) IsApplicable(node *core.Node) bool { } // Scope is a bitwise OR combination of both filters -func (f *PolyFilter) Scope() enums.FilterScope { +func (f *Poly) Scope() enums.FilterScope { return f.File.Scope() | f.Folder.Scope() } diff --git a/internal/filtering/regex.go b/internal/filtering/regex.go index 1fb5c19..39bb747 100644 --- a/internal/filtering/regex.go +++ b/internal/filtering/regex.go @@ -11,8 +11,8 @@ import ( func createRegexFilter(def *core.FilterDef, ifNotApplicable bool, ) core.TraverseFilter { - return &RegexFilter{ - Filter: Filter{ + return &RegExpr{ + Base: Base{ name: def.Description, scope: def.Scope, pattern: def.Pattern, @@ -24,15 +24,15 @@ func createRegexFilter(def *core.FilterDef, // RegexFilter ================================================================ -// RegexFilter regex filter. -type RegexFilter struct { - Filter +// RegExpr regex filter. +type RegExpr struct { + Base rex *regexp.Regexp } // Validate ensures the filter definition is valid, panics when invalid -func (f *RegexFilter) Validate() error { - if err := f.Filter.Validate(); err != nil { +func (f *RegExpr) Validate() error { + if err := f.Base.Validate(); err != nil { return err } @@ -45,7 +45,7 @@ func (f *RegexFilter) Validate() error { } // IsMatch -func (f *RegexFilter) IsMatch(node *core.Node) bool { +func (f *RegExpr) IsMatch(node *core.Node) bool { if f.IsApplicable(node) { return f.invert(f.rex.MatchString(node.Extension.Name)) } @@ -55,12 +55,12 @@ func (f *RegexFilter) IsMatch(node *core.Node) bool { // ChildRegexFilter =========================================================== -type ChildRegexFilter struct { - ChildFilter +type ChildRegex struct { + Child rex *regexp.Regexp } -func (f *ChildRegexFilter) Validate() error { +func (f *ChildRegex) Validate() error { var ( err error ) @@ -69,7 +69,7 @@ func (f *ChildRegexFilter) Validate() error { return err } -func (f *ChildRegexFilter) Matching(children []fs.DirEntry) []fs.DirEntry { +func (f *ChildRegex) Matching(children []fs.DirEntry) []fs.DirEntry { return lo.Filter(children, func(entry fs.DirEntry, _ int) bool { return f.invert(f.rex.MatchString(entry.Name())) }) @@ -77,20 +77,20 @@ func (f *ChildRegexFilter) Matching(children []fs.DirEntry) []fs.DirEntry { // SampleRegexFilter ========================================================== -// SampleRegexFilter is a hybrid between a child filter and a node filter. It +// SampleRegex is a hybrid between a child filter and a node filter. It // is used to filter on a compound basis but has some differences to ChildRegexFilter // that necessitates its use. The biggest difference is that ChildRegexFilter is -// designed to only be applied to file directory entries, where as SampleRegexFilter +// designed to only be applied to file directory entries, where as SampleRegex // can be applied to files or folders. It also possesses a scope field used to // distinguish only between files and folders. -type SampleRegexFilter struct { - SampleFilter +type SampleRegex struct { + Sample rex *regexp.Regexp } // Validate ensures the filter definition is valid, panics when invalid -func (f *SampleRegexFilter) Validate() error { - if err := f.Filter.Validate(); err != nil { +func (f *SampleRegex) Validate() error { + if err := f.Base.Validate(); err != nil { return err } @@ -102,7 +102,7 @@ func (f *SampleRegexFilter) Validate() error { return err } -func (f *SampleRegexFilter) Matching(entries []fs.DirEntry) []fs.DirEntry { +func (f *SampleRegex) Matching(entries []fs.DirEntry) []fs.DirEntry { filterable, bypass := f.fetch(entries) filtered := lo.Filter(filterable, func(entry fs.DirEntry, _ int) bool { return f.invert(f.rex.MatchString(entry.Name())) diff --git a/internal/filtering/sample.go b/internal/filtering/sample.go index 876ea38..7c63221 100644 --- a/internal/filtering/sample.go +++ b/internal/filtering/sample.go @@ -3,9 +3,12 @@ package filtering import ( "io/fs" + "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/third/lo" + "github.com/snivilised/traverse/locale" "github.com/snivilised/traverse/nfs" + "github.com/snivilised/traverse/pref" ) // BaseSampleFilter =========================================================== @@ -14,35 +17,83 @@ type ( candidates func(entries []fs.DirEntry) (wanted, others []fs.DirEntry) ) -type SampleFilter struct { - Filter +type Sample struct { + Base } -// NewSampleFilter only needs to be called explicitly when defining -// a custom sample filter. -func NewSampleFilter(scope enums.FilterScope) SampleFilter { - return SampleFilter{ - Filter: Filter{ - scope: scope, +func NewSampleFilter(def *core.SampleFilterDef, + so *pref.SamplingOptions, +) (core.SampleTraverseFilter, error) { + var ( + filter core.SampleTraverseFilter + ) + + if def == nil { + return nil, locale.ErrFilterIsNil + } + + base := Sample{ + Base: Base{ + name: def.Description, + scope: def.Scope.Scrub(), + pattern: def.Pattern, + negate: def.Negate, }, } + + if base.scope.IsFile() && so.NoOf.Files == 0 { + return nil, locale.ErrInvalidFileSamplingSpecMissingFiles + } + + if base.scope.IsFolder() && so.NoOf.Folders == 0 { + return nil, locale.ErrInvalidFolderSamplingSpecMissingFolders + } + + switch def.Type { + case enums.FilterTypeExtendedGlob: + case enums.FilterTypeRegex: + filter = &SampleRegex{ + Sample: base, + } + case enums.FilterTypeGlob: + filter = &SampleGlob{ + Sample: base, + } + + case enums.FilterTypeCustom: + if def.Custom == nil { + return nil, locale.ErrFilterIsNil + } + filter = def.Custom + case enums.FilterTypePoly: + case enums.FilterTypeUndefined: + return nil, locale.ErrFilterMissingType + } + + if filter != nil { + if err := filter.Validate(); err != nil { + return nil, err + } + } + + return filter, nil } -func (f *SampleFilter) files(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { +func (f *Sample) files(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { wanted, others = nfs.Separate(entries) return wanted, others } -func (f *SampleFilter) folders(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { +func (f *Sample) folders(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { others, wanted = nfs.Separate(entries) return wanted, others } -func (f *SampleFilter) all(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { +func (f *Sample) all(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { return entries, []fs.DirEntry{} } -func (f *SampleFilter) fn() candidates { +func (f *Sample) fn() candidates { if f.scope.IsFolder() { return f.folders } @@ -54,14 +105,14 @@ func (f *SampleFilter) fn() candidates { return f.all } -func (f *SampleFilter) fetch(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { +func (f *Sample) fetch(entries []fs.DirEntry) (wanted, others []fs.DirEntry) { return f.fn()(entries) } // GetMatching sampler func. type GetMatching func(entry fs.DirEntry, index int) bool -func (f *SampleFilter) Matching(children []fs.DirEntry, +func (f *Sample) Matching(children []fs.DirEntry, get GetMatching, ) []fs.DirEntry { filterable, bypass := f.fetch(children) @@ -69,3 +120,13 @@ func (f *SampleFilter) Matching(children []fs.DirEntry, return append(filtered, bypass...) } + +// NewCustomSampleFilter only needs to be called explicitly when defining +// a custom sample filter. +func NewCustomSampleFilter(scope enums.FilterScope) Sample { + return Sample{ + Base: Base{ + scope: scope, + }, + } +} diff --git a/internal/kernel/gomega-matchers_test.go b/internal/kernel/gomega-matchers_test.go index 2cc60ba..16835fb 100644 --- a/internal/kernel/gomega-matchers_test.go +++ b/internal/kernel/gomega-matchers_test.go @@ -5,19 +5,19 @@ import ( . "github.com/onsi/gomega/types" //nolint:revive // ok "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/internal/feat/refine" + "github.com/snivilised/traverse/internal/filtering" ) // === MatchCurrentRegexFilter === // type IsCurrentRegexMatchMatcher struct { - filter interface{} + rxFilter interface{} } func MatchCurrentRegexFilter(expected interface{}) GomegaMatcher { return &IsCurrentRegexMatchMatcher{ - filter: expected, + rxFilter: expected, } } @@ -27,38 +27,42 @@ func (m *IsCurrentRegexMatchMatcher) Match(actual interface{}) (bool, error) { return false, fmt.Errorf("matcher expected a *TraverseItem (%T)", item) } - filter, filterOk := m.filter.(*refine.RegexFilter) + rxFilter, filterOk := m.rxFilter.(*filtering.RegExpr) if !filterOk { - return false, fmt.Errorf("matcher expected a *RegexFilter (%T)", filter) + return false, fmt.Errorf("matcher expected a *RegexFilter (%T)", rxFilter) } - return filter.IsMatch(item), nil + return rxFilter.IsMatch(item), nil } func (m *IsCurrentRegexMatchMatcher) FailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(*refine.RegexFilter) + rxFilter, _ := m.rxFilter.(*filtering.RegExpr) - return fmt.Sprintf("🔥 Expected\n\t%v\nto match regex\n\t%v\n", item.Extension.Name, filter.Source()) + return fmt.Sprintf("🔥 Expected\n\t%v\nto match regex\n\t%v\n", + item.Extension.Name, rxFilter.Source(), + ) } func (m *IsCurrentRegexMatchMatcher) NegatedFailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(*refine.RegexFilter) + rxFilter, _ := m.rxFilter.(*filtering.RegExpr) - return fmt.Sprintf("🔥 Expected\n\t%v\nNOT to match regex\n\t%v\n", item.Extension.Name, filter.Source()) + return fmt.Sprintf("🔥 Expected\n\t%v\nNOT to match regex\n\t%v\n", + item.Extension.Name, rxFilter.Source(), + ) } // === MatchCurrentGlobFilter === // type IsCurrentGlobMatchMatcher struct { - filter interface{} + gbFilter interface{} } func MatchCurrentGlobFilter(expected interface{}) GomegaMatcher { return &IsCurrentGlobMatchMatcher{ - filter: expected, + gbFilter: expected, } } @@ -68,38 +72,42 @@ func (m *IsCurrentGlobMatchMatcher) Match(actual interface{}) (bool, error) { return false, fmt.Errorf("matcher expected a *TraverseItem (%T)", item) } - filter, filterOk := m.filter.(*refine.GlobFilter) + gbFilter, filterOk := m.gbFilter.(*filtering.Glob) if !filterOk { - return false, fmt.Errorf("matcher expected a *GlobFilter (%T)", filter) + return false, fmt.Errorf("matcher expected a *GlobFilter (%T)", gbFilter) } - return filter.IsMatch(item), nil + return gbFilter.IsMatch(item), nil } func (m *IsCurrentGlobMatchMatcher) FailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(*refine.GlobFilter) + gbFilter, _ := m.gbFilter.(*filtering.Glob) - return fmt.Sprintf("🔥 Expected\n\t%v\nto match glob\n\t%v\n", item.Extension.Name, filter.Source()) + return fmt.Sprintf("🔥 Expected\n\t%v\nto match glob\n\t%v\n", + item.Extension.Name, gbFilter.Source(), + ) } func (m *IsCurrentGlobMatchMatcher) NegatedFailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(*refine.GlobFilter) + gbFilter, _ := m.gbFilter.(*filtering.Glob) - return fmt.Sprintf("🔥 Expected\n\t%v\nNOT to match glob\n\t%v\n", item.Extension.Name, filter.Source()) + return fmt.Sprintf("🔥 Expected\n\t%v\nNOT to match glob\n\t%v\n", + item.Extension.Name, gbFilter.Source(), + ) } // === MatchCurrentExtendedGlobFilter === // type IsCurrentExtendedGlobMatchMatcher struct { - filter interface{} + egbFilter interface{} } func MatchCurrentExtendedFilter(expected interface{}) GomegaMatcher { return &IsCurrentExtendedGlobMatchMatcher{ - filter: expected, + egbFilter: expected, } } @@ -109,29 +117,29 @@ func (m *IsCurrentExtendedGlobMatchMatcher) Match(actual interface{}) (bool, err return false, fmt.Errorf("matcher expected a *TraverseItem (%T)", item) } - filter, filterOk := m.filter.(*refine.ExtendedGlobFilter) + egbFilter, filterOk := m.egbFilter.(*filtering.ExtendedGlob) if !filterOk { - return false, fmt.Errorf("matcher expected a *IncaseFilter (%T)", filter) + return false, fmt.Errorf("matcher expected a *IncaseFilter (%T)", egbFilter) } - return filter.IsMatch(item), nil + return egbFilter.IsMatch(item), nil } func (m *IsCurrentExtendedGlobMatchMatcher) FailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(*refine.ExtendedGlobFilter) + egbFilter, _ := m.egbFilter.(*filtering.ExtendedGlob) return fmt.Sprintf("🔥 Expected\n\t%v\nto match incase\n\t%v\n", - item.Extension.Name, filter.Source(), + item.Extension.Name, egbFilter.Source(), ) } func (m *IsCurrentExtendedGlobMatchMatcher) NegatedFailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(*refine.ExtendedGlobFilter) + egbFilter, _ := m.egbFilter.(*filtering.ExtendedGlob) return fmt.Sprintf("🔥 Expected\n\t%v\nNOT to match incase\n\t%v\n", - item.Extension.Name, filter.Source(), + item.Extension.Name, egbFilter.Source(), ) } @@ -139,12 +147,12 @@ func (m *IsCurrentExtendedGlobMatchMatcher) NegatedFailureMessage(actual interfa // type IsCurrentCustomMatchMatcher struct { - filter interface{} + tvFilter interface{} } func MatchCurrentCustomFilter(expected interface{}) GomegaMatcher { return &IsCurrentCustomMatchMatcher{ - filter: expected, + tvFilter: expected, } } @@ -154,28 +162,28 @@ func (m *IsCurrentCustomMatchMatcher) Match(actual interface{}) (bool, error) { return false, fmt.Errorf("matcher expected a *TraverseItem (%T)", item) } - filter, filterOk := m.filter.(core.TraverseFilter) + tvFilter, filterOk := m.tvFilter.(core.TraverseFilter) if !filterOk { - return false, fmt.Errorf("matcher expected a core.TraverseFilter (%T)", filter) + return false, fmt.Errorf("matcher expected a core.TraverseFilter (%T)", tvFilter) } - return filter.IsMatch(item), nil + return tvFilter.IsMatch(item), nil } func (m *IsCurrentCustomMatchMatcher) FailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(core.TraverseFilter) + tvFilter, _ := m.tvFilter.(core.TraverseFilter) return fmt.Sprintf("🔥 Expected\n\t%v\nto match custom filter\n\t%v\n", - item.Extension.Name, filter.Source(), + item.Extension.Name, tvFilter.Source(), ) } func (m *IsCurrentCustomMatchMatcher) NegatedFailureMessage(actual interface{}) string { item, _ := actual.(*core.Node) - filter, _ := m.filter.(core.TraverseFilter) + tvFilter, _ := m.tvFilter.(core.TraverseFilter) return fmt.Sprintf("🔥 Expected\n\t%v\nNOT to match custom filter\n\t%v\n", - item.Extension.Name, filter.Source(), + item.Extension.Name, tvFilter.Source(), ) } diff --git a/internal/kernel/kernel-suite_test.go b/internal/kernel/kernel-suite_test.go index 516d002..4696251 100644 --- a/internal/kernel/kernel-suite_test.go +++ b/internal/kernel/kernel-suite_test.go @@ -13,7 +13,7 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/cycle" "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/feat/refine" + "github.com/snivilised/traverse/internal/filtering" "github.com/snivilised/traverse/internal/helpers" "github.com/snivilised/traverse/internal/third/lo" "github.com/snivilised/traverse/pref" @@ -328,9 +328,9 @@ func assertMetrics(entry *naviTE, to *testOptions) { // customSamplingFilter is a custom sampling filter that just happens // to use a glob as part of its implementation. The client can of course -// define their own custom implementation using refine.SampleFilter. +// define their own custom implementation using filter.SampleFilter. type customSamplingFilter struct { - refine.SampleFilter + filtering.Sample description string pattern string } @@ -340,11 +340,11 @@ func (f *customSamplingFilter) Description() string { } func (f *customSamplingFilter) Scope() enums.FilterScope { - return f.SampleFilter.Scope() + return f.Sample.Scope() } func (f *customSamplingFilter) Matching(children []fs.DirEntry) []fs.DirEntry { - return f.SampleFilter.Matching(children, + return f.Sample.Matching(children, func(entry fs.DirEntry, _ int) bool { matched, _ := filepath.Match(f.pattern, entry.Name()) return matched diff --git a/internal/kernel/navigator-filter-regex_test.go b/internal/kernel/navigator-filter-regex_test.go index c0c183e..fb0099c 100644 --- a/internal/kernel/navigator-filter-regex_test.go +++ b/internal/kernel/navigator-filter-regex_test.go @@ -17,7 +17,7 @@ import ( "github.com/snivilised/traverse/pref" ) -var _ = Describe("NavigatorFilterRegex", Ordered, func() { +var _ = Describe("filter", Ordered, func() { var ( vfs fstest.MapFS root string @@ -25,7 +25,7 @@ var _ = Describe("NavigatorFilterRegex", Ordered, func() { BeforeAll(func() { const ( - verbose = true + verbose = false ) vfs, root = helpers.Musico(verbose, @@ -39,7 +39,7 @@ var _ = Describe("NavigatorFilterRegex", Ordered, func() { services.Reset() }) - DescribeTable("regex-filter", + DescribeTable("regex", func(ctx SpecContext, entry *filterTE) { var ( traverseFilter core.TraverseFilter diff --git a/internal/kernel/navigator-sample_test.go b/internal/kernel/navigator-sample_test.go index 150bd90..ed14570 100644 --- a/internal/kernel/navigator-sample_test.go +++ b/internal/kernel/navigator-sample_test.go @@ -672,9 +672,9 @@ var _ = Describe("Sampling", Ordered, func() { filter: &filterTE{ // 🍒 typ: enums.FilterTypeCustom, sample: &customSamplingFilter{ - SampleFilter: tv.NewSampleFilter(enums.ScopeFile), - description: "custom(glob): items with cover prefix", - pattern: "cover*", + Sample: tv.NewCustomSampleFilter(enums.ScopeFile), + description: "custom(glob): items with cover prefix", + pattern: "cover*", }, }, sampleType: enums.SampleTypeCustom, @@ -698,9 +698,9 @@ var _ = Describe("Sampling", Ordered, func() { filter: &filterTE{ // 🍒 typ: enums.FilterTypeCustom, sample: &customSamplingFilter{ - SampleFilter: tv.NewSampleFilter(enums.ScopeFolder), - description: "custom(glob): items with A prefix", - pattern: "A*", + Sample: tv.NewCustomSampleFilter(enums.ScopeFolder), + description: "custom(glob): items with A prefix", + pattern: "A*", }, }, sampleType: enums.SampleTypeCustom, @@ -724,9 +724,9 @@ var _ = Describe("Sampling", Ordered, func() { filter: &filterTE{ // 🍒 typ: enums.FilterTypeCustom, sample: &customSamplingFilter{ - SampleFilter: tv.NewSampleFilter(enums.ScopeFile), - description: "custom(glob): items with .flac suffix", - pattern: "*.flac", + Sample: tv.NewCustomSampleFilter(enums.ScopeFile), + description: "custom(glob): items with .flac suffix", + pattern: "*.flac", }, }, sampleType: enums.SampleTypeCustom, diff --git a/internal/third/lo/condition.go b/internal/third/lo/condition.go index d582dc2..b2b97a4 100644 --- a/internal/third/lo/condition.go +++ b/internal/third/lo/condition.go @@ -20,6 +20,16 @@ func TernaryF[T any](condition bool, ifFunc, elseFunc func() T) T { return elseFunc() } +// TernaryE is the same as TernaryF except the functions defined +// must return an error +func TernaryE[T any](condition bool, ifFunc, elseFunc func() (T, error)) (T, error) { + if condition { + return ifFunc() + } + + return elseFunc() +} + // type IfElse[T any] struct { // result T // done bool diff --git a/traverse-api.go b/traverse-api.go index 9ace991..c57ffc7 100644 --- a/traverse-api.go +++ b/traverse-api.go @@ -5,7 +5,7 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/feat/refine" + "github.com/snivilised/traverse/internal/filtering" "github.com/snivilised/traverse/nfs" "github.com/snivilised/traverse/pref" ) @@ -53,9 +53,6 @@ type ( Subscription = enums.Subscription ResumeStrategy = enums.ResumeStrategy - // 🌀 refine - BaseSampleFilter = refine.SampleFilter - // 🌀 nfs FileSystems = nfs.FileSystems @@ -87,8 +84,8 @@ var ( NewNativeFS = nfs.NewNativeFS NewQueryStatusFS = nfs.NewQueryStatusFS - // 🌀 refine - NewSampleFilter = refine.NewSampleFilter + // 🌀 filtering + NewCustomSampleFilter = filtering.NewCustomSampleFilter // 🌀 pref IfOption = pref.IfOption @@ -139,9 +136,9 @@ var ( // // 🔆 feature layer // resume: ["pref"] -// sampling: ["refine"] -// hiber: ["refine", "services"] -// refine: [] +// sampling: ["filter"] +// hiber: ["filter", "services"] +// filter: [] // // 🔆 central layer // kernel: []