1 |
|
2 | # Pattern is a zero-conflict wrapper extending RegExp features
|
3 | # in order to make YAML parsing regex more expressive.
|
4 | #
|
5 | class Pattern
|
6 |
|
7 | # @property [RegExp] The RegExp instance
|
8 | regex: null
|
9 |
|
10 | # @property [String] The raw regex string
|
11 | rawRegex: null
|
12 |
|
13 | # @property [String] The cleaned regex string (used to create the RegExp instance)
|
14 | cleanedRegex: null
|
15 |
|
16 | # @property [Object] The dictionary mapping names to capturing bracket numbers
|
17 | mapping: null
|
18 |
|
19 | # Constructor
|
20 | #
|
21 | # @param [String] rawRegex The raw regex string defining the pattern
|
22 | #
|
23 | constructor: (rawRegex, modifiers = '') ->
|
24 | cleanedRegex = ''
|
25 | len = rawRegex.length
|
26 | mapping = null
|
27 |
|
28 | # Cleanup raw regex and compute mapping
|
29 | capturingBracketNumber = 0
|
30 | i = 0
|
31 | while i < len
|
32 | _char = rawRegex.charAt(i)
|
33 | if _char is '\\'
|
34 | # Ignore next character
|
35 | cleanedRegex += rawRegex[i..i+1]
|
36 | i++
|
37 | else if _char is '('
|
38 | # Increase bracket number, only if it is capturing
|
39 | if i < len - 2
|
40 | part = rawRegex[i..i+2]
|
41 | if part is '(?:'
|
42 | # Non-capturing bracket
|
43 | i += 2
|
44 | cleanedRegex += part
|
45 | else if part is '(?<'
|
46 | # Capturing bracket with possibly a name
|
47 | capturingBracketNumber++
|
48 | i += 2
|
49 | name = ''
|
50 | while i + 1 < len
|
51 | subChar = rawRegex.charAt(i + 1)
|
52 | if subChar is '>'
|
53 | cleanedRegex += '('
|
54 | i++
|
55 | if name.length > 0
|
56 | # Associate a name with a capturing bracket number
|
57 | mapping ?= {}
|
58 | mapping[name] = capturingBracketNumber
|
59 | break
|
60 | else
|
61 | name += subChar
|
62 |
|
63 | i++
|
64 | else
|
65 | cleanedRegex += _char
|
66 | capturingBracketNumber++
|
67 | else
|
68 | cleanedRegex += _char
|
69 | else
|
70 | cleanedRegex += _char
|
71 |
|
72 | i++
|
73 |
|
74 | @rawRegex = rawRegex
|
75 | @cleanedRegex = cleanedRegex
|
76 | @regex = new RegExp @cleanedRegex, 'g'+modifiers.replace('g', '')
|
77 | @mapping = mapping
|
78 |
|
79 |
|
80 | # Executes the pattern's regex and returns the matching values
|
81 | #
|
82 | # @param [String] str The string to use to execute the pattern
|
83 | #
|
84 | # @return [Array] The matching values extracted from capturing brackets or null if nothing matched
|
85 | #
|
86 | exec: (str) ->
|
87 | @regex.lastIndex = 0
|
88 | matches = @regex.exec str
|
89 |
|
90 | if not matches?
|
91 | return null
|
92 |
|
93 | if @mapping?
|
94 | for name, index of @mapping
|
95 | matches[name] = matches[index]
|
96 |
|
97 | return matches
|
98 |
|
99 |
|
100 | # Tests the pattern's regex
|
101 | #
|
102 | # @param [String] str The string to use to test the pattern
|
103 | #
|
104 | # @return [Boolean] true if the string matched
|
105 | #
|
106 | test: (str) ->
|
107 | @regex.lastIndex = 0
|
108 | return @regex.test str
|
109 |
|
110 |
|
111 | # Replaces occurences matching with the pattern's regex with replacement
|
112 | #
|
113 | # @param [String] str The source string to perform replacements
|
114 | # @param [String] replacement The string to use in place of each replaced occurence.
|
115 | #
|
116 | # @return [String] The replaced string
|
117 | #
|
118 | replace: (str, replacement) ->
|
119 | @regex.lastIndex = 0
|
120 | return str.replace @regex, replacement
|
121 |
|
122 |
|
123 | # Replaces occurences matching with the pattern's regex with replacement and
|
124 | # get both the replaced string and the number of replaced occurences in the string.
|
125 | #
|
126 | # @param [String] str The source string to perform replacements
|
127 | # @param [String] replacement The string to use in place of each replaced occurence.
|
128 | # @param [Integer] limit The maximum number of occurences to replace (0 means infinite number of occurences)
|
129 | #
|
130 | # @return [Array] A destructurable array containing the replaced string and the number of replaced occurences. For instance: ["my replaced string", 2]
|
131 | #
|
132 | replaceAll: (str, replacement, limit = 0) ->
|
133 | @regex.lastIndex = 0
|
134 | count = 0
|
135 | while @regex.test(str) and (limit is 0 or count < limit)
|
136 | @regex.lastIndex = 0
|
137 | str = str.replace @regex, ''
|
138 | count++
|
139 |
|
140 | return [str, count]
|
141 |
|
142 |
|
143 | module.exports = Pattern
|
144 |
|