Class: Goodcheck::Pattern::Token

Inherits:
Object
  • Object
show all
Defined in:
lib/goodcheck/pattern.rb

Defined Under Namespace

Classes: VarPattern

Constant Summary collapse

%r{
  (?: ((?:ed2k|ftp|http|https|irc|mailto|news|gopher|nntp|telnet|webcal|xmpp|callto|feed|svn|urn|aim|rsync|tag|ssh|sftp|rtsp|afs|file):)// | www\. )
  [^\s<\u00A0")]+
}ix
AUTO_EMAIL_LOCAL_RE =
/[\w.!#\$%&'*\/=?^`{|}~+-]/
AUTO_EMAIL_RE =
/(?<!#{AUTO_EMAIL_LOCAL_RE})[\w.!#\$%+-]\.?#{AUTO_EMAIL_LOCAL_RE}*@[\w-]+(?:\.[\w-]+)+/
WORD_RE =
/\w+|[\p{L}&&\p{^ASCII}]+/
@@TYPES =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(source:, variables:, case_sensitive:) ⇒ Token

Returns a new instance of Token.



42
43
44
45
46
# File 'lib/goodcheck/pattern.rb', line 42

def initialize(source:, variables:, case_sensitive:)
  @source = source
  @variables = variables
  @case_sensitive = case_sensitive
end

Instance Attribute Details

#case_sensitiveObject (readonly)

Returns the value of attribute case_sensitive.



40
41
42
# File 'lib/goodcheck/pattern.rb', line 40

def case_sensitive
  @case_sensitive
end

#sourceObject (readonly)

Returns the value of attribute source.



40
41
42
# File 'lib/goodcheck/pattern.rb', line 40

def source
  @source
end

#variablesObject (readonly)

Returns the value of attribute variables.



40
41
42
# File 'lib/goodcheck/pattern.rb', line 40

def variables
  @variables
end

Class Method Details

.compile_tokens(source, variables, case_sensitive:) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
# File 'lib/goodcheck/pattern.rb', line 207

def self.compile_tokens(source, variables, case_sensitive:)
  tokens = []
  s = StringScanner.new(source)

  until s.eos?
    case
    when s.scan(/\${(?<name>[a-zA-Z_]\w*)(?::(?<type>#{::Regexp.union(*@@TYPES.keys.map(&:to_s))}))?}/)
      name = s[:name].to_sym
      type = s[:type] ? s[:type].to_sym : :__

      if variables.key?(name)
        if !s[:type] && s.pre_match == ""
          Goodcheck.logger.error "Variable binding ${#{name}} at the beginning of pattern would cause an unexpected match"
        end
        if !s[:type] && s.peek(1) == ""
          Goodcheck.logger.error "Variable binding ${#{name}} at the end of pattern would cause an unexpected match"
        end

        tokens << :nobr
        variables[name].type = type
        regexp = regexp_for_type(name: name, type: type, scanner: s).to_s
        if tokens.empty? && (type == :word || type == :identifier)
          regexp = /\b#{regexp.to_s}/
        end
        tokens << regexp.to_s
        tokens << :nobr
      else
        tokens << ::Regexp.escape("${")
        tokens << ::Regexp.escape(name.to_s)
        tokens << ::Regexp.escape("}")
      end
    when s.scan(/\(|\)|\{|\}|\[|\]|\<|\>/)
      tokens << ::Regexp.escape(s.matched)
    when s.scan(/\s+/)
      tokens << '\s+'
    when s.scan(WORD_RE)
      tokens << ::Regexp.escape(s.matched)
    when s.scan(%r{[!"#%&'=\-^~¥\\|`@*:+;/?.,]+})
      tokens << ::Regexp.escape(s.matched.rstrip)
    when s.scan(/./)
      tokens << ::Regexp.escape(s.matched)
    end
  end

  if source[0] =~ /\p{L}/
    tokens.first.prepend('\b')
  end

  if source[-1] =~ /\p{L}/
    tokens.last << '\b'
  end

  options = ::Regexp::MULTILINE
  options |= ::Regexp::IGNORECASE unless case_sensitive

  buf, skip = tokens[0].is_a?(String) ? [tokens[0], false] : ["", true]
  tokens.drop(1).each do |tok|
    if tok == :nobr
      skip = true
    else
      buf << '\s*' unless skip
      skip = false
      buf << tok
    end
  end

  ::Regexp.new(buf.
    gsub(/\\s\*(\\s\+\\s\*)+/, '\s+').
    gsub(/#{::Regexp.escape('\s+\s*')}/, '\s+').
    gsub(/#{::Regexp.escape('\s*\s+')}/, '\s+'), options)
end

.expand(prefix, suffix, depth: 5) ⇒ Object



165
166
167
168
169
170
171
172
173
174
# File 'lib/goodcheck/pattern.rb', line 165

def self.expand(prefix, suffix, depth: 5)
  if depth == 0
    [
      /[^#{suffix}]*/
    ]
  else
    expandeds = expand(prefix, suffix, depth: depth - 1)
    [/[^#{prefix}#{suffix}]*#{prefix}#{expandeds.first}#{suffix}[^#{prefix}#{suffix}]*/] + expandeds
  end
end

.regexp_for_type(name:, type:, scanner:) ⇒ Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/goodcheck/pattern.rb', line 176

def self.regexp_for_type(name:, type:, scanner:)
  prefix = scanner.pre_match[-1]
  suffix = scanner.check(WORD_RE) || scanner.peek(1)

  case
  when type == :__
    body = case
           when prefix == "{" && suffix == "}"
             ::Regexp.union(expand(prefix, suffix))
           when prefix == "(" && suffix == ")"
             ::Regexp.union(expand(prefix, suffix))
           when prefix == "[" && suffix == "]"
             ::Regexp.union(expand(prefix, suffix))
           when prefix == "<" && suffix == ">"
             ::Regexp.union(expand(prefix, suffix))
           else
             unless suffix.empty?
               /(?~#{::Regexp.escape(suffix)})/
             else
               /.*/
             end
           end
    /(?<#{name}>#{body})/

  when @@TYPES.key?(type)
    @@TYPES[type][name]
  end
end

Instance Method Details

#regexpObject



48
49
50
# File 'lib/goodcheck/pattern.rb', line 48

def regexp
  @regexp ||= Token.compile_tokens(source, variables, case_sensitive: case_sensitive)
end

#test_variables(match) ⇒ Object



97
98
99
100
101
102
# File 'lib/goodcheck/pattern.rb', line 97

def test_variables(match)
  variables.all? do |name, var|
    str = match[name]
    str && var.test(str)
  end
end