class Mustermann::StringScanner
Class inspired by Ruby's StringScanner
to scan an input string using multiple patterns.
@example
require 'mustermann/string_scanner' scanner = Mustermann::StringScanner.new("here is our example string") scanner.scan("here") # => "here" scanner.getch # => " " if scanner.scan(":verb our") scanner.scan(:noun, capture: :word) scanner[:verb] # => "is" scanner[:nound] # => "example" end scanner.rest # => "string"
@note
This structure is not thread-safe, you should not scan on the same StringScanner instance concurrently. Even if it was thread-safe, scanning concurrently would probably lead to unwanted behaviour.
Constants
- PATTERN_CACHE
- ScanError
Exception raised if scan/unscan operation cannot be performed.
Attributes
Params from all previous matches from {#scan} and {#scan_until}, but not from {#check} and {#check_until}. Changes can be reverted with {#unscan} and it can be completely cleared via {#reset}.
@return [Hash] current params
@return [Hash] default pattern options used for {#scan} and similar methods @see initialize
@return [Integer] current scan position on the input string
@return [Integer] current scan position on the input string
@return [Integer] current scan position on the input string
Public Class Methods
@return [Integer] number of cached patterns @see clear_cache
@api private
# File lib/mustermann/string_scanner.rb, line 44 def self.cache_size PATTERN_CACHE.size end
Patterns created by {#scan} will be globally cached, since we assume that there is a finite number of different patterns used and that they are more likely to be reused than not. This method allows clearing the cache.
@see Mustermann::PatternCache
# File lib/mustermann/string_scanner.rb, line 37 def self.clear_cache PATTERN_CACHE.clear end
@example with different default type
require 'mustermann/string_scanner' scanner = Mustermann::StringScanner.new("foo/bar/baz", type: :shell) scanner.scan('*') # => "foo" scanner.scan('**/*') # => "/bar/baz"
@param [String] string the string to scan @param [Hash] pattern_options
default options used for {#scan}
# File lib/mustermann/string_scanner.rb, line 132 def initialize(string = "", **pattern_options) @pattern_options = pattern_options @string = String(string).dup reset end
Public Instance Methods
Appends the given string to the string being scanned
@example
require 'mustermann/string_scanner' scanner = Mustermann::StringScanner.new scanner << "foo" scanner.scan(/.+/) # => "foo"
@param [String] string will be appended @return [Mustermann::StringScanner] the scanner itself
# File lib/mustermann/string_scanner.rb, line 235 def <<(string) @string << string self end
Shorthand for accessing {#params}. Accepts symbols as keys.
# File lib/mustermann/string_scanner.rb, line 269 def [](key) params[key.to_s] end
@return [true, false] whether or not the current position is at the start of a line
# File lib/mustermann/string_scanner.rb, line 246 def beginning_of_line? @position == 0 or @string[@position - 1] == "\n" end
Checks if the given pattern matches any substring starting at the current position.
Does not affect {#position} or {#params}.
@param (see Mustermann.new) @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
# File lib/mustermann/string_scanner.rb, line 195 def check(pattern, **options) params, length = create_pattern(pattern, **options).peek_params(rest) ScanResult.new(self, @position, length, params) if params end
Checks if the given pattern matches any substring starting at any position after the current position.
Does not affect {#position} or {#params}.
@param (see Mustermann.new) @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
# File lib/mustermann/string_scanner.rb, line 206 def check_until(pattern, **options) check_until_with_prefix(pattern, **options).first end
@return [true, false] whether or not the end of the string has been reached
# File lib/mustermann/string_scanner.rb, line 241 def eos? @position >= @string.size end
Reads a single character and advances the {#position} by one. @return [Mustermann::StringScanner::ScanResult, nil] the character, nil if at end of string
# File lib/mustermann/string_scanner.rb, line 221 def getch track_result ScanResult.new(self, @position, 1) unless eos? end
@!visibility private
# File lib/mustermann/string_scanner.rb, line 291 def inspect "#<%p %d/%d @ %p>" % [ self.class, @position, @string.size, @string ] end
Allows to peek at a number of still unscanned characters without advacing the {#position}.
@param [Integer] length how many characters to look at @return [String] the substring
# File lib/mustermann/string_scanner.rb, line 264 def peek(length = 1) @string[@position, length] end
Resets the {#position} to the start and clears all {#params}. @return [Mustermann::StringScanner] the scanner itself
# File lib/mustermann/string_scanner.rb, line 140 def reset @position = 0 @params = {} @history = [] self end
@return [String] outstanding string not yet matched, empty string at end of input string
# File lib/mustermann/string_scanner.rb, line 251 def rest @string[@position..-1] || "" end
@return [Integer] number of character remaining to be scanned
# File lib/mustermann/string_scanner.rb, line 256 def rest_size @position > size ? 0 : size - @position end
Checks if the given pattern matches any substring starting at the current position.
If it does, it will advance the current {#position} to the end of the substring and merges any params parsed from the substring into {#params}.
@param (see Mustermann.new) @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
# File lib/mustermann/string_scanner.rb, line 161 def scan(pattern, **options) track_result check(pattern, **options) end
Checks if the given pattern matches any substring starting at any position after the current position.
If it does, it will advance the current {#position} to the end of the substring and merges any params parsed from the substring into {#params}.
@param (see Mustermann.new) @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
# File lib/mustermann/string_scanner.rb, line 172 def scan_until(pattern, **options) result, prefix = check_until_with_prefix(pattern, **options) track_result(prefix, result) end
@return [Integer] size of the input string
# File lib/mustermann/string_scanner.rb, line 286 def size @string.size end
Moves the position to the end of the input string. @return [Mustermann::StringScanner] the scanner itself
# File lib/mustermann/string_scanner.rb, line 149 def terminate track_result ScanResult.new(self, @position, size - @position) self end
(see params
)
# File lib/mustermann/string_scanner.rb, line 274 def to_h params.dup end
@return [String] the input string @see initialize @see <<
# File lib/mustermann/string_scanner.rb, line 281 def to_s @string.dup end
Reverts the last operation that advanced the position.
Operations advancing the position: {#terminate}, {#scan}, {#scan_until}, {#getch}. @return [Mustermann::StringScanner] the scanner itself
# File lib/mustermann/string_scanner.rb, line 181 def unscan raise ScanError, 'unscan failed: previous match record not exist' if @history.empty? previous = @history[0..-2] reset previous.each { |r| track_result(*r) } self end
Private Instance Methods
# File lib/mustermann/string_scanner.rb, line 210 def check_until_with_prefix(pattern, **options) start = @position @position += 1 until eos? or result = check(pattern, **options) prefix = ScanResult.new(self, start, @position - start) if result [result, prefix] ensure @position = start end
@!visibility private
# File lib/mustermann/string_scanner.rb, line 296 def create_pattern(pattern, **options) PATTERN_CACHE.create_pattern(pattern, **options, **pattern_options) end
@!visibility private
# File lib/mustermann/string_scanner.rb, line 301 def track_result(*results) results.compact! @history << results if results.any? results.each do |result| @params.merge! result.params @position += result.length end results.last end