diff options
author | Ramon Fernandez <ramon@mongodb.com> | 2016-08-25 16:34:34 -0400 |
---|---|---|
committer | Ramon Fernandez <ramon@mongodb.com> | 2016-08-25 16:54:18 -0400 |
commit | c330c9991ab45e7d0685d53e699ef26dba065660 (patch) | |
tree | 3dc5cd06b5f6c7eaaa4cb20cbe763504c14a772b /src/mongo/gotools/mongorestore/ns/ns.go | |
parent | eb62b862d5ebf179a1bcd9f394070e69c30188ab (diff) | |
download | mongo-c330c9991ab45e7d0685d53e699ef26dba065660.tar.gz |
Import tools: 5b883d86fdb4df55036d5dba2ca6f9dfa0750b44 from branch v3.3
ref: 1ac1389bda..5b883d86fd
for: 3.3.12
SERVER-25814 Initial vendor import: tools
Diffstat (limited to 'src/mongo/gotools/mongorestore/ns/ns.go')
-rw-r--r-- | src/mongo/gotools/mongorestore/ns/ns.go | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/src/mongo/gotools/mongorestore/ns/ns.go b/src/mongo/gotools/mongorestore/ns/ns.go new file mode 100644 index 00000000000..ce17a38277c --- /dev/null +++ b/src/mongo/gotools/mongorestore/ns/ns.go @@ -0,0 +1,235 @@ +package ns + +import ( + "fmt" + "regexp" + "strings" +) + +// Renamer maps namespaces given user-defined patterns +type Renamer struct { + // List of regexps to match namespaces against + matchers []*regexp.Regexp + // List of regexp-syle replacement strings to use with the matcher + replacers []string +} + +// Matcher identifies namespaces given user-defined patterns +type Matcher struct { + // List of regexps to check namespaces against + matchers []*regexp.Regexp +} + +var ( + unescapeReplacements = []string{ + `\\`, `\`, + `\*`, "*", + `\`, "", + } + unescapeReplacer = strings.NewReplacer(unescapeReplacements...) +) + +// Escape escapes instances of '\' and '*' with a backslash +func Escape(in string) string { + in = strings.Replace(in, `\`, `\\`, -1) + in = strings.Replace(in, "*", `\*`, -1) + return in +} + +// Unescape removes the escaping backslash where applicable +func Unescape(in string) string { + return unescapeReplacer.Replace(in) +} + +var ( + // Finds non-escaped asterisks + wildCardRE = regexp.MustCompile(`^(|.*[^\\])\*(.*)$`) + // Finds $variables$ at the beginning of a string + variableRE = regexp.MustCompile(`^\$([^\$]*)\$(.*)$`) + // List of control characters that a regexp can use + escapedChars = `*[](){}\?$^+!.|` +) + +// peelLeadingVariable returns the first variable in the given string and +// the remaining string if there is such a variable at the beginning +func peelLeadingVariable(in string) (name, rest string, ok bool) { + var match = variableRE.FindStringSubmatch(in) + if len(match) != 3 { + return + } + return match[1], match[2], true +} + +// replaceWildCards replaces non-escaped asterisks with named variables +// i.e. 'pre*.*' becomes 'pre$1$.$2$' +func replaceWildCards(in string) string { + count := 1 + match := wildCardRE.FindStringSubmatch(in) + for len(match) > 2 { + in = fmt.Sprintf("%s$%d$%s", match[1], count, match[2]) + match = wildCardRE.FindStringSubmatch(in) + count++ + } + return Unescape(in) +} + +// countAsterisks returns the number of non-escaped asterisks +func countAsterisks(in string) int { + return strings.Count(in, "*") - strings.Count(in, `\*`) +} + +// countDollarSigns returns the number of dollar signs +func countDollarSigns(in string) int { + return strings.Count(in, "$") +} + +// validateReplacement performs preliminary checks on the from and to strings, +// returning an error if it finds a syntactic issue +func validateReplacement(from, to string) error { + if strings.Contains(from, "$") { + if countDollarSigns(from)%2 != 0 { + return fmt.Errorf("Odd number of dollar signs in from: '%s'", from) + } + if countDollarSigns(to)%2 != 0 { + return fmt.Errorf("Odd number of dollar signs in to: '%s'", to) + } + } else { + if countAsterisks(from) != countAsterisks(to) { + return fmt.Errorf("Different number of asterisks in from: '%s' and to: '%s'", from, to) + } + } + return nil +} + +// processReplacement converts the given from and to strings into a regexp and +// a corresponding replacement string +func processReplacement(from, to string) (re *regexp.Regexp, replacer string, err error) { + if !strings.Contains(from, "$") { + // Convert asterisk wild cards to named variables + from = replaceWildCards(from) + to = replaceWildCards(to) + } + + // Map from variable names to positions in the search regexp + vars := make(map[string]int) + + var matcher string + for len(from) > 0 { + varName, rest, ok := peelLeadingVariable(from) + if ok { // found variable + if _, ok := vars[varName]; ok { + // Cannot repeat the same variable in a 'from' string + err = fmt.Errorf("Variable name '%s' used more than once", varName) + return + } + // Put the variable in the map with its index in the string + vars[varName] = len(vars) + 1 + matcher += "(.*?)" + from = rest + continue + } + + c := rune(from[0]) + if c == '$' { + err = fmt.Errorf("Extraneous '$'") + return + } + if strings.ContainsRune(escapedChars, c) { + // Add backslash before special chars + matcher += `\` + } + matcher += string(c) + from = from[1:] + } + matcher = fmt.Sprintf("^%s$", matcher) + // The regexp we generated should always compile (it's not the user's fault) + re = regexp.MustCompile(matcher) + + for len(to) > 0 { + varName, rest, ok := peelLeadingVariable(to) + if ok { // found variable + if num, ok := vars[varName]; ok { + replacer += fmt.Sprintf("${%d}", num) + to = rest + } else { + err = fmt.Errorf("Unknown variable '%s'", varName) + return + } + continue + } + + c := rune(to[0]) + if c == '$' { + err = fmt.Errorf("Extraneous '$'") + return + } + replacer += string(c) + to = to[1:] + } + return +} + +// NewRenamer creates a Renamer that will use the given from and to slices to +// map namespaces +func NewRenamer(fromSlice, toSlice []string) (r *Renamer, err error) { + if len(fromSlice) != len(toSlice) { + err = fmt.Errorf("Different number of froms and tos") + return + } + r = new(Renamer) + for i := len(fromSlice) - 1; i >= 0; i-- { + // reversed for replacement precedence + from := fromSlice[i] + to := toSlice[i] + err = validateReplacement(from, to) + if err != nil { + return + } + matcher, replacer, e := processReplacement(from, to) + if e != nil { + err = fmt.Errorf("Invalid replacement from '%s' to '%s': %s", from, to, e) + return + } + r.matchers = append(r.matchers, matcher) + r.replacers = append(r.replacers, replacer) + } + return +} + +// Get returns the rewritten namespace according to the renamer's rules +func (r *Renamer) Get(name string) string { + for i, matcher := range r.matchers { + if matcher.MatchString(name) { + return matcher.ReplaceAllString(name, r.replacers[i]) + } + } + return name +} + +// NewMatcher creates a matcher that will use the given list patterns to +// match namespaces +func NewMatcher(patterns []string) (m *Matcher, err error) { + m = new(Matcher) + for _, pattern := range patterns { + if strings.Contains(pattern, "$") { + err = fmt.Errorf("'$' is not allowed in include/exclude patternsj") + } + re, _, e := processReplacement(pattern, pattern) + if e != nil { + err = fmt.Errorf("%s processing include/exclude pattern: '%s'", err, pattern) + return + } + m.matchers = append(m.matchers, re) + } + return +} + +// Has returns whether the given namespace matches any of the matcher's patterns +func (m *Matcher) Has(name string) bool { + for _, re := range m.matchers { + if re.MatchString(name) { + return true + } + } + return false +} |