2017-04-06 15:31:17 +00:00
|
|
|
package generator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2022-11-21 11:52:56 +00:00
|
|
|
"fmt"
|
2017-04-06 15:31:17 +00:00
|
|
|
"io"
|
2017-06-13 11:56:07 +00:00
|
|
|
"io/ioutil"
|
2022-12-25 10:58:23 +00:00
|
|
|
"log"
|
2022-11-21 11:52:56 +00:00
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
2019-02-14 11:47:45 +00:00
|
|
|
|
|
|
|
"gopkg.in/yaml.v2"
|
2017-04-06 15:31:17 +00:00
|
|
|
)
|
|
|
|
|
2019-02-14 11:47:45 +00:00
|
|
|
// Vendor generates regex matchers in Go for vendoring files/dirs.
|
|
|
|
// It is of generator.File type.
|
2017-06-13 11:56:07 +00:00
|
|
|
func Vendor(fileToParse, samplesDir, outPath, tmplPath, tmplName, commit string) error {
|
|
|
|
data, err := ioutil.ReadFile(fileToParse)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-11-21 11:52:56 +00:00
|
|
|
var regexps []string
|
|
|
|
if err := yaml.Unmarshal(data, ®exps); err != nil {
|
|
|
|
return fmt.Errorf("failed to parse YAML %s, %q", fileToParse, err)
|
2017-04-06 15:31:17 +00:00
|
|
|
}
|
|
|
|
|
2022-12-25 10:58:23 +00:00
|
|
|
for _, re := range regexps {
|
|
|
|
if !isRE2(re) {
|
|
|
|
log.Printf("RE2 incompatible syntax for vendor:'%s'\n", re)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-06 15:31:17 +00:00
|
|
|
buf := &bytes.Buffer{}
|
2022-11-21 11:52:56 +00:00
|
|
|
if err := executeVendorTemplate(buf, regexps, tmplPath, tmplName, commit); err != nil {
|
|
|
|
return err
|
2017-04-06 15:31:17 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 11:56:07 +00:00
|
|
|
return formatedWrite(outPath, buf.Bytes())
|
2017-04-06 15:31:17 +00:00
|
|
|
}
|
|
|
|
|
2022-11-21 11:52:56 +00:00
|
|
|
func executeVendorTemplate(out io.Writer, regexps []string, tmplPath, tmplName, commit string) error {
|
|
|
|
funcs := template.FuncMap{"optimize": collateAllMatchers}
|
|
|
|
return executeTemplate(out, tmplName, tmplPath, commit, funcs, regexps)
|
|
|
|
}
|
|
|
|
|
|
|
|
func collateAllMatchers(regexps []string) string {
|
|
|
|
// We now collate all regexps from VendorMatchers to a single large regexp
|
|
|
|
// which is at least twice as fast to test than simply iterating & matching.
|
|
|
|
//
|
|
|
|
// ---
|
|
|
|
//
|
|
|
|
// We could test each matcher from VendorMatchers in turn i.e.
|
|
|
|
//
|
|
|
|
// func IsVendor(filename string) bool {
|
|
|
|
// for _, matcher := range data.VendorMatchers {
|
|
|
|
// if matcher.MatchString(filename) {
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return false
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// Or naïvely concatentate all these regexps using groups i.e.
|
|
|
|
//
|
|
|
|
// `(regexp1)|(regexp2)|(regexp3)|...`
|
|
|
|
//
|
|
|
|
// However, both of these are relatively slow and don't take advantage
|
|
|
|
// of the inherent structure within our regexps.
|
|
|
|
//
|
|
|
|
// Imperical observation: by looking at the regexps, we only have 3 types.
|
|
|
|
// 1. Those that start with `^`
|
|
|
|
// 2. Those that start with `(^|/)`
|
|
|
|
// 3. All the rest
|
|
|
|
//
|
|
|
|
// If we collate our regexps into these 3 groups - that will significantly
|
|
|
|
// reduce the likelihood of backtracking within the regexp trie matcher.
|
|
|
|
//
|
|
|
|
// A further improvement is to use non-capturing groups (?:) as otherwise
|
|
|
|
// the regexp parser, whilst matching, will have to allocate slices for
|
|
|
|
// matching positions. (A future improvement left out could be to
|
|
|
|
// enforce non-capturing groups within the sub-regexps.)
|
|
|
|
const (
|
|
|
|
caret = "^"
|
|
|
|
caretOrSlash = "(^|/)"
|
|
|
|
)
|
|
|
|
|
|
|
|
sort.Strings(regexps)
|
|
|
|
|
|
|
|
var caretPrefixed, caretOrSlashPrefixed, theRest []string
|
|
|
|
// Check prefix, add to the respective group slices
|
|
|
|
for _, re := range regexps {
|
2022-12-25 10:58:23 +00:00
|
|
|
if !isRE2(re) {
|
|
|
|
continue
|
|
|
|
}
|
2022-11-21 11:52:56 +00:00
|
|
|
if strings.HasPrefix(re, caret) {
|
|
|
|
caretPrefixed = append(caretPrefixed, re[len(caret):])
|
|
|
|
} else if strings.HasPrefix(re, caretOrSlash) {
|
|
|
|
caretOrSlashPrefixed = append(caretOrSlashPrefixed, re[len(caretOrSlash):])
|
|
|
|
} else {
|
|
|
|
theRest = append(theRest, re)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var sb strings.Builder
|
|
|
|
appendGroupWithCommonPrefix(&sb, "^", caretPrefixed)
|
|
|
|
sb.WriteString("|")
|
|
|
|
|
|
|
|
appendGroupWithCommonPrefix(&sb, "(?:^|/)", caretOrSlashPrefixed)
|
|
|
|
sb.WriteString("|")
|
|
|
|
|
|
|
|
appendGroupWithCommonPrefix(&sb, "", theRest)
|
|
|
|
return sb.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func appendGroupWithCommonPrefix(sb *strings.Builder, commonPrefix string, res []string) {
|
|
|
|
sb.WriteString("(?:")
|
|
|
|
if commonPrefix != "" {
|
|
|
|
sb.WriteString(fmt.Sprintf("%s(?:(?:", commonPrefix))
|
|
|
|
}
|
|
|
|
sb.WriteString(strings.Join(res, ")|(?:"))
|
|
|
|
if commonPrefix != "" {
|
|
|
|
sb.WriteString("))")
|
|
|
|
}
|
|
|
|
sb.WriteString(")")
|
2017-04-06 15:31:17 +00:00
|
|
|
}
|