Class: DelimScanner

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Default
Defined in:
lib/Getopt/DelimScanner.rb

Overview

A derivative of StringScanner that can scan for delimited constructs in addition to regular expressions.

Defined Under Namespace

Modules: Default Classes: DelimiterError, MatchFailure

Constant Summary collapse

Version =

Class constants

/([\d\.]+)/.match( %q{$Revision: 1.2 $} )[1]
Rcsid =
%q$Id: DelimScanner.rb,v 1.2 2003/01/12 20:56:51 deveiant Exp $
XmlName =

Pattern to match a valid XML name

'[a-zA-Z_:][a-zA-Z0-9:.-]*'
NeedCastingDelegators =

A list of delegated methods that need casting.

:scan, :skip, :match?, :check,
:scan_until, :skip_until, :exist?, :check_until

Constants included from Default

Default::CodeblockDelimiters, Default::MultipleFunctions

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(string, dup = true) ⇒ DelimScanner

Create a new DelimScanner object for the specified string. If dup is true, a duplicate of the target string will be used instead of the one given. The target string will be frozen after the scanner is created.



125
126
127
128
129
# File 'lib/Getopt/DelimScanner.rb', line 125

def initialize( string, dup=true )
    @scanner    = StringScanner::new( string, dup )
    @matchError = nil
    @debugLevel = 0
end

Instance Attribute Details

#debugLevelObject

Debugging level



162
163
164
# File 'lib/Getopt/DelimScanner.rb', line 162

def debugLevel
  @debugLevel
end

#matchErrorObject

The last match error encountered by the scanner



158
159
160
# File 'lib/Getopt/DelimScanner.rb', line 158

def matchError
  @matchError
end

Class Method Details

.def_casting_delegators(*methods) ⇒ Object

Define delegating methods that cast their argument to a Regexp from a String. This allows the scanner’s scanning methods to be called with Strings in addition to Regexps. This was mostly stolen from forwardable.rb.



109
110
111
112
113
114
115
116
117
118
# File 'lib/Getopt/DelimScanner.rb', line 109

def self.def_casting_delegators( *methods )
    methods.each {|methodName|
        class_eval( <<-EOF, "(--def_casting_delegators--)", 1 )
            def #{methodName}( pattern )
                pattern = pattern.to_s.to_re unless pattern.is_a?( Regexp )
                @scanner.#{methodName}( pattern )
            end
        EOF
    }
end

Instance Method Details

#extractBracketed(*args) ⇒ Object

Match using the #scanBracketed method, but only return the match or nil.



302
303
304
305
# File 'lib/Getopt/DelimScanner.rb', line 302

def extractBracketed( *args )
    rval = scanBracketed( *args ) or return nil
    return rval[:match]
end

#extractCodeblock(*args) ⇒ Object

Match using the #scanCodeblock method, but only return the match or nil.



571
572
573
574
# File 'lib/Getopt/DelimScanner.rb', line 571

def extractCodeblock( *args )
    rval = scanCodeblock( *args ) or return nil
    return rval[:match]
end

#extractDelimited(*args) ⇒ Object

Match using the #scanDelimited method, but only return the match or nil.



218
219
220
221
# File 'lib/Getopt/DelimScanner.rb', line 218

def extractDelimited( *args )
    rval = scanDelimited( *args ) or return nil
    return rval[:match]
end

#extractQuotelike(*args) ⇒ Object

Match using the #scanQuotelike method, but only return the match or nil.



461
462
463
464
# File 'lib/Getopt/DelimScanner.rb', line 461

def extractQuotelike( *args )
    rval = scanQuotelike( *args ) or return nil
    return rval[:match]
end

#extractTagged(*args) ⇒ Object

Match using the #scanTagged method, but only return the match or nil.



384
385
386
387
# File 'lib/Getopt/DelimScanner.rb', line 384

def extractTagged( *args )
    rval = scanTagged( *args ) or return nil
    return rval[:match]
end

#extractVariable(*args) ⇒ Object

Match using the #scanVariable method, but only return the match or nil.



510
511
512
513
# File 'lib/Getopt/DelimScanner.rb', line 510

def extractVariable( *args )
    rval = scanVariable( *args ) or return nil
    return rval[:match]
end

#matchError?Boolean

Returns true if the scanner has encountered a match error.

Returns:

  • (Boolean)


167
168
169
# File 'lib/Getopt/DelimScanner.rb', line 167

def matchError?
    return ! @matchError.nil?
end

#scanBracketed(delimiters = "{([<", prefix = '\s*') ⇒ Object

Starting at the scan pointer, try to match a substring delimited by balanced delimiters of the type specified, after skipping the specified prefix. On a successful match, this method advances the scan pointer and returns a Hash with the following key/value pairs:

:match

The text of the match, including the delimiting brackets.

:prefix

The matched prefix, if any.

On failure, returns nil.



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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/Getopt/DelimScanner.rb', line 250

def scanBracketed( delimiters="{([<", prefix='\s*' )
    delimiters  ||= "{([<"
    prefix      ||= '\s*'

    prefix = prefix.to_re unless prefix.kind_of?( Regexp )

    debugMsg( 1, "Scanning for bracketed text: delimiters = (%s), prefix = (%s)",
              delimiters, prefix )

    self.matchError = nil

    # Split the left-delimiters (brackets) from the quote delimiters.
    ldel = delimiters.dup
    qdel = ldel.squeeze.split(//).find_all {|char| char =~ /["'`]/ }.join('|')
    qdel = nil if qdel.empty?
    quotelike = true if ldel =~ /q/

    # Change all instances of delimiters to the left-hand versions, and
    # strip away anything but bracketing delimiters
    ldel = ldel.tr( '[](){}<>', '[[(({{<<' ).gsub(/[^#{Regexp.quote('[\\](){}<>')}]+/, '').squeeze

    ### Now build the right-delim equivalent of the left delim string
    rdel = ldel.dup
    unless rdel.tr!( '[({<', '])}>' )
        raise DelimiterError, "Did not find a suitable bracket in delimiter: '#{delimiters}'"
    end

    # Build regexps from both bracketing delimiter strings
    ldel = ldel.split(//).collect {|ch| Regexp.quote(ch)}.join('|')
    rdel = rdel.split(//).collect {|ch| Regexp.quote(ch)}.join('|')

    depth = self.scanDepth
    result = nil
    startPos = self.pointer

    begin
        result = matchBracketed( prefix, ldel, qdel, quotelike, rdel )
    rescue MatchFailure => e
        debugMsg( depth + 1, "Match error: %s" % e.message )
        self.matchError = e.message
        self.pointer = startPos
        result = nil
    rescue => e
        self.pointer = startPos
        Kernel::raise
    end

    return result
end

#scanCodeblock(innerDelim = CodeblockDelimiters, prefix = '\s*', outerDelim = innerDelim) ⇒ Object

Starting from the scan pointer, and skipping the specified prefix, try to to recognize and match a balanced bracket-, do/end-, or begin/end-delimited substring that may contain unbalanced delimiters inside quotes or quotelike operations.



537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/Getopt/DelimScanner.rb', line 537

def scanCodeblock( innerDelim=CodeblockDelimiters, prefix='\s*', outerDelim=innerDelim )
    self.matchError = nil
    result          = nil
    startPos        = self.pointer

    prefix          ||= '\s*'
    innerDelim      ||= CodeblockDelimiters
    outerDelim      ||= innerDelim

    depth = caller(1).find_all {|frame|
        frame =~ /in `scan(Variable|Tagged|Codeblock|Bracketed|Quotelike)'/
    }.length

    begin
        debugMsg 3, "------------------------------------"
        debugMsg 3, "Calling matchCodeBlock( %s, %s, %s )",
            prefix.inspect, innerDelim.inspect, outerDelim.inspect
        debugMsg 3, "------------------------------------"
        result = matchCodeblock( prefix, innerDelim, outerDelim )
    rescue MatchFailure => e
        debugMsg( depth + 1, "Match error: %s" % e.message )
        self.matchError = e.message
        self.pointer = startPos
        result = nil
    rescue => e
        self.pointer = startPos
        Kernel::raise
    end

    return result
end

#scanDelimited(delimiters = "'\"`", prefix = '\\s*', escape = '\\') ⇒ Object

Starting at the scan pointer, try to match a substring delimited by the specified delimiters, skipping the specified prefix and any character escaped by the specified escape character/s. If matched, advances the scan pointer and returns a Hash with the following key/value pairs on success:

:match

The text of the match, including delimiters.

:prefix

The matched prefix, if any.

If the match fails, returns nil.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/Getopt/DelimScanner.rb', line 184

def scanDelimited( delimiters="'\"`", prefix='\\s*', escape='\\' )
    delimiters  ||= "'\"`"
    prefix      ||= '\\s*'
    escape      ||= '\\'

    debugMsg( 1, "Scanning for delimited text: delim = (%s), prefix=(%s), escape=(%s)",
              delimiters, prefix, escape )
    self.matchError = nil

    # Try to match the prefix first to get the length
    unless (( prefixLength = self.match?(prefix.to_re) ))
        self.matchError = "Failed to match prefix '%s' at offset %d" %
            [ prefix, self.pointer ]
        return nil
    end
        
    # Now build a delimited pattern with the specified parameters.
    delimPattern = makeDelimPattern( delimiters, escape, prefix )
    debugMsg( 2, "Delimiter pattern is %s" % delimPattern.inspect )

    # Fail if no match
    unless (( matchedString = self.scan(delimPattern) ))
        self.matchError = "No delimited string found."
        return nil
    end

    return {
        :match  => matchedString[prefixLength .. -1],
        :prefix => matchedString[0..prefixLength-1],
    }
end

#scanQuotelike(prefix = '\s*', matchRawRegex = true) ⇒ Object

Starting from the scan pointer, try to match any one of the various Ruby quotes and quotelike operators after skipping the specified prefix. Nested backslashed delimiters, embedded balanced bracket delimiters (for the quotelike operators), and trailing modifiers are all caught. If matchRawRegex is true, inline regexen (eg., /pattern/) are matched as well. Advances the scan pointer and returns a Hash with the following key/value pairs on success:

:match

The entire text of the match.

:prefix

The matched prefix, if any.

:quoteOp

The name of the quotelike operator (if any) (eg., ‘%Q’, ‘%r’, etc).

:leftDelim

The left delimiter of the first block of the operation.

:delimText

The text of the first block of the operation.

:rightDelim

The right delimiter of the first block of the operation.

:modifiers

The trailing modifiers on the operation (if any).

On failure, returns nil.



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'lib/Getopt/DelimScanner.rb', line 436

def scanQuotelike( prefix='\s*', matchRawRegex=true )

    self.matchError = nil
    result          = nil
    startPos        = self.pointer

    depth = self.scanDepth

    begin
        result = matchQuotelike( prefix, matchRawRegex )
    rescue MatchFailure => e
        debugMsg( depth + 1, "Match error: %s" % e.message )
        self.matchError = e.message
        self.pointer = startPos
        result = nil
    rescue => e
        self.pointer = startPos
        Kernel::raise
    end

    return result
end

#scanTagged(opentag = nil, closetag = nil, prefix = '\s*', options = {}) ⇒ Object

Extracts and segments text from the scan pointer forward that occurs between (balanced) specified tags, after skipping the specified prefix. If the opentag argument is nil, a pattern which will match any standard HTML/XML tag will be used. If the closetag argument is nil, a pattern is created which prepends a / character to the matched opening tag, after any bracketing characters. The options argument is a Hash of one or more options which govern the matching operation. They are described in more detail in the Description section of ‘lib/DelimScanner.rb’. On a successful match, this method advances the scan pointer and returns an

:match

The text of the match, including the delimiting tags.

:prefix

The matched prefix, if any.

On failure, returns nil.

Raises:

  • (ArgumentError)


342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
# File 'lib/Getopt/DelimScanner.rb', line 342

def scanTagged( opentag=nil, closetag=nil, prefix='\s*', options={} )
    prefix ||= '\s*'

    ldel = opentag || %Q,<\\w+(?:#{ makeDelimPattern(%q:'":) }|[^>])*>,
    rdel = closetag
    raise ArgumentError, "Options argument must be a hash" unless options.kind_of?( Hash )

    failmode    = options[:fail]
    bad     = if options[:reject].is_a?( Array ) then
                  options[:reject].join("|")
              else
                  (options[:reject] || '')
              end
    ignore  = if options[:ignore].is_a?( Array ) then
                  options[:ignore].join("|")
              else
                  (options[:ignore] || '')
              end

    self.matchError = nil
    result          = nil
    startPos        = self.pointer

    depth = self.scanDepth

    begin
        result = matchTagged( prefix, ldel, rdel, failmode, bad, ignore )
    rescue MatchFailure => e
        debugMsg( depth + 1, "Match error: %s" % e.message )
        self.matchError = e.message
        self.pointer = startPos
        result = nil
    rescue => e
        self.pointer = startPos
        Kernel::raise
    end

    return result
end

#scanVariable(prefix = '\s*') ⇒ Object

Starting from the scan pointer, try to match a Ruby variable after skipping the specified prefix.



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
# File 'lib/Getopt/DelimScanner.rb', line 486

def scanVariable( prefix='\s*' )
    self.matchError = nil
    result          = nil
    startPos        = self.pointer

    depth = self.scanDepth

    begin
        result = matchVariable( prefix )
    rescue MatchFailure => e
        debugMsg( depth + 1, "Match error: %s" % e.message )
        self.matchError = e.message
        self.pointer = startPos
        result = nil
    rescue => e
        self.pointer = startPos
        Kernel::raise
    end

    return result
end

#skipBracketed(*args) ⇒ Object

Starting at the scan pointer, try to match a substring with #scanBracketed. On a successful match, this method advances the scan pointer and returns the length of the match, including the delimiters and any prefix that was skipped. On failure, returns nil.



312
313
314
315
316
317
318
319
320
321
322
# File 'lib/Getopt/DelimScanner.rb', line 312

def skipBracketed( *args )
    startPos = self.pointer

    match = scanBracketed( *args )

    return nil unless match
    return match.length + prefix.length
ensure
    debugMsg( 2, "Resetting scan pointer." )
    self.pointer = startPos
end

#skipCodeblock(*args) ⇒ Object

Starting at the scan pointer, try to match a substring with #scanCodeblock. On a successful match, this method advances the scan pointer and returns the length of the match, including any delimiters and any prefix that was skipped. On failure, returns nil.



581
582
583
584
585
586
587
588
589
590
591
# File 'lib/Getopt/DelimScanner.rb', line 581

def skipCodeblock( *args )
    startPos = self.pointer

    match = scanCodeblock( *args )

    return nil unless match
    return match.length + prefix.length
ensure
    debugMsg( 2, "Resetting scan pointer." )
    self.pointer = startPos
end

#skipDelimited(delimiters = "'\"`", prefix = '\\s*', escape = '\\') ⇒ Object

Starting at the scan pointer, try to match a substring delimited by the specified delimiters, skipping the specified prefix and any character escaped by the specified escape character/s. If matched, advances the scan pointer and returns the length of the matched string; if it fails the match, returns nil.



229
230
231
232
233
234
235
236
# File 'lib/Getopt/DelimScanner.rb', line 229

def skipDelimited( delimiters="'\"`", prefix='\\s*', escape='\\' )
    delimiters  ||= "'\"`"
    prefix      ||= '\\s*'
    escape      ||= '\\'

    self.matchError = nil
    return self.skip( makeDelimPattern(delimiters, escape, prefix) )
end

#skipQuotelike(*args) ⇒ Object

Starting at the scan pointer, try to match a substring with #scanQuotelike. On a successful match, this method advances the scan pointer and returns the length of the match, including any delimiters and any prefix that was skipped. On failure, returns nil.



471
472
473
474
475
476
477
478
479
480
481
# File 'lib/Getopt/DelimScanner.rb', line 471

def skipQuotelike( *args )
    startPos = self.pointer

    match = scanQuotelike( *args )

    return nil unless match
    return match.length + prefix.length
ensure
    debugMsg( 2, "Resetting scan pointer." )
    self.pointer = startPos
end

#skipTagged(*args) ⇒ Object

Starting at the scan pointer, try to match a substring with #scanTagged. On a successful match, this method advances the scan pointer and returns the length of the match, including any delimiters and any prefix that was skipped. On failure, returns nil.



394
395
396
397
398
399
400
401
402
403
404
# File 'lib/Getopt/DelimScanner.rb', line 394

def skipTagged( *args )
    startPos = self.pointer

    match = scanTagged( *args )

    return nil unless match
    return match.length + prefix.length
ensure
    debugMsg( 2, "Resetting scan pointer." )
    self.pointer = startPos
end

#skipVariable(*args) ⇒ Object

Starting at the scan pointer, try to match a substring with #scanVariable. On a successful match, this method advances the scan pointer and returns the length of the match, including any delimiters and any prefix that was skipped. On failure, returns nil.



520
521
522
523
524
525
526
527
528
529
530
# File 'lib/Getopt/DelimScanner.rb', line 520

def skipVariable( *args )
    startPos = self.pointer

    match = scanVariable( *args )

    return nil unless match
    return match.length + prefix.length
ensure
    debugMsg( 2, "Resetting scan pointer." )
    self.pointer = startPos
end