package carapace
import (
"fmt"
"os"
"regexp"
"runtime"
"strings"
"time"
shlex "github.com/rsteube/carapace-shlex"
"github.com/rsteube/carapace/internal/cache"
"github.com/rsteube/carapace/internal/common"
pkgcache "github.com/rsteube/carapace/pkg/cache"
"github.com/rsteube/carapace/pkg/match"
"github.com/rsteube/carapace/pkg/style"
pkgtraverse "github.com/rsteube/carapace/pkg/traverse"
)
type Action struct {
meta common .Meta
rawValues common .RawValues
callback CompletionCallback
}
type ActionMap map [string ]Action
type CompletionCallback func (c Context ) Action
func (a Action ) Cache (timeout time .Duration , keys ...pkgcache .Key ) Action {
if a .callback != nil {
cachedCallback := a .callback
_ , file , line , _ := runtime .Caller (1 )
a .callback = func (c Context ) Action {
cacheFile , err := cache .File (file , line , keys ...)
if err != nil {
return cachedCallback (c )
}
if cached , err := cache .Load (cacheFile , timeout ); err == nil {
return Action {meta : cached .Meta , rawValues : cached .Values }
}
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 ()
}
}
return a
}
func (a Action ) Chdir (dir string ) Action {
return ActionCallback (func (c Context ) Action {
abs , err := c .Abs (dir )
if err != nil {
return ActionMessage (err .Error())
}
if info , err := os .Stat (abs ); err != nil {
return ActionMessage (err .Error())
} else if !info .IsDir () {
return ActionMessage ("not a directory: %v" , abs )
}
c .Dir = abs
return a .Invoke (c ).ToA ()
})
}
func (a Action ) ChdirF (f func (tc pkgtraverse .Context ) (string , error )) Action {
return ActionCallback (func (c Context ) Action {
newDir , err := f (c )
if err != nil {
return ActionMessage (err .Error())
}
return a .Chdir (newDir )
})
}
func (a Action ) Filter (values ...string ) Action {
return ActionCallback (func (c Context ) Action {
return a .Invoke (c ).Filter (values ...).ToA ()
})
}
func (a Action ) FilterArgs () Action {
return ActionCallback (func (c Context ) Action {
return a .Filter (c .Args ...)
})
}
func (a Action ) FilterParts () Action {
return ActionCallback (func (c Context ) Action {
return a .Filter (c .Parts ...)
})
}
func (a Action ) Invoke (c Context ) InvokedAction {
if c .Args == nil {
c .Args = []string {}
}
if c .Env == nil {
c .Env = []string {}
}
if c .Parts == nil {
c .Parts = []string {}
}
if a .rawValues == nil && a .callback != nil {
result := a .callback (c ).Invoke (c )
result .meta .Merge (a .meta )
return result
}
return InvokedAction {a }
}
func (a Action ) List (divider string ) Action {
return ActionMultiParts (divider , func (c Context ) Action {
return a .Invoke (c ).ToA ().NoSpace ()
})
}
func (a Action ) MultiParts (dividers ...string ) Action {
return ActionCallback (func (c Context ) Action {
return a .Invoke (c ).ToMultiPartsA (dividers ...)
})
}
func (a Action ) MultiPartsP (delimiter string , pattern string , f func (placeholder string , matches map [string ]string ) Action ) Action {
return ActionCallback (func (c Context ) Action {
invoked := a .Invoke (c )
return ActionMultiParts (delimiter , func (c Context ) Action {
rPlaceholder := regexp .MustCompile (pattern )
matchedData := make (map [string ]string )
matchedSegments := make (map [string ]common .RawValue )
staticMatches := make (map [int ]bool )
path :
for index , value := range invoked .rawValues {
segments := strings .Split (value .Value , delimiter )
segment :
for index , segment := range segments {
if index > len (c .Parts )-1 {
break segment
} else {
if segment != c .Parts [index ] {
if !rPlaceholder .MatchString (segment ) {
continue path
} else {
matchedData [segment ] = c .Parts [index ]
}
} else {
staticMatches [index ] = true
}
}
}
if len (segments ) < len (c .Parts )+1 {
continue path
}
for key := range staticMatches {
if segments [key ] != c .Parts [key ] {
continue path
}
}
if len (segments ) == (len (c .Parts ) + 1 ) {
matchedSegments [segments [len (c .Parts )]] = invoked .rawValues [index ]
} else {
matchedSegments [segments [len (c .Parts )]+delimiter ] = common .RawValue {}
}
}
actions := make ([]Action , 0 , len (matchedSegments ))
for key , value := range matchedSegments {
if trimmedKey := strings .TrimSuffix (key , delimiter ); rPlaceholder .MatchString (trimmedKey ) {
suffix := ""
if strings .HasSuffix (key , delimiter ) {
suffix = delimiter
}
actions = append (actions , ActionCallback (func (c Context ) Action {
invoked := f (trimmedKey , matchedData ).Invoke (c ).Suffix (suffix )
for index := range invoked .rawValues {
invoked .rawValues [index ].Display += suffix
}
return invoked .ToA ()
}))
} else {
actions = append (actions , ActionStyledValuesDescribed (key , value .Description , value .Style ))
}
}
a := Batch (actions ...).ToA ()
a .meta .Merge (invoked .meta )
return a
})
})
}
func (a Action ) NoSpace (suffixes ...rune ) Action {
return ActionCallback (func (c Context ) Action {
if len (suffixes ) == 0 {
a .meta .Nospace .Add ('*' )
}
a .meta .Nospace .Add (suffixes ...)
return a
})
}
func (a Action ) Prefix (prefix string ) Action {
return ActionCallback (func (c Context ) Action {
switch {
case match .HasPrefix (c .Value , prefix ):
c .Value = match .TrimPrefix (c .Value , prefix )
case match .HasPrefix (prefix , c .Value ):
c .Value = ""
default :
return ActionValues ()
}
return a .Invoke (c ).Prefix (prefix ).ToA ()
})
}
func (a Action ) Retain (values ...string ) Action {
return ActionCallback (func (c Context ) Action {
return a .Invoke (c ).Retain (values ...).ToA ()
})
}
func (a Action ) Shift (n int ) Action {
return ActionCallback (func (c Context ) Action {
switch {
case n < 0 :
return ActionMessage ("invalid argument [ActionShift]: %v" , n )
case len (c .Args ) < n :
c .Args = []string {}
default :
c .Args = c .Args [n :]
}
return a .Invoke (c ).ToA ()
})
}
func (a Action ) Split () Action {
return a .split (false )
}
func (a Action ) SplitP () Action {
return a .split (true )
}
func (a Action ) split (pipelines bool ) Action {
return ActionCallback (func (c Context ) Action {
tokens , err := shlex .Split (c .Value )
if err != nil {
return ActionMessage (err .Error())
}
var context Context
if pipelines {
tokens = tokens .CurrentPipeline ()
context = NewContext (tokens .FilterRedirects ().Words ().Strings ()...)
} else {
context = NewContext (tokens .Words ().Strings ()...)
}
originalValue := c .Value
prefix := originalValue [:tokens .Words ().CurrentToken ().Index ]
c .Args = context .Args
c .Parts = []string {}
c .Value = context .Value
if pipelines {
if len (tokens ) > 1 && tokens [len (tokens )-2 ].WordbreakType .IsRedirect () {
LOG .Printf ("completing files for redirect arg %#v" , tokens .Words ().CurrentToken ().Value )
prefix = originalValue [:tokens .CurrentToken ().Index ]
c .Value = tokens .CurrentToken ().Value
a = ActionFiles ()
}
}
invoked := a .Invoke (c )
for index , value := range invoked .rawValues {
if !invoked .meta .Nospace .Matches (value .Value ) || strings .Contains (value .Value , " " ) {
switch tokens .CurrentToken ().State {
case shlex .QUOTING_ESCAPING_STATE :
invoked .rawValues [index ].Value = fmt .Sprintf (`"%v"` , strings .ReplaceAll (value .Value , `"` , `\"` ))
case shlex .QUOTING_STATE :
invoked .rawValues [index ].Value = fmt .Sprintf (`'%v'` , strings .ReplaceAll (value .Value , `'` , `'"'"'` ))
default :
invoked .rawValues [index ].Value = strings .Replace (value .Value , ` ` , `\ ` , -1 )
}
}
if !invoked .meta .Nospace .Matches (value .Value ) {
invoked .rawValues [index ].Value += " "
}
}
return invoked .Prefix (prefix ).ToA ().NoSpace ()
})
}
func (a Action ) Style (s string ) Action {
return a .StyleF (func (_ string , _ style .Context ) string {
return s
})
}
func (a Action ) StyleF (f func (s string , sc style .Context ) string ) Action {
return ActionCallback (func (c Context ) Action {
invoked := a .Invoke (c )
for index , v := range invoked .rawValues {
invoked .rawValues [index ].Style = f (v .Value , c )
}
return invoked .ToA ()
})
}
func (a Action ) StyleR (s *string ) Action {
return ActionCallback (func (c Context ) Action {
if s != nil {
return a .Style (*s )
}
return a
})
}
func (a Action ) Suffix (suffix string ) Action {
return ActionCallback (func (c Context ) Action {
return a .Invoke (c ).Suffix (suffix ).ToA ()
})
}
func (a Action ) Suppress (expr ...string ) Action {
return ActionCallback (func (c Context ) Action {
invoked := a .Invoke (c )
if err := invoked .meta .Messages .Suppress (expr ...); err != nil {
return ActionMessage (err .Error())
}
return invoked .ToA ()
})
}
func (a Action ) Tag (tag string ) Action {
return a .TagF (func (value string ) string {
return tag
})
}
func (a Action ) TagF (f func (s string ) string ) Action {
return ActionCallback (func (c Context ) Action {
invoked := a .Invoke (c )
for index , v := range invoked .rawValues {
invoked .rawValues [index ].Tag = f (v .Value )
}
return invoked .ToA ()
})
}
func (a Action ) Timeout (d time .Duration , alternative Action ) Action {
return ActionCallback (func (c Context ) Action {
currentChannel := make (chan string , 1 )
var result InvokedAction
go func () {
result = a .Invoke (c )
currentChannel <- ""
}()
select {
case <- currentChannel :
case <- time .After (d ):
return alternative
}
return result .ToA ()
})
}
func (a Action ) UniqueList (divider string ) Action {
return ActionMultiParts (divider , func (c Context ) Action {
return a .FilterParts ().NoSpace ()
})
}
func (a Action ) UniqueListF (divider string , f func (s string ) string ) Action {
return ActionMultiParts (divider , func (c Context ) Action {
for i := range c .Parts {
c .Parts [i ] = f (c .Parts [i ])
}
return a .Filter (c .Parts ...).NoSpace ()
})
}
func (a Action ) Usage (usage string , args ...interface {}) Action {
return a .UsageF (func () string {
return fmt .Sprintf (usage , args ...)
})
}
func (a Action ) UsageF (f func () string ) Action {
return ActionCallback (func (c Context ) Action {
if usage := f (); usage != "" {
a .meta .Usage = usage
}
return a
})
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .