/src/mongo/gotools/src/github.com/mongodb/mongo-tools/mongorestore/ns/ns.go

https://github.com/paralect/mongo · Go · 241 lines · 180 code · 23 blank · 38 comment · 39 complexity · fc3abcffdc5181452fd885eeec6c7e7b MD5 · raw file

  1. // Copyright (C) MongoDB, Inc. 2014-present.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
  6. package ns
  7. import (
  8. "fmt"
  9. "regexp"
  10. "strings"
  11. )
  12. // Renamer maps namespaces given user-defined patterns
  13. type Renamer struct {
  14. // List of regexps to match namespaces against
  15. matchers []*regexp.Regexp
  16. // List of regexp-style replacement strings to use with the matcher
  17. replacers []string
  18. }
  19. // Matcher identifies namespaces given user-defined patterns
  20. type Matcher struct {
  21. // List of regexps to check namespaces against
  22. matchers []*regexp.Regexp
  23. }
  24. var (
  25. unescapeReplacements = []string{
  26. `\\`, `\`,
  27. `\*`, "*",
  28. `\`, "",
  29. }
  30. unescapeReplacer = strings.NewReplacer(unescapeReplacements...)
  31. )
  32. // Escape escapes instances of '\' and '*' with a backslash
  33. func Escape(in string) string {
  34. in = strings.Replace(in, `\`, `\\`, -1)
  35. in = strings.Replace(in, "*", `\*`, -1)
  36. return in
  37. }
  38. // Unescape removes the escaping backslash where applicable
  39. func Unescape(in string) string {
  40. return unescapeReplacer.Replace(in)
  41. }
  42. var (
  43. // Finds non-escaped asterisks
  44. wildCardRE = regexp.MustCompile(`^(|.*[^\\])\*(.*)$`)
  45. // Finds $variables$ at the beginning of a string
  46. variableRE = regexp.MustCompile(`^\$([^\$]*)\$(.*)$`)
  47. // List of control characters that a regexp can use
  48. escapedChars = `*[](){}\?$^+!.|`
  49. )
  50. // peelLeadingVariable returns the first variable in the given string and
  51. // the remaining string if there is such a variable at the beginning
  52. func peelLeadingVariable(in string) (name, rest string, ok bool) {
  53. var match = variableRE.FindStringSubmatch(in)
  54. if len(match) != 3 {
  55. return
  56. }
  57. return match[1], match[2], true
  58. }
  59. // replaceWildCards replaces non-escaped asterisks with named variables
  60. // i.e. 'pre*.*' becomes 'pre$1$.$2$'
  61. func replaceWildCards(in string) string {
  62. count := 1
  63. match := wildCardRE.FindStringSubmatch(in)
  64. for len(match) > 2 {
  65. in = fmt.Sprintf("%s$%d$%s", match[1], count, match[2])
  66. match = wildCardRE.FindStringSubmatch(in)
  67. count++
  68. }
  69. return Unescape(in)
  70. }
  71. // countAsterisks returns the number of non-escaped asterisks
  72. func countAsterisks(in string) int {
  73. return strings.Count(in, "*") - strings.Count(in, `\*`)
  74. }
  75. // countDollarSigns returns the number of dollar signs
  76. func countDollarSigns(in string) int {
  77. return strings.Count(in, "$")
  78. }
  79. // validateReplacement performs preliminary checks on the from and to strings,
  80. // returning an error if it finds a syntactic issue
  81. func validateReplacement(from, to string) error {
  82. if strings.Contains(from, "$") {
  83. if countDollarSigns(from)%2 != 0 {
  84. return fmt.Errorf("Odd number of dollar signs in from: '%s'", from)
  85. }
  86. if countDollarSigns(to)%2 != 0 {
  87. return fmt.Errorf("Odd number of dollar signs in to: '%s'", to)
  88. }
  89. } else {
  90. if countAsterisks(from) != countAsterisks(to) {
  91. return fmt.Errorf("Different number of asterisks in from: '%s' and to: '%s'", from, to)
  92. }
  93. }
  94. return nil
  95. }
  96. // processReplacement converts the given from and to strings into a regexp and
  97. // a corresponding replacement string
  98. func processReplacement(from, to string) (re *regexp.Regexp, replacer string, err error) {
  99. if !strings.Contains(from, "$") {
  100. // Convert asterisk wild cards to named variables
  101. from = replaceWildCards(from)
  102. to = replaceWildCards(to)
  103. }
  104. // Map from variable names to positions in the search regexp
  105. vars := make(map[string]int)
  106. var matcher string
  107. for len(from) > 0 {
  108. varName, rest, ok := peelLeadingVariable(from)
  109. if ok { // found variable
  110. if _, ok := vars[varName]; ok {
  111. // Cannot repeat the same variable in a 'from' string
  112. err = fmt.Errorf("Variable name '%s' used more than once", varName)
  113. return
  114. }
  115. // Put the variable in the map with its index in the string
  116. vars[varName] = len(vars) + 1
  117. matcher += "(.*?)"
  118. from = rest
  119. continue
  120. }
  121. c := rune(from[0])
  122. if c == '$' {
  123. err = fmt.Errorf("Extraneous '$'")
  124. return
  125. }
  126. if strings.ContainsRune(escapedChars, c) {
  127. // Add backslash before special chars
  128. matcher += `\`
  129. }
  130. matcher += string(c)
  131. from = from[1:]
  132. }
  133. matcher = fmt.Sprintf("^%s$", matcher)
  134. // The regexp we generated should always compile (it's not the user's fault)
  135. re = regexp.MustCompile(matcher)
  136. for len(to) > 0 {
  137. varName, rest, ok := peelLeadingVariable(to)
  138. if ok { // found variable
  139. if num, ok := vars[varName]; ok {
  140. replacer += fmt.Sprintf("${%d}", num)
  141. to = rest
  142. } else {
  143. err = fmt.Errorf("Unknown variable '%s'", varName)
  144. return
  145. }
  146. continue
  147. }
  148. c := rune(to[0])
  149. if c == '$' {
  150. err = fmt.Errorf("Extraneous '$'")
  151. return
  152. }
  153. replacer += string(c)
  154. to = to[1:]
  155. }
  156. return
  157. }
  158. // NewRenamer creates a Renamer that will use the given from and to slices to
  159. // map namespaces
  160. func NewRenamer(fromSlice, toSlice []string) (r *Renamer, err error) {
  161. if len(fromSlice) != len(toSlice) {
  162. err = fmt.Errorf("Different number of froms and tos")
  163. return
  164. }
  165. r = new(Renamer)
  166. for i := len(fromSlice) - 1; i >= 0; i-- {
  167. // reversed for replacement precedence
  168. from := fromSlice[i]
  169. to := toSlice[i]
  170. err = validateReplacement(from, to)
  171. if err != nil {
  172. return
  173. }
  174. matcher, replacer, e := processReplacement(from, to)
  175. if e != nil {
  176. err = fmt.Errorf("Invalid replacement from '%s' to '%s': %s", from, to, e)
  177. return
  178. }
  179. r.matchers = append(r.matchers, matcher)
  180. r.replacers = append(r.replacers, replacer)
  181. }
  182. return
  183. }
  184. // Get returns the rewritten namespace according to the renamer's rules
  185. func (r *Renamer) Get(name string) string {
  186. for i, matcher := range r.matchers {
  187. if matcher.MatchString(name) {
  188. return matcher.ReplaceAllString(name, r.replacers[i])
  189. }
  190. }
  191. return name
  192. }
  193. // NewMatcher creates a matcher that will use the given list patterns to
  194. // match namespaces
  195. func NewMatcher(patterns []string) (m *Matcher, err error) {
  196. m = new(Matcher)
  197. for _, pattern := range patterns {
  198. if strings.Contains(pattern, "$") {
  199. err = fmt.Errorf("'$' is not allowed in include/exclude patternsj")
  200. }
  201. re, _, e := processReplacement(pattern, pattern)
  202. if e != nil {
  203. err = fmt.Errorf("%s processing include/exclude pattern: '%s'", err, pattern)
  204. return
  205. }
  206. m.matchers = append(m.matchers, re)
  207. }
  208. return
  209. }
  210. // Has returns whether the given namespace matches any of the matcher's patterns
  211. func (m *Matcher) Has(name string) bool {
  212. for _, re := range m.matchers {
  213. if re.MatchString(name) {
  214. return true
  215. }
  216. }
  217. return false
  218. }