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.



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

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

Instance Attribute Details

#debugLevelObject

Debugging level



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

def debugLevel
  @debugLevel
end

#matchErrorObject

The last match error encountered by the scanner



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

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
119
# File 'lib/Getopt/DelimScanner.rb', line 109

def self.def_casting_delegators( *methods )
    methods.each {|methodName|
        class_eval( <<-EOF, "(--def_casting_delegators--)", 1 )
            undef :#{methodName}
            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.



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

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.



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

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.



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

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.



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

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.



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

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.



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

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)


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

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.



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
299
# File 'lib/Getopt/DelimScanner.rb', line 251

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.



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
568
# File 'lib/Getopt/DelimScanner.rb', line 538

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.



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
215
# File 'lib/Getopt/DelimScanner.rb', line 185

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.



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

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)


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
381
# File 'lib/Getopt/DelimScanner.rb', line 343

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.



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

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.



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

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.



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

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.



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

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.



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

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.



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

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.



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

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