diff --git a/Taskfile.yml b/Taskfile.yml index aff2b76..55cc22b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -80,7 +80,7 @@ tasks: cmds: - go test ./core - tf-filter: + tf: cmds: - go test ./internal/filtering @@ -191,7 +191,6 @@ tasks: - mkdir -p ./internal/kernel/coverage - mkdir -p ./internal/level/coverage - mkdir -p ./internal/measure/coverage - - mkdir -p ./internal/override/coverage - mkdir -p ./internal/services/coverage - mkdir -p ./internal/level/coverage - mkdir -p ./internal/third/bus/coverage diff --git a/builders.go b/builders.go index 5be99a5..66fc2c3 100644 --- a/builders.go +++ b/builders.go @@ -4,7 +4,6 @@ import ( "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/kernel" "github.com/snivilised/traverse/internal/measure" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/third/lo" "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/pref" @@ -18,6 +17,7 @@ type buildArtefacts struct { } type Builders struct { + using *pref.Using readerFS pref.ReadDirFileSystemBuilder queryFS pref.QueryStatusFileSystemBuilder options optionsBuilder @@ -48,22 +48,6 @@ func (bs *Builders) buildAll() (*buildArtefacts, error) { // BUILD NAVIGATOR // - actions := &override.Actions{ - HandleChildren: override.NewActionCtrl[override.HandleChildrenInterceptor]( - func(inspection override.Inspection, mums measure.MutableMetrics) { - // [KEEP-FILTER-IN-SYNC] keep this in sync with filter-plugin/childScheme.init - files := inspection.Sort(enums.EntryTypeFile) - inspection.AssignChildren(files) - - // The behaviour of this default child handler is to assign - // the children and tick the metrics. However, when filtering is - // active, then this handler should be overridden by the filter - // to only tick the child metric, if their parent is filtered in. - // - mums[enums.MetricNoChildFilesFound].Times(uint(len(files))) - }, - ), - } artefacts, navErr := bs.navigator.Build(o, &types.Resources{ FS: FileSystems{ @@ -72,7 +56,6 @@ func (bs *Builders) buildAll() (*buildArtefacts, error) { R: ext.resFS(), }, Supervisor: measure.New(), - Actions: actions, }) if navErr != nil { @@ -86,6 +69,7 @@ func (bs *Builders) buildAll() (*buildArtefacts, error) { // BUILD PLUGINS // plugins, pluginsErr := bs.plugins.build(o, + bs.using, artefacts.Mediator, artefacts.Kontroller, ext.plugin(artefacts), @@ -109,7 +93,6 @@ func (bs *Builders) buildAll() (*buildArtefacts, error) { order := manifest(active) artefacts.Mediator.Arrange(active, order) pi := &types.PluginInit{ - Actions: actions, O: o, Controls: artefacts.Mediator.Controls(), } diff --git a/director.go b/director.go index 19dba90..6cb780c 100644 --- a/director.go +++ b/director.go @@ -5,6 +5,7 @@ import ( "github.com/snivilised/traverse/internal/feat/filter" "github.com/snivilised/traverse/internal/feat/hiber" + "github.com/snivilised/traverse/internal/feat/nanny" "github.com/snivilised/traverse/internal/feat/resume" "github.com/snivilised/traverse/internal/feat/sampling" "github.com/snivilised/traverse/internal/kernel" @@ -13,12 +14,12 @@ import ( "github.com/snivilised/traverse/pref" ) -type ifActive func(o *pref.Options, mediator types.Mediator) types.Plugin +type ifActive func(o *pref.Options, using *pref.Using, mediator types.Mediator) types.Plugin // features interrogates options and invokes requests on behalf of the user // to activate features according to option selections. other plugins will // be initialised after primary plugins -func features(o *pref.Options, mediator types.Mediator, +func features(o *pref.Options, using *pref.Using, mediator types.Mediator, kc types.KernelController, others ...types.Plugin, ) (plugins []types.Plugin, err error) { @@ -29,7 +30,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, filter.IfActive, sampling.IfActive, + hiber.IfActive, nanny.IfActive, filter.IfActive, sampling.IfActive, } ) @@ -44,7 +45,7 @@ func features(o *pref.Options, mediator types.Mediator, }, lo.Reduce(all, func(acc []types.Plugin, query ifActive, _ int) []types.Plugin { - if plugin := query(o, mediator); plugin != nil { + if plugin := query(o, using, mediator); plugin != nil { acc = append(acc, plugin) } return acc @@ -73,6 +74,7 @@ func Prime(using *pref.Using, settings ...pref.Option) *Builders { // by a panic. // return &Builders{ + using: using, readerFS: pref.CreateReadDirFS(func() fs.ReadDirFS { if using.GetReadDirFS != nil { return using.GetReadDirFS() @@ -129,6 +131,7 @@ func Resume(was *Was, settings ...pref.Option) *Builders { // path was. // return &Builders{ + using: &was.Using, readerFS: pref.CreateReadDirFS(func() fs.ReadDirFS { if was.Using.GetReadDirFS != nil { return was.Using.GetReadDirFS() diff --git a/enums/metric-en-auto.go b/enums/metric-en-auto.go index 69aa7a2..df4e88c 100644 --- a/enums/metric-en-auto.go +++ b/enums/metric-en-auto.go @@ -8,12 +8,12 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[MetricNoFilesInvoked-0] - _ = x[MetricNoFilesFilteredOut-1] - _ = x[MetricNoFoldersInvoked-2] - _ = x[MetricNoFoldersFilteredOut-3] - _ = x[MetricNoChildFilesFound-4] - _ = x[MetricNoChildFilesFilteredOut-5] + _ = x[MetricNoFilesInvoked-1] + _ = x[MetricNoFilesFilteredOut-2] + _ = x[MetricNoFoldersInvoked-3] + _ = x[MetricNoFoldersFilteredOut-4] + _ = x[MetricNoChildFilesFound-5] + _ = x[MetricNoChildFilesFilteredOut-6] } const _Metric_name = "metric-no-of-filesmetric-no-of-files-filtered-outmetric-no-of-foldersmetric-no-of-folders-filtered-outmetric-no-of-child-files-foundmetric-no-of-child-files-found" @@ -21,8 +21,9 @@ const _Metric_name = "metric-no-of-filesmetric-no-of-files-filtered-outmetric-no var _Metric_index = [...]uint8{0, 18, 49, 69, 102, 132, 162} func (i Metric) String() string { + i -= 1 if i >= Metric(len(_Metric_index)-1) { - return "Metric(" + strconv.FormatInt(int64(i), 10) + ")" + return "Metric(" + strconv.FormatInt(int64(i+1), 10) + ")" } return _Metric_name[_Metric_index[i]:_Metric_index[i+1]] } diff --git a/enums/metric-en.go b/enums/metric-en.go index b60431f..c91d486 100644 --- a/enums/metric-en.go +++ b/enums/metric-en.go @@ -7,9 +7,11 @@ type Metric uint // if new metrics are added, ensure that navigationMetricsFactory.new is kept // in sync. const ( + _ Metric = iota + // MetricNoFilesInvoked represents the no of files invoked for during traversal // - MetricNoFilesInvoked Metric = iota // metric-no-of-files + MetricNoFilesInvoked // metric-no-of-files // MetricNoFilesFilteredOut represents the no of files filtered out // diff --git a/enums/role-en-auto.go b/enums/role-en-auto.go index dee6e1f..7b6f083 100644 --- a/enums/role-en-auto.go +++ b/enums/role-en-auto.go @@ -13,11 +13,12 @@ func _() { _ = x[RoleClientFilter-2] _ = x[RoleHibernate-3] _ = x[RoleSampler-4] + _ = x[RoleNanny-5] } -const _Role_name = "undefined-roleanchor-roleclient-filter-rolehibernate-rolesampler-role" +const _Role_name = "undefined-roleanchor-roleclient-filter-rolehibernate-rolesampler-rolenanny-role" -var _Role_index = [...]uint8{0, 14, 25, 43, 57, 69} +var _Role_index = [...]uint8{0, 14, 25, 43, 57, 69, 79} func (i Role) String() string { if i >= Role(len(_Role_index)-1) { diff --git a/enums/role-en.go b/enums/role-en.go index e61e6ae..98a3455 100644 --- a/enums/role-en.go +++ b/enums/role-en.go @@ -10,4 +10,5 @@ const ( RoleClientFilter // client-filter-role RoleHibernate // hibernate-role RoleSampler // sampler-role + RoleNanny // nanny-role ) diff --git a/internal-traverse-defs.go b/internal-traverse-defs.go index ee3ab6b..70a9039 100644 --- a/internal-traverse-defs.go +++ b/internal-traverse-defs.go @@ -21,6 +21,7 @@ func (fn optionals) build(ext extent) (*pref.Options, error) { // pluginsBuilder type pluginsBuilder interface { build(o *pref.Options, + using *pref.Using, mediator types.Mediator, kc types.KernelController, others ...types.Plugin, @@ -28,17 +29,19 @@ type pluginsBuilder interface { } type activated func(*pref.Options, + *pref.Using, types.Mediator, types.KernelController, ...types.Plugin, ) ([]types.Plugin, error) func (fn activated) build(o *pref.Options, + using *pref.Using, mediator types.Mediator, kc types.KernelController, others ...types.Plugin, ) ([]types.Plugin, error) { - return fn(o, mediator, kc, others...) + return fn(o, using, mediator, kc, others...) } type fsBuilder interface { diff --git a/internal/feat/filter/errors.go b/internal/feat/filter/errors.go new file mode 100644 index 0000000..59063d7 --- /dev/null +++ b/internal/feat/filter/errors.go @@ -0,0 +1,20 @@ +package filter + +import ( + "github.com/pkg/errors" +) + +// โŒ InvalidNotificationMuteRequested error: this scenario should +// never happen, if it does then its an internal issue to be +// addressed here in the filter plugin. + +// IsNoSubordinateHybridSchemesDefinedError uses errors.Is to check +// if the err's error tree contains the core error: +// InvalidNotificationMuteRequestedError +func IsNoSubordinateHybridSchemesDefinedError(err error) bool { + return errors.Is(err, ErrNoSubordinateHybridSchemesDefined) +} + +var ErrNoSubordinateHybridSchemesDefined = errors.New( + "invalid filter scheme, both primary and child not set", +) diff --git a/internal/feat/filter/filter-plugin.go b/internal/feat/filter/filter-plugin.go index f3f5f26..d569563 100644 --- a/internal/feat/filter/filter-plugin.go +++ b/internal/feat/filter/filter-plugin.go @@ -7,14 +7,13 @@ import ( "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/kernel" "github.com/snivilised/traverse/internal/measure" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/pref" ) -func IfActive(o *pref.Options, mediator types.Mediator) types.Plugin { +func IfActive(o *pref.Options, _ *pref.Using, mediator types.Mediator) types.Plugin { if o.Filter.IsFilteringActive() { - return &Plugin{ + return &plugin{ BasePlugin: kernel.BasePlugin{ O: o, Mediator: mediator, @@ -28,28 +27,31 @@ func IfActive(o *pref.Options, mediator types.Mediator) types.Plugin { return nil } -// Plugin manages all filtering aspects of navigation -type Plugin struct { +// plugin manages all filtering aspects of navigation +type plugin struct { kernel.BasePlugin sink pref.FilteringSink crate measure.Crate scheme scheme } -func (p *Plugin) Name() string { +func (p *plugin) Name() string { return "filtering" } -func (p *Plugin) Register(kc types.KernelController) error { - p.Kontroller = kc +func (p *plugin) Register(kc types.KernelController) error { + if err := p.BasePlugin.Register(kc); err != nil { + return err + } + return p.scheme.create() } -func (p *Plugin) Next(node *core.Node, inspection override.Inspection) (bool, error) { +func (p *plugin) Next(node *core.Node, inspection types.Inspection) (bool, error) { return p.scheme.next(node, inspection) } -func (p *Plugin) Init(pi *types.PluginInit) error { +func (p *plugin) Init(pi *types.PluginInit) error { p.crate.Mums = p.Mediator.Supervisor().Many( enums.MetricNoFoldersFilteredOut, enums.MetricNoFilesFilteredOut, diff --git a/internal/feat/filter/scheme-custom.go b/internal/feat/filter/scheme-custom.go new file mode 100644 index 0000000..3307444 --- /dev/null +++ b/internal/feat/filter/scheme-custom.go @@ -0,0 +1,49 @@ +package filter + +import ( + "github.com/snivilised/traverse/core" + "github.com/snivilised/traverse/enums" + "github.com/snivilised/traverse/internal/measure" + "github.com/snivilised/traverse/internal/third/lo" + "github.com/snivilised/traverse/internal/types" + "github.com/snivilised/traverse/pref" +) + +type customScheme struct { + common + filter core.TraverseFilter +} + +func (s *customScheme) create() error { + s.filter = s.o.Filter.Custom + + if s.o.Filter.Sink != nil { + s.o.Filter.Sink(pref.FilterReply{ + Node: s.filter, + }) + } + + return s.filter.Validate() +} + +func (s *customScheme) next(node *core.Node, + _ types.Inspection, +) (bool, error) { + return matchNext(s.filter, node, s.crate) +} + +func matchNext(filter core.TraverseFilter, + node *core.Node, crate *measure.Crate, +) (bool, error) { + matched := filter.IsMatch(node) + + if !matched { + filteredOutMetric := lo.Ternary(node.IsFolder(), + enums.MetricNoFoldersFilteredOut, + enums.MetricNoFilesFilteredOut, + ) + crate.Mums[filteredOutMetric].Tick() + } + + return matched, nil +} diff --git a/internal/feat/filter/scheme-hybrid.go b/internal/feat/filter/scheme-hybrid.go new file mode 100644 index 0000000..685756c --- /dev/null +++ b/internal/feat/filter/scheme-hybrid.go @@ -0,0 +1,73 @@ +package filter + +import ( + "github.com/snivilised/traverse/core" + "github.com/snivilised/traverse/internal/measure" + "github.com/snivilised/traverse/internal/types" +) + +// hybridScheme required because node based filtering can be active at +// the same time as child filtering. +// +// The hybridScheme is related to the nanny plugin. If no filtering is active, +// then the nanny will become active and be responsible for handling the +// children. If filtering is active, then the nanny will remain dormant as +// the filter plugin becomes active. The nannyScheme will take over handling +// the children, using the child filter to do so. The primary scheme +// performs node based filtering. + +type hybridScheme struct { + common + primary scheme + nanny scheme +} + +func (s *hybridScheme) create() error { + if s.primary == nil && s.nanny == nil { + return ErrNoSubordinateHybridSchemesDefined + } + + if s.primary != nil { + if err := s.primary.create(); err != nil { + return err + } + } + + if s.nanny != nil { + return s.nanny.create() + } + + return nil +} + +func (s *hybridScheme) init(pi *types.PluginInit, crate *measure.Crate) { + s.common.init(pi, crate) + + if s.primary != nil { + s.primary.init(pi, crate) + } + + if s.nanny != nil { + s.nanny.init(pi, crate) + } +} + +func (s *hybridScheme) next(node *core.Node, + inspection types.Inspection, +) (bool, error) { + if s.primary != nil { + invokeNext, err := s.primary.next(node, inspection) + + if invokeNext && s.nanny != nil { + // The nanny has no say in wether the next link is invoked, + // therefore we ignore its next result + _, err := s.nanny.next(node, inspection) + + return invokeNext, err + } + + return invokeNext, err + } + + return true, nil +} diff --git a/internal/feat/filter/scheme-nanny.go b/internal/feat/filter/scheme-nanny.go new file mode 100644 index 0000000..a7a1543 --- /dev/null +++ b/internal/feat/filter/scheme-nanny.go @@ -0,0 +1,49 @@ +package filter + +import ( + "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/types" + "github.com/snivilised/traverse/pref" +) + +type nannyScheme struct { + common + filter core.ChildTraverseFilter +} + +func (s *nannyScheme) create() error { + filter, err := filtering.NewChild(s.o.Filter.Child) + + if err != nil { + return err + } + s.filter = filter + + if s.o.Filter.Sink != nil { + s.o.Filter.Sink(pref.FilterReply{ + Child: s.filter, + }) + } + + return nil +} + +func (s *nannyScheme) init(pi *types.PluginInit, crate *measure.Crate) { + s.common.init(pi, crate) +} + +func (s *nannyScheme) next(_ *core.Node, inspection types.Inspection) (bool, error) { + files := inspection.Sort(enums.EntryTypeFile) + matching := s.filter.Matching(files) + + inspection.AssignChildren(matching) + s.crate.Mums[enums.MetricNoChildFilesFound].Times(uint(len(matching))) + + filteredOut := len(files) - len(matching) + s.crate.Mums[enums.MetricNoChildFilesFilteredOut].Times(uint(filteredOut)) + + return true, nil +} diff --git a/internal/feat/filter/scheme-native.go b/internal/feat/filter/scheme-native.go new file mode 100644 index 0000000..1641af7 --- /dev/null +++ b/internal/feat/filter/scheme-native.go @@ -0,0 +1,36 @@ +package filter + +import ( + "github.com/snivilised/traverse/core" + "github.com/snivilised/traverse/internal/filtering" + "github.com/snivilised/traverse/internal/types" + "github.com/snivilised/traverse/pref" +) + +type nativeScheme struct { + common + filter core.TraverseFilter +} + +func (s *nativeScheme) create() error { + filter, err := filtering.New(s.o.Filter.Node, &s.o.Filter) + if err != nil { + return err + } + + s.filter = filter + + if s.o.Filter.Sink != nil { + s.o.Filter.Sink(pref.FilterReply{ + Node: s.filter, + }) + } + + return nil +} + +func (s *nativeScheme) next(node *core.Node, + _ types.Inspection, +) (bool, error) { + return matchNext(s.filter, node, s.crate) +} diff --git a/internal/feat/filter/scheme-sampler.go b/internal/feat/filter/scheme-sampler.go new file mode 100644 index 0000000..6326430 --- /dev/null +++ b/internal/feat/filter/scheme-sampler.go @@ -0,0 +1,65 @@ +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/third/lo" + "github.com/snivilised/traverse/internal/types" + "github.com/snivilised/traverse/nfs" + "github.com/snivilised/traverse/pref" +) + +type samplerScheme struct { + common + filter core.SampleTraverseFilter +} + +func (s *samplerScheme) create() error { + filter, err := filtering.NewSample(s.o.Filter.Sample, &s.o.Sampling) + + if err != nil { + return err + } + + s.filter = filter + + // the filter plugin performs premature filtering (with fs.DirEntry as opposed + // to core.Node) on behalf of the sampler. + s.o.Hooks.ReadDirectory.Chain( + func(result []fs.DirEntry, err error, + _ fs.ReadDirFS, _ string, + ) ([]fs.DirEntry, error) { + return s.filter.Matching(result), err + }) + + if s.o.Filter.Sink != nil { + s.o.Filter.Sink(pref.FilterReply{ + Sampler: s.filter, + }) + } + + return filter.Validate() +} + +func (s *samplerScheme) next(node *core.Node, + inspection types.Inspection, +) (bool, error) { + if node.Extension.Scope.IsRoot() { + matching := s.filter.Matching( + []fs.DirEntry{nfs.FromFileInfo(node.Info)}, + ) + result := len(matching) > 0 + + lo.Ternary(result, + s.crate.Mums[enums.MetricNoChildFilesFound], + s.crate.Mums[enums.MetricNoChildFilesFilteredOut], + ).Times(uint(len(inspection.Contents().Files()))) + + return result, nil + } + + return true, nil +} diff --git a/internal/feat/filter/schemes.go b/internal/feat/filter/schemes.go index 38c737d..bebd214 100644 --- a/internal/feat/filter/schemes.go +++ b/internal/feat/filter/schemes.go @@ -1,16 +1,10 @@ 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/override" "github.com/snivilised/traverse/internal/third/lo" "github.com/snivilised/traverse/internal/types" - "github.com/snivilised/traverse/nfs" "github.com/snivilised/traverse/pref" ) @@ -18,42 +12,10 @@ type ( scheme interface { create() error init(pi *types.PluginInit, crate *measure.Crate) - next(node *core.Node, inspection override.Inspection) (bool, error) + next(node *core.Node, inspection types.Inspection) (bool, error) } ) -// need to narrow down the options -func newScheme(o *pref.Options) scheme { - c := common{o: o} - - if o.Filter.IsNodeFilteringActive() { - return &nativeScheme{ - common: c, - } - } - - if o.Filter.IsChildFilteringActive() { - return &childScheme{ - common: c, - } - } - - if o.Filter.IsSampleFilteringActive() { - return &samplerScheme{ - common: c, - } - } - - // If none of the other schemes active, then according to - // FilterOptions.IsFilteringActive, the only other possibility - // is that the client has defined a custom filter, so we don't - // need to check again. - // - return &customScheme{ - common: c, - } -} - type common struct { o *pref.Options crate *measure.Crate @@ -63,175 +25,65 @@ func (f *common) init(_ *types.PluginInit, crate *measure.Crate) { f.crate = crate } -type nativeScheme struct { - common - filter core.TraverseFilter -} - -func (f *nativeScheme) create() error { - filter, err := filtering.New(f.o.Filter.Node, &f.o.Filter) - if err != nil { - return err - } - - f.filter = filter - - if f.o.Filter.Sink != nil { - f.o.Filter.Sink(pref.FilterReply{ - Node: f.filter, - }) - } - - return nil -} - -func (f *nativeScheme) next(node *core.Node, _ override.Inspection) (bool, error) { - return matchNext(f.filter, node, f.crate) -} - -type childScheme struct { - common - filter core.ChildTraverseFilter -} - -func (f *childScheme) create() error { - filter, err := filtering.NewChild(f.o.Filter.Child) +func newScheme(o *pref.Options) scheme { + c := common{o: o} + primary, nanny := binary(o) - if err != nil { - return err + if primary != nil && nanny != nil { + return &hybridScheme{ + common: c, + primary: primary, + nanny: nanny, + } } - f.filter = filter - if f.o.Filter.Sink != nil { - f.o.Filter.Sink(pref.FilterReply{ - Child: f.filter, - }) + if primary != nil { + return primary } - return nil + return nanny } -func (f *childScheme) init(pi *types.PluginInit, crate *measure.Crate) { - f.common.init(pi, crate) - - // [KEEP-FILTER-IN-SYNC] keep this in sync with the default - // behaviour in builders.override.Actions - // - pi.Actions.HandleChildren.Intercept( - func(inspection override.Inspection, mums measure.MutableMetrics) { - files := inspection.Sort(enums.EntryTypeFile) - matching := f.filter.Matching(files) - - inspection.AssignChildren(matching) - mums[enums.MetricNoChildFilesFound].Times(uint(len(files))) +func binary(o *pref.Options) (primary, nanny scheme) { + c := common{o: o} - filteredOut := len(files) - len(matching) - f.crate.Mums[enums.MetricNoChildFilesFilteredOut].Times(uint(filteredOut)) + primary = unary(c) + nanny = lo.TernaryF(o.Filter.IsChildFilteringActive(), + func() scheme { + return &nannyScheme{ + common: c, + } }, - ) -} - -func (f *childScheme) next(_ *core.Node, _ override.Inspection) (bool, error) { - return false, nil -} - -type samplerScheme struct { - common - filter core.SampleTraverseFilter -} - -func (f *samplerScheme) create() error { - filter, err := filtering.NewSample(f.o.Filter.Sample, &f.o.Sampling) - - if err != nil { - return err - } - - f.filter = filter - - // the filter plugin performs premature filtering (with fs.DirEntry as opposed - // to core.Node) on behalf of the sampler. - f.o.Hooks.ReadDirectory.Chain( - func(result []fs.DirEntry, err error, - _ fs.ReadDirFS, _ string, - ) ([]fs.DirEntry, error) { - return f.filter.Matching(result), err - }) - - if f.o.Filter.Sink != nil { - f.o.Filter.Sink(pref.FilterReply{ - Sampler: f.filter, - }) - } - - return filter.Validate() -} - -func (f *samplerScheme) init(pi *types.PluginInit, crate *measure.Crate) { - f.common.init(pi, crate) - - // [KEEP-FILTER-IN-SYNC] keep this in sync with the default - // behaviour in builders.override.Actions - // - pi.Actions.HandleChildren.Intercept( - func(inspection override.Inspection, _ measure.MutableMetrics) { - files := inspection.Sort(enums.EntryTypeFile) - matching := f.filter.Matching(files) - - inspection.AssignChildren(matching) + func() scheme { + return nil }, ) -} -func (f *samplerScheme) next(node *core.Node, inspection override.Inspection) (bool, error) { - if node.Extension.Scope.IsRoot() { - matching := f.filter.Matching( - []fs.DirEntry{nfs.FromFileInfo(node.Info)}, - ) - result := len(matching) > 0 - - lo.Ternary(result, - f.crate.Mums[enums.MetricNoChildFilesFound], - f.crate.Mums[enums.MetricNoChildFilesFilteredOut], - ).Times(uint(len(inspection.Contents().Files()))) - - return result, nil + if nanny == nil { + return primary, nil } - return true, nil + return primary, nanny } -type customScheme struct { - common - filter core.TraverseFilter -} - -func (f *customScheme) create() error { - f.filter = f.o.Filter.Custom - - if f.o.Filter.Sink != nil { - f.o.Filter.Sink(pref.FilterReply{ - Node: f.filter, - }) +func unary(c common) scheme { + if c.o.Filter.IsCustomFilteringActive() { + return &customScheme{ + common: c, + } } - return f.filter.Validate() -} - -func (f *customScheme) next(node *core.Node, _ override.Inspection) (bool, error) { - return matchNext(f.filter, node, f.crate) -} - -func matchNext(filter core.TraverseFilter, node *core.Node, crate *measure.Crate) (bool, error) { - matched := filter.IsMatch(node) + if c.o.Filter.IsNodeFilteringActive() { + return &nativeScheme{ + common: c, + } + } - if !matched { - filteredOutMetric := lo.Ternary(node.IsFolder(), - enums.MetricNoFoldersFilteredOut, - enums.MetricNoFilesFilteredOut, - ) - crate.Mums[filteredOutMetric].Tick() + if c.o.Filter.IsSampleFilteringActive() { + return &samplerScheme{ + common: c, + } } - return matched, nil + return nil // only nanny is active } diff --git a/internal/feat/hiber/hibernate-defs.go b/internal/feat/hiber/hibernate-defs.go index e24daf2..3cc537f 100644 --- a/internal/feat/hiber/hibernate-defs.go +++ b/internal/feat/hiber/hibernate-defs.go @@ -4,7 +4,7 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/cycle" "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/override" + "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/pref" ) @@ -29,7 +29,7 @@ func RestoreOptions() { } type ( - nextFn func(node *core.Node, inspection override.Inspection) (bool, error) + nextFn func(node *core.Node, inspection types.Inspection) (bool, error) state struct { next nextFn @@ -43,7 +43,7 @@ type ( profile interface { init(controls *cycle.Controls) error - next(node *core.Node, inspection override.Inspection) (bool, error) + next(node *core.Node, inspection types.Inspection) (bool, error) } common struct { diff --git a/internal/feat/hiber/hibernate-plugin.go b/internal/feat/hiber/hibernate-plugin.go index a513951..8bdcd39 100644 --- a/internal/feat/hiber/hibernate-plugin.go +++ b/internal/feat/hiber/hibernate-plugin.go @@ -4,14 +4,13 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/kernel" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/pref" ) -func IfActive(o *pref.Options, mediator types.Mediator) types.Plugin { +func IfActive(o *pref.Options, _ *pref.Using, mediator types.Mediator) types.Plugin { if o.Hibernate.IsHibernateActive() { - return &Plugin{ + return &plugin{ BasePlugin: kernel.BasePlugin{ Mediator: mediator, ActivatedRole: enums.RoleHibernate, @@ -28,26 +27,20 @@ func IfActive(o *pref.Options, mediator types.Mediator) types.Plugin { return nil } -type Plugin struct { +type plugin struct { kernel.BasePlugin profile profile } -func (p *Plugin) Name() string { +func (p *plugin) Name() string { return "hibernation" } -func (p *Plugin) Register(kc types.KernelController) error { - p.Kontroller = kc - - return nil -} - -func (p *Plugin) Next(node *core.Node, inspection override.Inspection) (bool, error) { +func (p *plugin) Next(node *core.Node, inspection types.Inspection) (bool, error) { return p.profile.next(node, inspection) } -func (p *Plugin) Init(pi *types.PluginInit) error { +func (p *plugin) Init(pi *types.PluginInit) error { if err := p.profile.init(pi.Controls); err != nil { return err } diff --git a/internal/feat/hiber/hibernate-simple-profile.go b/internal/feat/hiber/hibernate-simple-profile.go index 2f730d8..b88db2a 100644 --- a/internal/feat/hiber/hibernate-simple-profile.go +++ b/internal/feat/hiber/hibernate-simple-profile.go @@ -8,7 +8,7 @@ import ( "github.com/snivilised/traverse/cycle" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/filtering" - "github.com/snivilised/traverse/internal/override" + "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/locale" ) @@ -65,14 +65,14 @@ func (p *simple) transition(en enums.Hibernation) { p.current = p.states[en] } -func (p *simple) next(node *core.Node, inspection override.Inspection) (bool, error) { +func (p *simple) next(node *core.Node, inspection types.Inspection) (bool, error) { return p.current.next(node, inspection) } func (p *simple) create() hibernateStates { return hibernateStates{ enums.HibernationPending: state{ - next: func(node *core.Node, _ override.Inspection) (bool, error) { + next: func(node *core.Node, _ types.Inspection) (bool, error) { if p.common.triggers.wake.IsMatch(node) { p.controls.Wake.Dispatch()(p.common.triggers.wake.Description()) p.transition(enums.HibernationActive) @@ -87,7 +87,7 @@ func (p *simple) create() hibernateStates { }, enums.HibernationActive: state{ - next: func(node *core.Node, _ override.Inspection) (bool, error) { + next: func(node *core.Node, _ types.Inspection) (bool, error) { if p.common.triggers.sleep.IsMatch(node) { p.controls.Sleep.Dispatch()(p.common.triggers.sleep.Description()) p.transition(enums.HibernationRetired) @@ -103,7 +103,7 @@ func (p *simple) create() hibernateStates { }, enums.HibernationRetired: state{ - next: func(_ *core.Node, _ override.Inspection) (bool, error) { + next: func(_ *core.Node, _ types.Inspection) (bool, error) { return false, fs.SkipAll }, }, diff --git a/internal/feat/nanny/nanny-plugin.go b/internal/feat/nanny/nanny-plugin.go new file mode 100644 index 0000000..084db89 --- /dev/null +++ b/internal/feat/nanny/nanny-plugin.go @@ -0,0 +1,56 @@ +package nanny + +// ๐Ÿ“ฆ pkg: nanny - handles a node's children for folders with children subscription + +import ( + "github.com/snivilised/traverse/core" + "github.com/snivilised/traverse/enums" + "github.com/snivilised/traverse/internal/kernel" + "github.com/snivilised/traverse/internal/measure" + "github.com/snivilised/traverse/internal/types" + "github.com/snivilised/traverse/pref" +) + +func IfActive(o *pref.Options, + using *pref.Using, mediator types.Mediator, +) types.Plugin { + if using.Subscription == enums.SubscribeFoldersWithFiles && + !o.Filter.IsFilteringActive() { + return &plugin{ + BasePlugin: kernel.BasePlugin{ + O: o, + Mediator: mediator, + ActivatedRole: enums.RoleNanny, + }, + } + } + + return nil +} + +type plugin struct { + kernel.BasePlugin + crate measure.Crate +} + +func (p *plugin) Name() string { + return "nanny" +} + +func (p *plugin) Next(node *core.Node, + inspection types.Inspection, +) (bool, error) { + files := inspection.Sort(enums.EntryTypeFile) + node.Children = files + p.crate.Mums[enums.MetricNoChildFilesFound].Times(uint(len(files))) + + return true, nil +} + +func (p *plugin) Init(_ *types.PluginInit) error { + p.crate.Mums = p.Mediator.Supervisor().Many( + enums.MetricNoChildFilesFound, + ) + + return p.Mediator.Decorate(p) +} diff --git a/internal/feat/resume/resume-plugin.go b/internal/feat/resume/resume-plugin.go index 831c254..2d8eafd 100644 --- a/internal/feat/resume/resume-plugin.go +++ b/internal/feat/resume/resume-plugin.go @@ -6,7 +6,6 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/kernel" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/pref" ) @@ -20,13 +19,7 @@ func (p *Plugin) Name() string { return "resume" } -func (p *Plugin) Register(kc types.KernelController) error { - p.Kontroller = kc - - return nil -} - -func (p *Plugin) Next(node *core.Node, inspection override.Inspection) (bool, error) { +func (p *Plugin) Next(node *core.Node, inspection types.Inspection) (bool, error) { _, _ = node, inspection // apply the wake filter diff --git a/internal/feat/sampling/controller.go b/internal/feat/sampling/controller.go index 3105948..18b63b2 100644 --- a/internal/feat/sampling/controller.go +++ b/internal/feat/sampling/controller.go @@ -5,8 +5,8 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/third/lo" + "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/nfs" "github.com/snivilised/traverse/pref" ) @@ -20,7 +20,7 @@ func (p *controller) Role() enums.Role { return enums.RoleSampler } -func (p *controller) Next(_ *core.Node, _ override.Inspection) (bool, error) { +func (p *controller) Next(_ *core.Node, _ types.Inspection) (bool, error) { return true, nil } diff --git a/internal/feat/sampling/sampling-plugin.go b/internal/feat/sampling/sampling-plugin.go index 7997f77..72e8533 100644 --- a/internal/feat/sampling/sampling-plugin.go +++ b/internal/feat/sampling/sampling-plugin.go @@ -7,9 +7,9 @@ import ( "github.com/snivilised/traverse/pref" ) -func IfActive(o *pref.Options, mediator types.Mediator) types.Plugin { +func IfActive(o *pref.Options, _ *pref.Using, mediator types.Mediator) types.Plugin { if o.Sampling.IsSamplingActive() { - return &Plugin{ + return &plugin{ BasePlugin: kernel.BasePlugin{ O: o, Mediator: mediator, @@ -28,22 +28,16 @@ type samplingOptions struct { sampling *pref.SamplingOptions } -type Plugin struct { +type plugin struct { kernel.BasePlugin ctrl controller } -func (p *Plugin) Name() string { +func (p *plugin) Name() string { return "sampling" } -func (p *Plugin) Register(kc types.KernelController) error { - p.Kontroller = kc - - return nil -} - -func (p *Plugin) Init(_ *types.PluginInit) error { +func (p *plugin) Init(_ *types.PluginInit) error { p.O.Hooks.ReadDirectory.Chain( p.ctrl.sample, ) diff --git a/internal/filtering/extended-glob.go b/internal/filtering/extended-glob.go index 7c371d5..9d1125e 100644 --- a/internal/filtering/extended-glob.go +++ b/internal/filtering/extended-glob.go @@ -1,6 +1,7 @@ package filtering import ( + "cmp" "io/fs" "path/filepath" "slices" @@ -106,21 +107,17 @@ func filterFileByExtendedGlob(name, base, exclusion string, return false } - return lo.TernaryF(anyExtension, + return cmp.Or( func() bool { - return true - }, + return anyExtension + }(), + func() bool { + return extension == "" && len(suffixes) == 0 + }(), func() bool { - return lo.TernaryF(extension == "", - func() bool { - return len(suffixes) == 0 - }, - func() bool { - return lo.Contains( - suffixes, strings.ToLower(strings.TrimPrefix(extension, ".")), - ) - }, + return lo.Contains( + suffixes, strings.ToLower(strings.TrimPrefix(extension, ".")), ) - }, + }(), ) } diff --git a/internal/kernel/navigator-folders-with-files-filtered_test.go b/internal/filtering/filter-hybrid_test.go similarity index 57% rename from internal/kernel/navigator-folders-with-files-filtered_test.go rename to internal/filtering/filter-hybrid_test.go index 0b7eb8c..5c7227d 100644 --- a/internal/kernel/navigator-folders-with-files-filtered_test.go +++ b/internal/filtering/filter-hybrid_test.go @@ -1,4 +1,4 @@ -package kernel_test +package filtering_test import ( "fmt" @@ -19,7 +19,7 @@ import ( "github.com/snivilised/traverse/pref" ) -var _ = Describe("NavigatorFoldersWithFiles", Ordered, func() { +var _ = Describe("feature", Ordered, func() { var ( FS fstest.MapFS root string @@ -48,30 +48,26 @@ var _ = Describe("NavigatorFoldersWithFiles", Ordered, func() { }) DescribeTable("folders with files filtered", - func(ctx SpecContext, entry *helpers.FilterTE) { + func(ctx SpecContext, entry *helpers.HybridFilterTE) { var ( childFilter core.ChildTraverseFilter ) recording := make(helpers.RecordingMap) filterDefs := &pref.FilterOptions{ - Child: &core.ChildFilterDef{ - Type: enums.FilterTypeGlob, - Description: entry.Description, - Pattern: entry.Pattern, - Negate: entry.Negate, - }, + Node: &entry.NodeDef, + Child: &entry.ChildDef, Sink: func(reply pref.FilterReply) { childFilter = reply.Child }, } path := helpers.Path(root, entry.Relative) - callback := func(item *core.Node) error { actualNoChildren := len(item.Children) GinkgoWriter.Printf( - "===> ๐Ÿ’  Compound Glob Filter(%v, children: %v) source: '%v', node-name: '%v', node-scope: '%v', depth: '%v'\n", + "===> ๐Ÿ’  Child Glob Filter(%v, children: %v)"+ + "source: '%v', node-name: '%v', node-scope: '%v', depth: '%v'\n", childFilter.Description(), actualNoChildren, childFilter.Source(), @@ -96,6 +92,8 @@ var _ = Describe("NavigatorFoldersWithFiles", Ordered, func() { return FS }, }, + tv.WithOnBegin(helpers.Begin("๐Ÿ›ก๏ธ")), + tv.WithOnEnd(helpers.End("๐Ÿ")), tv.WithFilter(filterDefs), tv.WithHookQueryStatus( func(qsys fs.StatFS, path string) (fs.FileInfo, error) { @@ -109,94 +107,70 @@ var _ = Describe("NavigatorFoldersWithFiles", Ordered, func() { ), )).Navigate(ctx) - Expect(err).To(Succeed()) - - if entry.Mandatory != nil { - for _, name := range entry.Mandatory { - _, found := recording[name] - Expect(found).To(BeTrue(), helpers.Reason(name)) - } - } - - if entry.Prohibited != nil { - for _, name := range entry.Prohibited { - _, found := recording[name] - Expect(found).To(BeFalse(), helpers.Reason(name)) - } - } - - for n, actualNoChildren := range entry.ExpectedNoOf.Children { - expected := recording[n] - - Expect(expected).To(Equal(actualNoChildren), - helpers.BecauseQuantity("Incorrect no of children", - expected, - actualNoChildren, - ), - ) - } - - Expect(result.Metrics().Count(enums.MetricNoFilesInvoked)).To( - Equal(entry.ExpectedNoOf.Files), - helpers.BecauseQuantity("Incorrect no of files", - int(entry.ExpectedNoOf.Files), - int(result.Metrics().Count(enums.MetricNoFilesInvoked)), - ), - ) - - Expect(result.Metrics().Count(enums.MetricNoFoldersInvoked)).To( - Equal(entry.ExpectedNoOf.Folders), - helpers.BecauseQuantity("Incorrect no of folders", - int(entry.ExpectedNoOf.Folders), - int(result.Metrics().Count(enums.MetricNoFoldersInvoked)), - ), - ) + helpers.AssertNavigation(&entry.NaviTE, &helpers.TestOptions{ + FS: FS, + Recording: recording, + Path: path, + Result: result, + Err: err, + }) }, - func(entry *helpers.FilterTE) string { + func(entry *helpers.HybridFilterTE) string { return fmt.Sprintf("๐Ÿงช ===> given: '%v'", entry.Given) }, - // folders with files not implemented yet - XEntry(nil, &helpers.FilterTE{ + Entry(nil, &helpers.HybridFilterTE{ NaviTE: helpers.NaviTE{ Given: "folder(with files): glob filter", Relative: "RETRO-WAVE", Subscription: enums.SubscribeFoldersWithFiles, ExpectedNoOf: helpers.Quantities{ - Files: 0, - Folders: 8, + Folders: 6, Children: map[string]int{ - "Night Drive": 2, "Northern Council": 2, "Teenage Color": 2, "Innerworld": 2, }, }, }, - Description: "items with '.flac' suffix", - Pattern: "*.flac", + NodeDef: core.FilterDef{ + Type: enums.FilterTypeGlob, + Description: "folders contains o", + Pattern: "*o*", + Scope: enums.ScopeFolder, + }, + ChildDef: core.ChildFilterDef{ + Type: enums.FilterTypeGlob, + Description: "items with '.flac' suffix", + Pattern: "*.flac", + }, }), - // folders with files not implemented yet - XEntry(nil, &helpers.FilterTE{ + Entry(nil, &helpers.HybridFilterTE{ NaviTE: helpers.NaviTE{ Given: "folder(with files): glob filter (negate)", Relative: "RETRO-WAVE", Subscription: enums.SubscribeFoldersWithFiles, ExpectedNoOf: helpers.Quantities{ - Files: 0, - Folders: 8, + Folders: 2, Children: map[string]int{ - "Night Drive": 3, - "Northern Council": 3, - "Teenage Color": 2, - "Innerworld": 2, + "Night Drive": 3, }, }, }, - Description: "items without '.txt' suffix", - Pattern: "*.txt", - Negate: true, + NodeDef: core.FilterDef{ + Type: enums.FilterTypeGlob, + Description: "folders don't contain o", + Pattern: "*o*", + Scope: enums.ScopeFolder, + Negate: true, + }, + ChildDef: core.ChildFilterDef{ + Type: enums.FilterTypeGlob, + Description: "items without '.txt' suffix", + Pattern: "*.txt", + Negate: true, + }, }), ) }) diff --git a/internal/helpers/assert.go b/internal/helpers/assert.go index 9572b69..1232a28 100644 --- a/internal/helpers/assert.go +++ b/internal/helpers/assert.go @@ -1,7 +1,6 @@ package helpers import ( - "fmt" "io/fs" "path/filepath" "strings" @@ -65,14 +64,11 @@ func AssertNavigation(entry *NaviTE, to *TestOptions) { Expect(every).To(BeTrue()) } - for n, actualNoChildren := range entry.ExpectedNoOf.Children { - expected := to.Recording[n] - Expect(to.Recording[n]).To(Equal(actualNoChildren), - BecauseQuantity(fmt.Sprintf("folder: '%v'", n), - expected, - actualNoChildren, - ), - ) + for name, expected := range entry.ExpectedNoOf.Children { + Expect(to.Recording).To(HaveChildCountOf(ExpectedCount{ + Name: name, + Count: expected, + })) } if entry.Mandatory != nil { diff --git a/internal/helpers/helper-defs.go b/internal/helpers/helper-defs.go index 22c788c..b2ab66d 100644 --- a/internal/helpers/helper-defs.go +++ b/internal/helpers/helper-defs.go @@ -35,6 +35,12 @@ type ( Sample core.SampleTraverseFilter } + HybridFilterTE struct { + NaviTE + NodeDef core.FilterDef + ChildDef core.ChildFilterDef + } + PolyTE struct { NaviTE File core.FilterDef diff --git a/internal/helpers/matchers.go b/internal/helpers/matchers.go index ff0f79b..f012bf3 100644 --- a/internal/helpers/matchers.go +++ b/internal/helpers/matchers.go @@ -171,3 +171,67 @@ func (m *NotInvokeNodeMatcher) NegatedFailureMessage(_ interface{}) string { mandatory, ) } + +type ExpectedCount struct { + Name string + Count int +} + +type ChildCountMatcher struct { + expected interface{} +} + +func HaveChildCountOf(expected interface{}) GomegaMatcher { + return &ChildCountMatcher{ + expected: expected, + } +} + +func (m *ChildCountMatcher) Match(actual interface{}) (bool, error) { + recording, ok := actual.(RecordingMap) + if !ok { + return false, fmt.Errorf("matcher expected actual to be a RecordingMap (%T)", actual) + } + + expected, ok := m.expected.(ExpectedCount) + if !ok { + return false, fmt.Errorf("matcher expected ExpectedCount (%T)", actual) + } + + count, ok := recording[expected.Name] + if !ok { + return false, fmt.Errorf("๐Ÿ”ฅ not found: '%v'", expected.Name) + } + + if count != expected.Count { + return false, fmt.Errorf( + "โŒ incorrect child count for: '%v', actual: '%v', expected: '%v'", + expected.Name, + count, expected.Count, + ) + } + + return true, nil +} + +func (m *ChildCountMatcher) FailureMessage(_ interface{}) string { + expected, ok := m.expected.(ExpectedCount) + if !ok { + return fmt.Sprintf("๐Ÿ”ฅ matcher expected ExpectedCount (%T)", m.expected) + } + + return fmt.Sprintf("โŒ Expected\n\t%v\nnode to be invoked\n", + expected, + ) +} + +func (m *ChildCountMatcher) NegatedFailureMessage(_ interface{}) string { + expected, ok := m.expected.(ExpectedCount) + if !ok { + return fmt.Sprintf("๐Ÿ”ฅ matcher expected ExpectedCount (%T)", m.expected) + } + + return fmt.Sprintf("โŒ Expected\n\t%v\nnode NOT to be invoked\n", + expected, + ) +} diff --git a/internal/kernel/base-plugin.go b/internal/kernel/base-plugin.go index a2c6eb4..9402445 100644 --- a/internal/kernel/base-plugin.go +++ b/internal/kernel/base-plugin.go @@ -16,3 +16,9 @@ type BasePlugin struct { func (p *BasePlugin) Role() enums.Role { return p.ActivatedRole } + +func (p *BasePlugin) Register(kc types.KernelController) error { + p.Kontroller = kc + + return nil +} diff --git a/internal/kernel/guardian.go b/internal/kernel/guardian.go index acafeea..0a5d61f 100644 --- a/internal/kernel/guardian.go +++ b/internal/kernel/guardian.go @@ -5,7 +5,6 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/measure" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/third/lo" "github.com/snivilised/traverse/internal/types" ) @@ -18,22 +17,23 @@ type ( // anchor is a specialised link that should always be the // last in the chain and contains the original client's handler. type anchor struct { - client core.Client - crate measure.Crate + subscription enums.Subscription + client core.Client + crate measure.Crate } -func (t *anchor) Next(node *core.Node, _ override.Inspection) (bool, error) { +func (a *anchor) Next(node *core.Node, _ types.Inspection) (bool, error) { if metric := lo.Ternary(node.IsFolder(), - t.crate.Mums[enums.MetricNoFoldersInvoked], - t.crate.Mums[enums.MetricNoFilesInvoked], + a.crate.Mums[enums.MetricNoFoldersInvoked], + a.crate.Mums[enums.MetricNoFilesInvoked], ); metric != nil { metric.Tick() } - return false, t.client(node) + return false, a.client(node) } -func (t *anchor) Role() enums.Role { +func (a *anchor) Role() enums.Role { return enums.RoleAnchor } @@ -50,23 +50,26 @@ type guardian struct { anchor *anchor } -func newGuardian(client core.Client, - master types.GuardianSealer, - mums measure.MutableMetrics, -) *guardian { - anchor := &anchor{ - client: client, - crate: measure.Crate{ - Mums: mums, - }, - } +type guardianInfo struct { + subscription enums.Subscription + client core.Client + master types.GuardianSealer + mums measure.MutableMetrics +} +func newGuardian(info *guardianInfo) *guardian { return &guardian{ container: iterationContainer{ chain: make(invocationChain), }, - master: master, - anchor: anchor, + master: info.master, + anchor: &anchor{ + subscription: info.subscription, + client: info.client, + crate: measure.Crate{ + Mums: info.mums, + }, + }, } } @@ -74,7 +77,7 @@ func (g *guardian) arrange(active, order []enums.Role) { g.container.chain[enums.RoleAnchor] = g.anchor if len(active) == 0 { - g.container.invoker = NodeInvoker(func(node *core.Node, inspection override.Inspection) error { + g.container.invoker = NodeInvoker(func(node *core.Node, inspection types.Inspection) error { _, err := g.anchor.Next(node, inspection) return err }) @@ -83,7 +86,7 @@ func (g *guardian) arrange(active, order []enums.Role) { } g.container.positions = collections.NewPositionalSet(order, enums.RoleAnchor) - g.container.invoker = NodeInvoker(func(node *core.Node, inspection override.Inspection) error { + g.container.invoker = NodeInvoker(func(node *core.Node, inspection types.Inspection) error { return g.iterate(node, inspection) }) } @@ -122,11 +125,11 @@ func (g *guardian) Unwind(role enums.Role) error { // Invoke executes the chain which may or may not end up resulting in // the invocation of the client's callback, depending on the contents // of the chain. -func (g *guardian) Invoke(node *core.Node, inspection override.Inspection) error { +func (g *guardian) Invoke(node *core.Node, inspection types.Inspection) error { return g.container.invoker.Invoke(node, inspection) } -func (g *guardian) iterate(node *core.Node, inspection override.Inspection) error { +func (g *guardian) iterate(node *core.Node, inspection types.Inspection) error { for _, role := range g.container.positions.Items() { link := g.container.chain[role] diff --git a/internal/kernel/kernel-defs.go b/internal/kernel/kernel-defs.go index dc020d9..de15464 100644 --- a/internal/kernel/kernel-defs.go +++ b/internal/kernel/kernel-defs.go @@ -6,7 +6,6 @@ import ( "github.com/snivilised/traverse/core" "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/types" ) @@ -90,7 +89,7 @@ type ( // Invokable Invokable interface { - Invoke(node *core.Node, inspection override.Inspection) error + Invoke(node *core.Node, inspection types.Inspection) error } // Mutant represents the mutable interface to the Guardian @@ -120,7 +119,7 @@ type ( } inspection interface { // after content has been read - override.Inspection + types.Inspection static() *navigationStatic clear() } @@ -186,8 +185,8 @@ func (v *navigationVapour) AssignChildren(children []fs.DirEntry) { v.present.Children = children } -type NodeInvoker func(node *core.Node, inspection override.Inspection) error +type NodeInvoker func(node *core.Node, inspection types.Inspection) error -func (fn NodeInvoker) Invoke(node *core.Node, inspection override.Inspection) error { +func (fn NodeInvoker) Invoke(node *core.Node, inspection types.Inspection) error { return fn(node, inspection) } diff --git a/internal/kernel/mediator.go b/internal/kernel/mediator.go index 2edc97a..58510b2 100644 --- a/internal/kernel/mediator.go +++ b/internal/kernel/mediator.go @@ -8,7 +8,6 @@ import ( "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/level" "github.com/snivilised/traverse/internal/measure" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/internal/types" "github.com/snivilised/traverse/pref" ) @@ -45,11 +44,16 @@ func newMediator(using *pref.Using, subscription: using.Subscription, using: using, impl: impl, - guardian: newGuardian(using.Handler, sealer, mums), - periscope: level.New(), - o: o, - resources: resources, - mums: mums, + guardian: newGuardian(&guardianInfo{ + subscription: using.Subscription, + client: using.Handler, + master: sealer, + mums: mums, + }), + periscope: level.New(), + o: o, + resources: resources, + mums: mums, } } @@ -112,7 +116,7 @@ func (m *mediator) Spawn(ctx context.Context, root string) (core.TraverseResult, }) } -func (m *mediator) Invoke(node *core.Node, inspection override.Inspection) error { +func (m *mediator) Invoke(node *core.Node, inspection types.Inspection) error { return m.guardian.Invoke(node, inspection) } diff --git a/internal/kernel/navigator-folders.go b/internal/kernel/navigator-folders.go index 89ed9b5..1135616 100644 --- a/internal/kernel/navigator-folders.go +++ b/internal/kernel/navigator-folders.go @@ -71,12 +71,6 @@ func (n *navigatorFolders) inspect(ns *navigationStatic, vapour.Sort(enums.EntryTypeFolder) vapour.Pick(enums.EntryTypeFolder) - if n.using.Subscription == enums.SubscribeFoldersWithFiles { - ns.mediator.resources.Actions.HandleChildren.Invoke()( - vapour, ns.mediator.mums, - ) - } - extend(ns, vapour) return vapour, err diff --git a/internal/override/actions.go b/internal/override/actions.go deleted file mode 100644 index efebb0d..0000000 --- a/internal/override/actions.go +++ /dev/null @@ -1,73 +0,0 @@ -package override - -import ( - "io/fs" - - "github.com/snivilised/traverse/core" - "github.com/snivilised/traverse/enums" - "github.com/snivilised/traverse/internal/measure" - "github.com/snivilised/traverse/tapable" -) - -// ๐Ÿ“ฆ pkg: override - provides a similar function to tapable except we -// use the name action to replace hook. The difference between the -// two are that hooks allow for the client to customise core internal -// behaviour, where as an action allows for internal behaviour to -// be customised by internal entities. One might wonder why this isn't -// implemented inside types as that package is for internal affairs only, -// but types does provide any functionality and types has dependencies -// that we should avoid in override; that is to say we need to avoid -// circular dependencies;... - -type ( - - // Inspection - Inspection interface { - Current() *core.Node - Contents() core.DirectoryContents - Entries() []fs.DirEntry - Sort(et enums.EntryType) []fs.DirEntry - Pick(et enums.EntryType) - AssignChildren(children []fs.DirEntry) - } - - Action[F any] interface { - tapable.Invokable[F] - // Intercept overrides the default tap-able core function - Intercept(handler F) - } - - Actions struct { - HandleChildren Action[HandleChildrenInterceptor] - } - - // ActionCtrl contains the handler function to be invoked. The control - // is agnostic to the handler's signature and therefore can not invoke it. - ActionCtrl[F any] struct { - handler F - def F - } - - // HandleChildrenInterceptor used for folders with files subscription - // type. - HandleChildrenInterceptor func( - inspection Inspection, - mums measure.MutableMetrics, - ) -) - -func NewActionCtrl[F any](handler F) *ActionCtrl[F] { - return &ActionCtrl[F]{ - handler: handler, - } -} - -// add life-cycle style broadcast - -func (c *ActionCtrl[F]) Intercept(handler F) { - c.handler = handler -} - -func (c *ActionCtrl[F]) Invoke() F { - return c.handler -} diff --git a/internal/types/definitions.go b/internal/types/definitions.go index 4d89751..45bda35 100644 --- a/internal/types/definitions.go +++ b/internal/types/definitions.go @@ -8,7 +8,6 @@ import ( "github.com/snivilised/traverse/cycle" "github.com/snivilised/traverse/enums" "github.com/snivilised/traverse/internal/measure" - "github.com/snivilised/traverse/internal/override" "github.com/snivilised/traverse/nfs" "github.com/snivilised/traverse/pref" ) @@ -21,7 +20,7 @@ type ( // Next invokes this decorator which returns true if // next link in the chain can be run or false to stop // execution of subsequent links. - Next(node *core.Node, inspection override.Inspection) (bool, error) + Next(node *core.Node, inspection Inspection) (bool, error) // Role indicates the identity of the link Role() enums.Role @@ -50,7 +49,6 @@ type ( // PluginInit PluginInit struct { - Actions *override.Actions O *pref.Options Controls *cycle.Controls } @@ -70,7 +68,6 @@ type ( Resources struct { FS nfs.FileSystems Supervisor *measure.Supervisor - Actions *override.Actions } // Plugin used to define interaction with supplementary features @@ -106,6 +103,16 @@ type ( Mediator() Mediator Conclude(result core.TraverseResult) } + + // Inspection + Inspection interface { + Current() *core.Node + Contents() core.DirectoryContents + Entries() []fs.DirEntry + Sort(et enums.EntryType) []fs.DirEntry + Pick(et enums.EntryType) + AssignChildren(children []fs.DirEntry) + } ) // KernelResult is the internal representation of core.TraverseResult diff --git a/manifest.go b/manifest.go index 87053aa..57a1df4 100644 --- a/manifest.go +++ b/manifest.go @@ -15,8 +15,10 @@ type ( var ( manifestOrder = []enums.Role{ enums.RoleHibernate, + enums.RoleNanny, enums.RoleClientFilter, enums.RoleSampler, + // anchor goes at this end } ) @@ -36,10 +38,18 @@ func manifest(active []enums.Role) []enums.Role { } return true }, + "nanny-defers-to-filter": func(current enums.Role, active, _ []enums.Role) bool { + if current == enums.RoleNanny && + slices.Contains(active, enums.RoleClientFilter) { + return false + } + return true + }, } // only roles that satisfy all rules are returned // + initial := make([]enums.Role, 0, len(active)+1) return lo.Reduce(active, func(acc []enums.Role, role enums.Role, _ int) []enums.Role { if lo.EveryBy(lo.Keys(rules), func(name string) bool { @@ -49,6 +59,6 @@ func manifest(active []enums.Role) []enums.Role { } return acc }, - make([]enums.Role, 0, len(active)+1), + initial, ) }