github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/glob.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  	"unicode"
     9  
    10  	"github.com/markusbkk/elvish/pkg/eval/vals"
    11  	"github.com/markusbkk/elvish/pkg/glob"
    12  	"github.com/markusbkk/elvish/pkg/parse"
    13  )
    14  
    15  // An ephemeral value generated when evaluating tilde and wildcards.
    16  type globPattern struct {
    17  	glob.Pattern
    18  	Flags  globFlag
    19  	Buts   []string
    20  	TypeCb func(os.FileMode) bool
    21  }
    22  
    23  type globFlag uint
    24  
    25  var typeCbMap = map[string]func(os.FileMode) bool{
    26  	"dir":     os.FileMode.IsDir,
    27  	"regular": os.FileMode.IsRegular,
    28  }
    29  
    30  const (
    31  	// noMatchOK indicates that the "nomatch-ok" glob index modifier was
    32  	// present.
    33  	noMatchOK globFlag = 1 << iota
    34  )
    35  
    36  func (f globFlag) Has(g globFlag) bool {
    37  	return (f & g) == g
    38  }
    39  
    40  var _ vals.ErrIndexer = globPattern{}
    41  
    42  var (
    43  	ErrMustFollowWildcard    = errors.New("must follow wildcard")
    44  	ErrModifierMustBeString  = errors.New("modifier must be string")
    45  	ErrWildcardNoMatch       = errors.New("wildcard has no match")
    46  	ErrMultipleTypeModifiers = errors.New("only one type modifier allowed")
    47  	ErrUnknownTypeModifier   = errors.New("unknown type modifier")
    48  )
    49  
    50  var runeMatchers = map[string]func(rune) bool{
    51  	"control": unicode.IsControl,
    52  	"digit":   unicode.IsDigit,
    53  	"graphic": unicode.IsGraphic,
    54  	"letter":  unicode.IsLetter,
    55  	"lower":   unicode.IsDigit,
    56  	"mark":    unicode.IsMark,
    57  	"number":  unicode.IsNumber,
    58  	"print":   unicode.IsPrint,
    59  	"punct":   unicode.IsPunct,
    60  	"space":   unicode.IsSpace,
    61  	"symbol":  unicode.IsSymbol,
    62  	"title":   unicode.IsTitle,
    63  	"upper":   unicode.IsUpper,
    64  }
    65  
    66  func (gp globPattern) Kind() string { return "glob-pattern" }
    67  
    68  func (gp globPattern) Index(k interface{}) (interface{}, error) {
    69  	modifierv, ok := k.(string)
    70  	if !ok {
    71  		return nil, ErrModifierMustBeString
    72  	}
    73  	modifier := modifierv
    74  	switch {
    75  	case modifier == "nomatch-ok":
    76  		gp.Flags |= noMatchOK
    77  	case strings.HasPrefix(modifier, "but:"):
    78  		gp.Buts = append(gp.Buts, modifier[len("but:"):])
    79  	case modifier == "match-hidden":
    80  		lastSeg, err := gp.lastWildSeg()
    81  		if err != nil {
    82  			return nil, err
    83  		}
    84  		gp.Segments[len(gp.Segments)-1] = glob.Wild{
    85  			Type: lastSeg.Type, MatchHidden: true, Matchers: lastSeg.Matchers,
    86  		}
    87  	case strings.HasPrefix(modifier, "type:"):
    88  		if gp.TypeCb != nil {
    89  			return nil, ErrMultipleTypeModifiers
    90  		}
    91  		typeName := modifier[len("type:"):]
    92  		cb, ok := typeCbMap[typeName]
    93  		if !ok {
    94  			return nil, ErrUnknownTypeModifier
    95  		}
    96  		gp.TypeCb = cb
    97  	default:
    98  		var matcher func(rune) bool
    99  		if m, ok := runeMatchers[modifier]; ok {
   100  			matcher = m
   101  		} else if strings.HasPrefix(modifier, "set:") {
   102  			set := modifier[len("set:"):]
   103  			matcher = func(r rune) bool {
   104  				return strings.ContainsRune(set, r)
   105  			}
   106  		} else if strings.HasPrefix(modifier, "range:") {
   107  			rangeExpr := modifier[len("range:"):]
   108  			badRangeExpr := fmt.Errorf("bad range modifier: %s", parse.Quote(rangeExpr))
   109  			runes := []rune(rangeExpr)
   110  			if len(runes) != 3 {
   111  				return nil, badRangeExpr
   112  			}
   113  			from, sep, to := runes[0], runes[1], runes[2]
   114  			switch sep {
   115  			case '-':
   116  				matcher = func(r rune) bool {
   117  					return from <= r && r <= to
   118  				}
   119  			case '~':
   120  				matcher = func(r rune) bool {
   121  					return from <= r && r < to
   122  				}
   123  			default:
   124  				return nil, badRangeExpr
   125  			}
   126  		} else {
   127  			return nil, fmt.Errorf("unknown modifier %s", vals.ReprPlain(modifierv))
   128  		}
   129  		err := gp.addMatcher(matcher)
   130  		return gp, err
   131  	}
   132  	return gp, nil
   133  }
   134  
   135  func (gp globPattern) Concat(v interface{}) (interface{}, error) {
   136  	switch rhs := v.(type) {
   137  	case string:
   138  		gp.append(stringToSegments(rhs)...)
   139  		return gp, nil
   140  	case globPattern:
   141  		// We know rhs contains exactly one segment.
   142  		gp.append(rhs.Segments[0])
   143  		gp.Flags |= rhs.Flags
   144  		gp.Buts = append(gp.Buts, rhs.Buts...)
   145  		// This handles illegal cases such as `**[type:regular]x*[type:directory]`.
   146  		if gp.TypeCb != nil && rhs.TypeCb != nil {
   147  			return nil, ErrMultipleTypeModifiers
   148  		}
   149  		if rhs.TypeCb != nil {
   150  			gp.TypeCb = rhs.TypeCb
   151  		}
   152  		return gp, nil
   153  	}
   154  
   155  	return nil, vals.ErrConcatNotImplemented
   156  }
   157  
   158  func (gp globPattern) RConcat(v interface{}) (interface{}, error) {
   159  	switch lhs := v.(type) {
   160  	case string:
   161  		segs := stringToSegments(lhs)
   162  		// We know gp contains exactly one segment.
   163  		segs = append(segs, gp.Segments[0])
   164  		return globPattern{Pattern: glob.Pattern{Segments: segs}, Flags: gp.Flags,
   165  			Buts: gp.Buts, TypeCb: gp.TypeCb}, nil
   166  	}
   167  
   168  	return nil, vals.ErrConcatNotImplemented
   169  }
   170  
   171  func (gp *globPattern) lastWildSeg() (glob.Wild, error) {
   172  	if len(gp.Segments) == 0 {
   173  		return glob.Wild{}, ErrBadglobPattern
   174  	}
   175  	if !glob.IsWild(gp.Segments[len(gp.Segments)-1]) {
   176  		return glob.Wild{}, ErrMustFollowWildcard
   177  	}
   178  	return gp.Segments[len(gp.Segments)-1].(glob.Wild), nil
   179  }
   180  
   181  func (gp *globPattern) addMatcher(matcher func(rune) bool) error {
   182  	lastSeg, err := gp.lastWildSeg()
   183  	if err != nil {
   184  		return err
   185  	}
   186  	gp.Segments[len(gp.Segments)-1] = glob.Wild{
   187  		Type: lastSeg.Type, MatchHidden: lastSeg.MatchHidden,
   188  		Matchers: append(lastSeg.Matchers, matcher),
   189  	}
   190  	return nil
   191  }
   192  
   193  func (gp *globPattern) append(segs ...glob.Segment) {
   194  	gp.Segments = append(gp.Segments, segs...)
   195  }
   196  
   197  func wildcardToSegment(s string) (glob.Segment, error) {
   198  	switch s {
   199  	case "*":
   200  		return glob.Wild{Type: glob.Star, MatchHidden: false, Matchers: nil}, nil
   201  	case "**":
   202  		return glob.Wild{Type: glob.StarStar, MatchHidden: false, Matchers: nil}, nil
   203  	case "?":
   204  		return glob.Wild{Type: glob.Question, MatchHidden: false, Matchers: nil}, nil
   205  	default:
   206  		return nil, fmt.Errorf("bad wildcard: %q", s)
   207  	}
   208  }
   209  
   210  func stringToSegments(s string) []glob.Segment {
   211  	segs := []glob.Segment{}
   212  	for i := 0; i < len(s); {
   213  		j := i
   214  		for ; j < len(s) && s[j] != '/'; j++ {
   215  		}
   216  		if j > i {
   217  			segs = append(segs, glob.Literal{Data: s[i:j]})
   218  		}
   219  		if j < len(s) {
   220  			for ; j < len(s) && s[j] == '/'; j++ {
   221  			}
   222  			segs = append(segs, glob.Slash{})
   223  			i = j
   224  		} else {
   225  			break
   226  		}
   227  	}
   228  	return segs
   229  }
   230  
   231  func doGlob(gp globPattern, abort <-chan struct{}) ([]interface{}, error) {
   232  	but := make(map[string]struct{})
   233  	for _, s := range gp.Buts {
   234  		but[s] = struct{}{}
   235  	}
   236  
   237  	vs := make([]interface{}, 0)
   238  	if !gp.Glob(func(pathInfo glob.PathInfo) bool {
   239  		select {
   240  		case <-abort:
   241  			logger.Println("glob aborted")
   242  			return false
   243  		default:
   244  		}
   245  
   246  		if _, ignore := but[pathInfo.Path]; ignore {
   247  			return true
   248  		}
   249  
   250  		if gp.TypeCb == nil || gp.TypeCb(pathInfo.Info.Mode()) {
   251  			vs = append(vs, pathInfo.Path)
   252  		}
   253  		return true
   254  	}) {
   255  		return nil, ErrInterrupted
   256  	}
   257  	if len(vs) == 0 && !gp.Flags.Has(noMatchOK) {
   258  		return nil, ErrWildcardNoMatch
   259  	}
   260  	return vs, nil
   261  }