diff --git a/action.go b/action.go index 294fc694..0c1d9bed 100644 --- a/action.go +++ b/action.go @@ -56,6 +56,47 @@ func (a Action) Cache(timeout time.Duration, keys ...pkgcache.Key) Action { return a } +// CacheF requires a function to determine if we should use the cached data (if any and if it's +// still within timeout), and if asked to refresh it, to cache the proceeds of the invoked action. +func (a Action) CacheF(timeout time.Duration, refresh func() bool, keys ...pkgcache.Key) Action { + if a.callback == nil { + return a + } + + _, file, line, _ := runtime.Caller(1) + cachedCallback := a.callback + + return ActionCallback(func(c Context) Action { + // Know when the last cached values were written (if any) + cacheFile, err := cache.File(file, line, keys...) + if err != nil { + return a + } + + stat, err := os.Stat(cacheFile) + + // No cached results, just ask to cache after call. + cacheIsOutdated := (timeout > 0 && stat.ModTime().Add(timeout).Before(time.Now())) + if os.IsNotExist(err) || cacheIsOutdated { + return a.Cache(timeout, keys...) + } + + // If we are asked to refresh the cache with new results anyway, do it. + if refresh() { + invokedAction := (Action{callback: cachedCallback}).Invoke(c) + if invokedAction.meta.Messages.IsEmpty() { + if cacheFile, err := cache.File(file, line, keys...); err == nil { + _ = cache.Write(cacheFile, invokedAction.export()) + } + } + return invokedAction.ToA() + } + + // Else, once again let's the cache do it's work. + return a.Cache(timeout, keys...) + }) +} + // Chdir changes the current working directory to the named directory for the duration of invocation. func (a Action) Chdir(dir string) Action { return ActionCallback(func(c Context) Action { @@ -93,14 +134,14 @@ func (a Action) Filter(values ...string) Action { }) } -// FilterArgs filters Context.Args +// FilterArgs filters Context.Args. func (a Action) FilterArgs() Action { return ActionCallback(func(c Context) Action { return a.Filter(c.Args...) }) } -// FilterArgs filters Context.Parts +// FilterArgs filters Context.Parts. func (a Action) FilterParts() Action { return ActionCallback(func(c Context) Action { return a.Filter(c.Parts...) @@ -141,7 +182,7 @@ func (a Action) MultiParts(dividers ...string) Action { }) } -// MultiPartsP is like MultiParts but with placeholders +// MultiPartsP is like MultiParts but with placeholders. func (a Action) MultiPartsP(delimiter string, pattern string, f func(placeholder string, matches map[string]string) Action) Action { // TODO add delimiter as suffix for proper nospace handling (some values/placeholders might appear with and without suffix) return ActionCallback(func(c Context) Action { diff --git a/internalActions.go b/internalActions.go index 3a52e660..60b35994 100644 --- a/internalActions.go +++ b/internalActions.go @@ -14,68 +14,25 @@ import ( "github.com/spf13/cobra" ) -func actionPath(fileSuffixes []string, dirOnly bool) Action { +// ActionCommands returns the completed subcommands of a given cobra command. +func ActionCommands(cmd *cobra.Command) Action { return ActionCallback(func(c Context) Action { - if len(c.Value) == 2 && util.HasVolumePrefix(c.Value) { - // TODO should be fixed in Abs or wherever this is happening - return ActionValues(c.Value + "/") // prevent `C:` -> `C:.` - } - - abs, err := c.Abs(c.Value) - if err != nil { - return ActionMessage(err.Error()) - } - - displayFolder := filepath.ToSlash(filepath.Dir(c.Value)) - if displayFolder == "." { - displayFolder = "" - } else if !strings.HasSuffix(displayFolder, "/") { - displayFolder = displayFolder + "/" - } - - actualFolder := filepath.ToSlash(filepath.Dir(abs)) - files, err := ioutil.ReadDir(actualFolder) - if err != nil { - return ActionMessage(err.Error()) - } - - showHidden := !strings.HasSuffix(abs, "/") && strings.HasPrefix(filepath.Base(abs), ".") - - vals := make([]string, 0, len(files)*2) - for _, file := range files { - if !showHidden && strings.HasPrefix(file.Name(), ".") { - continue - } - - resolvedFile := file - if resolved, err := filepath.EvalSymlinks(actualFolder + file.Name()); err == nil { - if stat, err := os.Stat(resolved); err == nil { - resolvedFile = stat - } - } - - if resolvedFile.IsDir() { - vals = append(vals, displayFolder+file.Name()+"/", style.ForPath(filepath.Clean(actualFolder+"/"+file.Name()+"/"), c)) - } else if !dirOnly { - if len(fileSuffixes) == 0 { - fileSuffixes = []string{""} - } - for _, suffix := range fileSuffixes { - if strings.HasSuffix(file.Name(), suffix) { - vals = append(vals, displayFolder+file.Name(), style.ForPath(filepath.Clean(actualFolder+"/"+file.Name()), c)) - break - } + batch := Batch() + for _, subcommand := range cmd.Commands() { + if (!subcommand.Hidden || env.Hidden()) && subcommand.Deprecated == "" { + group := common.Group{Cmd: subcommand} + batch = append(batch, ActionStyledValuesDescribed(subcommand.Name(), subcommand.Short, group.Style()).Tag(group.Tag())) + for _, alias := range subcommand.Aliases { + batch = append(batch, ActionStyledValuesDescribed(alias, subcommand.Short, group.Style()).Tag(group.Tag())) } } } - if strings.HasPrefix(c.Value, "./") { - return ActionStyledValues(vals...).Invoke(Context{}).Prefix("./").ToA() - } - return ActionStyledValues(vals...) + return batch.ToA() }) } -func actionFlags(cmd *cobra.Command) Action { +// ActionFlags returns the completed flags of a given cobra command. +func ActionFlags(cmd *cobra.Command) Action { return ActionCallback(func(c Context) Action { cmd.InitDefaultHelpFlag() cmd.InitDefaultVersionFlag() @@ -133,19 +90,64 @@ func actionFlags(cmd *cobra.Command) Action { }).Tag("flags") } -func actionSubcommands(cmd *cobra.Command) Action { +func actionPath(fileSuffixes []string, dirOnly bool) Action { return ActionCallback(func(c Context) Action { - batch := Batch() - for _, subcommand := range cmd.Commands() { - if (!subcommand.Hidden || env.Hidden()) && subcommand.Deprecated == "" { - group := common.Group{Cmd: subcommand} - batch = append(batch, ActionStyledValuesDescribed(subcommand.Name(), subcommand.Short, group.Style()).Tag(group.Tag())) - for _, alias := range subcommand.Aliases { - batch = append(batch, ActionStyledValuesDescribed(alias, subcommand.Short, group.Style()).Tag(group.Tag())) + if len(c.Value) == 2 && util.HasVolumePrefix(c.Value) { + // TODO should be fixed in Abs or wherever this is happening + return ActionValues(c.Value + "/") // prevent `C:` -> `C:.` + } + + abs, err := c.Abs(c.Value) + if err != nil { + return ActionMessage(err.Error()) + } + + displayFolder := filepath.ToSlash(filepath.Dir(c.Value)) + if displayFolder == "." { + displayFolder = "" + } else if !strings.HasSuffix(displayFolder, "/") { + displayFolder = displayFolder + "/" + } + + actualFolder := filepath.ToSlash(filepath.Dir(abs)) + files, err := ioutil.ReadDir(actualFolder) + if err != nil { + return ActionMessage(err.Error()) + } + + showHidden := !strings.HasSuffix(abs, "/") && strings.HasPrefix(filepath.Base(abs), ".") + + vals := make([]string, 0, len(files)*2) + for _, file := range files { + if !showHidden && strings.HasPrefix(file.Name(), ".") { + continue + } + + resolvedFile := file + if resolved, err := filepath.EvalSymlinks(actualFolder + file.Name()); err == nil { + if stat, err := os.Stat(resolved); err == nil { + resolvedFile = stat + } + } + + if resolvedFile.IsDir() { + vals = append(vals, displayFolder+file.Name()+"/", style.ForPath(filepath.Clean(actualFolder+"/"+file.Name()+"/"), c)) + } else if !dirOnly { + if len(fileSuffixes) == 0 { + fileSuffixes = []string{""} + } + for _, suffix := range fileSuffixes { + if strings.HasSuffix(file.Name(), suffix) { + vals = append(vals, displayFolder+file.Name(), style.ForPath(filepath.Clean(actualFolder+"/"+file.Name()), c)) + break + } } } } - return batch.ToA() + if strings.HasPrefix(c.Value, "./") { + return ActionStyledValues(vals...).Invoke(Context{}).Prefix("./").ToA() + } + return ActionStyledValues(vals...) }) } @@ -167,7 +169,7 @@ func initHelpCompletion(cmd *cobra.Command) { if err != nil { return ActionMessage(err.Error()) } - return actionSubcommands(lastCmd) + return ActionCommands(lastCmd) }), ) } diff --git a/traverse.go b/traverse.go index 900b6384..81e4f987 100644 --- a/traverse.go +++ b/traverse.go @@ -147,14 +147,14 @@ loop: return storage.getFlag(c, f.Name).Prefix(f.Prefix), context } LOG.Printf("completing flags for arg %#v\n", context.Value) - return actionFlags(c), context + return ActionFlags(c), context // positional or subcommand default: LOG.Printf("completing positionals and subcommands for arg %#v\n", context.Value) batch := Batch(storage.getPositional(c, len(context.Args))) if c.HasAvailableSubCommands() && len(context.Args) == 0 { - batch = append(batch, actionSubcommands(c)) + batch = append(batch, ActionCommands(c)) } return batch.ToA(), context }