TBR

v: renaming the v directory to go

Change-Id: I4fd9f6ee2895d8034c23b65927eb118980b3c17a
diff --git a/lib/glob/glob.go b/lib/glob/glob.go
new file mode 100644
index 0000000..b1356c1
--- /dev/null
+++ b/lib/glob/glob.go
@@ -0,0 +1,165 @@
+// glob implements a glob language.
+//
+// Globs match a slash separated series of glob expressions.
+//
+// pattern:
+// term ['/' term]*
+// term:
+// '*'         matches any sequence of non-Separator characters
+// '?'         matches any single non-Separator character
+// '[' [ '^' ] { character-range } ']'
+// character class (must be non-empty)
+// c           matches character c (c != '*', '?', '\\', '[', '/')
+// '\\' c      matches character c
+// character-range:
+// c           matches character c (c != '\\', '-', ']')
+// '\\' c      matches character c
+// lo '-' hi   matches character c for lo <= c <= hi
+
+package glob
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+// Glob represents a slash separated path glob expression.
+type Glob struct {
+	elems     []string
+	recursive bool
+}
+
+// Parse returns a new Glob.
+func Parse(pattern string) (*Glob, error) {
+	if len(pattern) > 0 && pattern[0] == '/' {
+		return nil, filepath.ErrBadPattern
+	}
+
+	g := &Glob{}
+	if pattern != "" {
+		g.elems = strings.Split(pattern, "/")
+	}
+	if last := len(g.elems) - 1; last >= 0 && g.elems[last] == "..." {
+		g.elems = g.elems[:last]
+		g.recursive = true
+	}
+
+	// The only error we can get from the filepath library is badpattern.
+	// A future implementation would most likely recognize that here, so for now
+	// I'll just check every part to make sure it's error free.
+	for _, elem := range g.elems {
+		if _, err := filepath.Match(elem, ""); err != nil {
+			return nil, err
+		}
+	}
+
+	return g, nil
+}
+
+// Len returns the number of path elements represented by the glob expression.
+func (g *Glob) Len() int {
+	return len(g.elems)
+}
+
+// Finished returns true if the pattern cannot match anything.
+func (g *Glob) Finished() bool {
+	return !g.recursive && len(g.elems) == 0
+}
+
+// Split returns the suffix of g starting at the path element corresponding to start.
+func (g *Glob) Split(start int) *Glob {
+	if start >= len(g.elems) {
+		return &Glob{elems: nil, recursive: g.recursive}
+	}
+	return &Glob{elems: g.elems[start:], recursive: g.recursive}
+}
+
+// MatchInitialSegment tries to match segment against the initial element of g.
+// Returns a boolean indicating whether the match was successful and the
+// Glob representing the unmatched remainder of g.
+func (g *Glob) MatchInitialSegment(segment string) (bool, *Glob) {
+	if len(g.elems) == 0 {
+		if !g.recursive {
+			return false, nil
+		}
+		return true, g
+	}
+
+	if matches, err := filepath.Match(g.elems[0], segment); err != nil {
+		panic("Error in glob pattern found.")
+	} else if matches {
+		return true, g.Split(1)
+	}
+	return false, nil
+}
+
+// PartialMatch tries matching elems against part of a glob pattern.
+// The first return value is true if each element e_i of elems matches
+// the (start + i)th element of the glob pattern.  If the first return
+// value is true, the second return value returns the unmatched suffix
+// of the pattern.  It will be empty if the pattern is completely
+// matched.
+//
+// Note that if the glob is recursive elems can have more elements then
+// the glob pattern and still get a true result.
+func (g *Glob) PartialMatch(start int, elems []string) (bool, *Glob) {
+	g = g.Split(start)
+	for ; len(elems) > 0; elems = elems[1:] {
+		var matched bool
+		if matched, g = g.MatchInitialSegment(elems[0]); !matched {
+			return false, nil
+		}
+	}
+	return true, g
+}
+
+// isFixed returns the unescaped string and true if 's' is a pattern specifying
+// a fixed string.  Otherwise it returns the original string and false.
+func isFixed(s string) (string, bool) {
+	// No special characters.
+	if !strings.ContainsAny(s, "*?[") {
+		return s, true
+	}
+	// Special characters and no backslash.
+	if !strings.ContainsAny(s, "\\") {
+		return "", false
+	}
+	unescaped := ""
+	escape := false
+	for _, c := range s {
+		if escape {
+			escape = false
+			unescaped += string(c)
+		} else if strings.ContainsRune("*?[", c) {
+			// S contains an unescaped special character.
+			return s, false
+		} else if c == '\\' {
+			escape = true
+		} else {
+			unescaped += string(c)
+		}
+	}
+	return unescaped, true
+}
+
+func (g *Glob) SplitFixedPrefix() ([]string, *Glob) {
+	var prefix []string
+	start := 0
+	for _, elem := range g.elems {
+		if u, q := isFixed(elem); q {
+			prefix = append(prefix, u)
+			start++
+		} else {
+			break
+		}
+	}
+	return prefix, g.Split(start)
+}
+
+func (g *Glob) String() string {
+	e := g.elems
+	if g.recursive {
+		e = append(e, "...")
+	}
+	return filepath.Join(e...)
+}