mustermann-contrib 1.0.0.beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/README.md +1239 -0
 - data/examples/highlighting.rb +35 -0
 - data/highlighting.png +0 -0
 - data/irb.png +0 -0
 - data/lib/mustermann/cake.rb +18 -0
 - data/lib/mustermann/express.rb +37 -0
 - data/lib/mustermann/file_utils.rb +217 -0
 - data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
 - data/lib/mustermann/fileutils.rb +1 -0
 - data/lib/mustermann/flask.rb +198 -0
 - data/lib/mustermann/grape.rb +35 -0
 - data/lib/mustermann/pyramid.rb +28 -0
 - data/lib/mustermann/rails.rb +46 -0
 - data/lib/mustermann/shell.rb +56 -0
 - data/lib/mustermann/simple.rb +50 -0
 - data/lib/mustermann/string_scanner.rb +313 -0
 - data/lib/mustermann/strscan.rb +1 -0
 - data/lib/mustermann/template.rb +62 -0
 - data/lib/mustermann/uri_template.rb +1 -0
 - data/lib/mustermann/versions.rb +46 -0
 - data/lib/mustermann/visualizer.rb +38 -0
 - data/lib/mustermann/visualizer/highlight.rb +137 -0
 - data/lib/mustermann/visualizer/highlighter.rb +37 -0
 - data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
 - data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
 - data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
 - data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
 - data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
 - data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
 - data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
 - data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
 - data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
 - data/lib/mustermann/visualizer/renderer/html.rb +50 -0
 - data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
 - data/lib/mustermann/visualizer/tree.rb +63 -0
 - data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
 - data/mustermann-contrib.gemspec +19 -0
 - data/spec/cake_spec.rb +90 -0
 - data/spec/express_spec.rb +209 -0
 - data/spec/file_utils_spec.rb +119 -0
 - data/spec/flask_spec.rb +361 -0
 - data/spec/flask_subclass_spec.rb +368 -0
 - data/spec/grape_spec.rb +747 -0
 - data/spec/pattern_extension_spec.rb +49 -0
 - data/spec/pyramid_spec.rb +101 -0
 - data/spec/rails_spec.rb +647 -0
 - data/spec/shell_spec.rb +147 -0
 - data/spec/simple_spec.rb +268 -0
 - data/spec/string_scanner_spec.rb +271 -0
 - data/spec/template_spec.rb +841 -0
 - data/spec/visualizer_spec.rb +199 -0
 - data/theme.png +0 -0
 - data/tree.png +0 -0
 - metadata +126 -0
 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'mustermann'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mustermann/ast/pattern'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Mustermann
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Grape style pattern implementation.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 8 
     | 
    
         
            +
              #   Mustermann.new('/:foo', type: :grape) === '/bar' # => true
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @see Mustermann::Pattern
         
     | 
| 
      
 11 
     | 
    
         
            +
              # @see file:README.md#grape Syntax description in the README
         
     | 
| 
      
 12 
     | 
    
         
            +
              class Grape < AST::Pattern
         
     | 
| 
      
 13 
     | 
    
         
            +
                register :grape
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                on(nil, ??, ?)) { |c| unexpected(c) }
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                on(?*)  { |c| scan(/\w+/) ? node(:named_splat, buffer.matched) : node(:splat) }
         
     | 
| 
      
 18 
     | 
    
         
            +
                on(?:)  { |c| node(:capture, constraint: "[^/\\?#\.]") { scan(/\w+/) } }
         
     | 
| 
      
 19 
     | 
    
         
            +
                on(?\\) { |c| node(:char, expect(/./)) }
         
     | 
| 
      
 20 
     | 
    
         
            +
                on(?()  { |c| node(:optional, node(:group) { read unless scan(?)) }) }
         
     | 
| 
      
 21 
     | 
    
         
            +
                on(?|)  { |c| node(:or) }
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                on ?{ do |char|
         
     | 
| 
      
 24 
     | 
    
         
            +
                  type = scan(?+) ? :named_splat : :capture
         
     | 
| 
      
 25 
     | 
    
         
            +
                  name = expect(/[\w\.]+/)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  type = :splat if type == :named_splat and name == 'splat'
         
     | 
| 
      
 27 
     | 
    
         
            +
                  expect(?})
         
     | 
| 
      
 28 
     | 
    
         
            +
                  node(type, name)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                suffix ?? do |char, element|
         
     | 
| 
      
 32 
     | 
    
         
            +
                  node(:optional, element)
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'mustermann'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mustermann/ast/pattern'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Mustermann
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Pyramid style pattern implementation.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 8 
     | 
    
         
            +
              #   Mustermann.new('/<foo>', type: :pryamid) === '/bar' # => true
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @see Mustermann::Pattern
         
     | 
| 
      
 11 
     | 
    
         
            +
              # @see file:README.md#pryamid Syntax description in the README
         
     | 
| 
      
 12 
     | 
    
         
            +
              class Pyramid < AST::Pattern
         
     | 
| 
      
 13 
     | 
    
         
            +
                register :pyramid
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                on(nil, ?}) { |c| unexpected(c) }
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                on(?{) do |char|
         
     | 
| 
      
 18 
     | 
    
         
            +
                  name       = expect(/\w+/, char: char)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  constraint = read_brackets(?{, ?}) if scan(?:)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  expect(?}) unless constraint
         
     | 
| 
      
 21 
     | 
    
         
            +
                  node(:capture, name, constraint: constraint)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                on(?*) do |char|
         
     | 
| 
      
 25 
     | 
    
         
            +
                  node(:named_splat, expect(/\w+$/, char: char), convert: -> e { e.split(?/) })
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'mustermann'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mustermann/ast/pattern'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'mustermann/versions'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Mustermann
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Rails style pattern implementation.
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Mustermann.new('/:foo', type: :rails) === '/bar' # => true
         
     | 
| 
      
 10 
     | 
    
         
            +
              #
         
     | 
| 
      
 11 
     | 
    
         
            +
              # @see Mustermann::Pattern
         
     | 
| 
      
 12 
     | 
    
         
            +
              # @see file:README.md#rails Syntax description in the README
         
     | 
| 
      
 13 
     | 
    
         
            +
              class Rails < AST::Pattern
         
     | 
| 
      
 14 
     | 
    
         
            +
                extend Versions
         
     | 
| 
      
 15 
     | 
    
         
            +
                register :rails
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # first parser, no optional parts
         
     | 
| 
      
 18 
     | 
    
         
            +
                version('2.3') do
         
     | 
| 
      
 19 
     | 
    
         
            +
                  on(nil) { |c| unexpected(c) }
         
     | 
| 
      
 20 
     | 
    
         
            +
                  on(?*)  { |c| node(:named_splat) { scan(/\w+/) } }
         
     | 
| 
      
 21 
     | 
    
         
            +
                  on(?:)  { |c| node(:capture) { scan(/\w+/) } }
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                # rack-mount
         
     | 
| 
      
 25 
     | 
    
         
            +
                version('3.0', '3.1') do
         
     | 
| 
      
 26 
     | 
    
         
            +
                  on(?))  { |c| unexpected(c) }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  on(?()  { |c| node(:optional, node(:group) { read unless scan(?)) }) }
         
     | 
| 
      
 28 
     | 
    
         
            +
                  on(?\\) { |c| node(:char, expect(/./)) }
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                # stand-alone journey
         
     | 
| 
      
 32 
     | 
    
         
            +
                version('3.2') do
         
     | 
| 
      
 33 
     | 
    
         
            +
                  on(?|)  { |c| raise ParseError, "the implementation of | is broken in ActionDispatch, cannot compile compatible pattern" }
         
     | 
| 
      
 34 
     | 
    
         
            +
                  on(?\\) { |c| node(:char, c) }
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                # embedded journey, broken (ignored) escapes
         
     | 
| 
      
 38 
     | 
    
         
            +
                version('4.0', '4.1') { on(?\\) { |c| read } }
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                # escapes got fixed in 4.2
         
     | 
| 
      
 41 
     | 
    
         
            +
                version('4.2') { on(?\\) { |c| node(:char, expect(/./)) } }
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # Rails 5.0 fixes |
         
     | 
| 
      
 44 
     | 
    
         
            +
                version('5.0') { on(?|) { |c| node(:or) }}
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,56 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'mustermann'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mustermann/pattern'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'mustermann/simple_match'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Mustermann
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Matches strings that are identical to the pattern.
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   Mustermann.new('/*.*', type: :shell) === '/bar' # => false
         
     | 
| 
      
 10 
     | 
    
         
            +
              #
         
     | 
| 
      
 11 
     | 
    
         
            +
              # @see Mustermann::Pattern
         
     | 
| 
      
 12 
     | 
    
         
            +
              # @see file:README.md#shell Syntax description in the README
         
     | 
| 
      
 13 
     | 
    
         
            +
              class Shell < Pattern
         
     | 
| 
      
 14 
     | 
    
         
            +
                include Concat::Native
         
     | 
| 
      
 15 
     | 
    
         
            +
                register :shell
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # @!visibility private
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @return [#highlight, nil]
         
     | 
| 
      
 19 
     | 
    
         
            +
                #   highlighing logic for mustermann-visualizer,
         
     | 
| 
      
 20 
     | 
    
         
            +
                #   nil if mustermann-visualizer hasn't been loaded
         
     | 
| 
      
 21 
     | 
    
         
            +
                def highlighter
         
     | 
| 
      
 22 
     | 
    
         
            +
                  return unless defined? Mustermann::Visualizer::Highlighter
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @@highlighter ||= Mustermann::Visualizer::Highlighter.create do
         
     | 
| 
      
 24 
     | 
    
         
            +
                    on('\\') { |matched| escaped(matched, scanner.getch) }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    on(/[\*\[\]]/, :special)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    on("{") { nested(:union, ?{, ?}, ?,) }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                # @param (see Mustermann::Pattern#initialize)
         
     | 
| 
      
 31 
     | 
    
         
            +
                # @return (see Mustermann::Pattern#initialize)
         
     | 
| 
      
 32 
     | 
    
         
            +
                # @see (see Mustermann::Pattern#initialize)
         
     | 
| 
      
 33 
     | 
    
         
            +
                def initialize(string, **options)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @flags = File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB
         
     | 
| 
      
 35 
     | 
    
         
            +
                  super(string, **options)
         
     | 
| 
      
 36 
     | 
    
         
            +
                end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                # @param (see Mustermann::Pattern#===)
         
     | 
| 
      
 39 
     | 
    
         
            +
                # @return (see Mustermann::Pattern#===)
         
     | 
| 
      
 40 
     | 
    
         
            +
                # @see (see Mustermann::Pattern#===)
         
     | 
| 
      
 41 
     | 
    
         
            +
                def ===(string)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  File.fnmatch? @string, unescape(string), @flags
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                # @param (see Mustermann::Pattern#peek_size)
         
     | 
| 
      
 46 
     | 
    
         
            +
                # @return (see Mustermann::Pattern#peek_size)
         
     | 
| 
      
 47 
     | 
    
         
            +
                # @see (see Mustermann::Pattern#peek_size)
         
     | 
| 
      
 48 
     | 
    
         
            +
                def peek_size(string)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @peek_string ||= @string + "{**,/**,/**/*}"
         
     | 
| 
      
 50 
     | 
    
         
            +
                  super if File.fnmatch? @peek_string, unescape(string), @flags
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                # Used by {Mustermann::FileUtils} to not use a generic glob pattern.
         
     | 
| 
      
 54 
     | 
    
         
            +
                alias_method :to_glob, :to_s
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'mustermann'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mustermann/regexp_based'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Mustermann
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Sinatra 1.3 style pattern implementation.
         
     | 
| 
      
 6 
     | 
    
         
            +
              #
         
     | 
| 
      
 7 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 8 
     | 
    
         
            +
              #   Mustermann.new('/:foo', type: :simple) === '/bar' # => true
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              # @see Mustermann::Pattern
         
     | 
| 
      
 11 
     | 
    
         
            +
              # @see file:README.md#simple Syntax description in the README
         
     | 
| 
      
 12 
     | 
    
         
            +
              class Simple < RegexpBased
         
     | 
| 
      
 13 
     | 
    
         
            +
                register :simple
         
     | 
| 
      
 14 
     | 
    
         
            +
                supported_options :greedy, :space_matches_plus
         
     | 
| 
      
 15 
     | 
    
         
            +
                instance_delegate highlighter: 'self.class'
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # @!visibility private
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @return [#highlight, nil]
         
     | 
| 
      
 19 
     | 
    
         
            +
                #   highlighing logic for mustermann-visualizer,
         
     | 
| 
      
 20 
     | 
    
         
            +
                #   nil if mustermann-visualizer hasn't been loaded
         
     | 
| 
      
 21 
     | 
    
         
            +
                def self.highlighter
         
     | 
| 
      
 22 
     | 
    
         
            +
                  return unless defined? Mustermann::Visualizer::Highlighter
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @highlighter ||= Mustermann::Visualizer::Highlighter.create do
         
     | 
| 
      
 24 
     | 
    
         
            +
                    on(/:(\w+)/) { |matched| element(:capture, ':') { element(:name, matched[1..-1]) } }
         
     | 
| 
      
 25 
     | 
    
         
            +
                    on("*" => :splat, "?" => :optional)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def compile(greedy: true, uri_decode: true, space_matches_plus: true, **options)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  pattern = @string.gsub(/[^\?\%\\\/\:\*\w]/) { |c| encoded(c, uri_decode, space_matches_plus) }
         
     | 
| 
      
 31 
     | 
    
         
            +
                  pattern.gsub!(/((:\w+)|\*)/) do |match|
         
     | 
| 
      
 32 
     | 
    
         
            +
                    match == "*" ? "(?<splat>.*?)" : "(?<#{$2[1..-1]}>[^/?#]+#{?? unless greedy})"
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  Regexp.new(pattern)
         
     | 
| 
      
 35 
     | 
    
         
            +
                rescue SyntaxError, RegexpError => error
         
     | 
| 
      
 36 
     | 
    
         
            +
                  type = error.message["invalid group name"] ? CompileError : ParseError
         
     | 
| 
      
 37 
     | 
    
         
            +
                  raise type, error.message, error.backtrace
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def encoded(char, uri_decode, space_matches_plus)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  return Regexp.escape(char) unless uri_decode
         
     | 
| 
      
 42 
     | 
    
         
            +
                  parser  = URI::Parser.new
         
     | 
| 
      
 43 
     | 
    
         
            +
                  encoded = Regexp.union(parser.escape(char), parser.escape(char, /./).downcase, parser.escape(char, /./).upcase)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  encoded = Regexp.union(encoded, encoded('+', true, true)) if space_matches_plus and char == " "
         
     | 
| 
      
 45 
     | 
    
         
            +
                  encoded
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                private :compile, :encoded
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,313 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'mustermann'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mustermann/pattern_cache'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'delegate'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module Mustermann
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Class inspired by Ruby's StringScanner to scan an input string using multiple patterns.
         
     | 
| 
      
 7 
     | 
    
         
            +
              #
         
     | 
| 
      
 8 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 9 
     | 
    
         
            +
              #   require 'mustermann/string_scanner'
         
     | 
| 
      
 10 
     | 
    
         
            +
              #   scanner = Mustermann::StringScanner.new("here is our example string")
         
     | 
| 
      
 11 
     | 
    
         
            +
              #
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   scanner.scan("here") # => "here"
         
     | 
| 
      
 13 
     | 
    
         
            +
              #   scanner.getch        # => " "
         
     | 
| 
      
 14 
     | 
    
         
            +
              #
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   if scanner.scan(":verb our")
         
     | 
| 
      
 16 
     | 
    
         
            +
              #     scanner.scan(:noun, capture: :word)
         
     | 
| 
      
 17 
     | 
    
         
            +
              #     scanner[:verb]  # => "is"
         
     | 
| 
      
 18 
     | 
    
         
            +
              #     scanner[:nound] # => "example"
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 20 
     | 
    
         
            +
              #
         
     | 
| 
      
 21 
     | 
    
         
            +
              #   scanner.rest # => "string"
         
     | 
| 
      
 22 
     | 
    
         
            +
              #
         
     | 
| 
      
 23 
     | 
    
         
            +
              # @note
         
     | 
| 
      
 24 
     | 
    
         
            +
              #   This structure is not thread-safe, you should not scan on the same StringScanner instance concurrently.
         
     | 
| 
      
 25 
     | 
    
         
            +
              #   Even if it was thread-safe, scanning concurrently would probably lead to unwanted behaviour.
         
     | 
| 
      
 26 
     | 
    
         
            +
              class StringScanner
         
     | 
| 
      
 27 
     | 
    
         
            +
                # Exception raised if scan/unscan operation cannot be performed.
         
     | 
| 
      
 28 
     | 
    
         
            +
                ScanError     = Class.new(::ScanError)
         
     | 
| 
      
 29 
     | 
    
         
            +
                PATTERN_CACHE = PatternCache.new
         
     | 
| 
      
 30 
     | 
    
         
            +
                private_constant :PATTERN_CACHE
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # Patterns created by {#scan} will be globally cached, since we assume that there is a finite number
         
     | 
| 
      
 33 
     | 
    
         
            +
                # of different patterns used and that they are more likely to be reused than not.
         
     | 
| 
      
 34 
     | 
    
         
            +
                # This method allows clearing the cache.
         
     | 
| 
      
 35 
     | 
    
         
            +
                #
         
     | 
| 
      
 36 
     | 
    
         
            +
                # @see Mustermann::PatternCache
         
     | 
| 
      
 37 
     | 
    
         
            +
                def self.clear_cache
         
     | 
| 
      
 38 
     | 
    
         
            +
                  PATTERN_CACHE.clear
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                # @return [Integer] number of cached patterns
         
     | 
| 
      
 42 
     | 
    
         
            +
                # @see clear_cache
         
     | 
| 
      
 43 
     | 
    
         
            +
                # @api private
         
     | 
| 
      
 44 
     | 
    
         
            +
                def self.cache_size
         
     | 
| 
      
 45 
     | 
    
         
            +
                  PATTERN_CACHE.size
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                # Encapsulates return values for {StringScanner#scan}, {StringScanner#check}, and friends.
         
     | 
| 
      
 49 
     | 
    
         
            +
                # Behaves like a String (the substring which matched the pattern), but also exposes its position
         
     | 
| 
      
 50 
     | 
    
         
            +
                # in the main string and any params parsed from it.
         
     | 
| 
      
 51 
     | 
    
         
            +
                class ScanResult < DelegateClass(String)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # The scanner this result came from.
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #   require 'mustermann/string_scanner'
         
     | 
| 
      
 55 
     | 
    
         
            +
                  #   scanner = Mustermann::StringScanner.new('foo/bar')
         
     | 
| 
      
 56 
     | 
    
         
            +
                  #   scanner.scan(:name).scanner == scanner # => true
         
     | 
| 
      
 57 
     | 
    
         
            +
                  attr_reader :scanner
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 60 
     | 
    
         
            +
                  #   require 'mustermann/string_scanner'
         
     | 
| 
      
 61 
     | 
    
         
            +
                  #   scanner = Mustermann::StringScanner.new('foo/bar')
         
     | 
| 
      
 62 
     | 
    
         
            +
                  #   scanner.scan(:name).position # => 0
         
     | 
| 
      
 63 
     | 
    
         
            +
                  #   scanner.getch.position       # => 3
         
     | 
| 
      
 64 
     | 
    
         
            +
                  #   scanner.scan(:name).position # => 4
         
     | 
| 
      
 65 
     | 
    
         
            +
                  #
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # @return [Integer] position the substring starts at
         
     | 
| 
      
 67 
     | 
    
         
            +
                  attr_reader :position
         
     | 
| 
      
 68 
     | 
    
         
            +
                  alias_method :pos, :position
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 71 
     | 
    
         
            +
                  #   require 'mustermann/string_scanner'
         
     | 
| 
      
 72 
     | 
    
         
            +
                  #   scanner = Mustermann::StringScanner.new('foo/bar')
         
     | 
| 
      
 73 
     | 
    
         
            +
                  #   scanner.scan(:name).length # => 3
         
     | 
| 
      
 74 
     | 
    
         
            +
                  #   scanner.getch.length       # => 1
         
     | 
| 
      
 75 
     | 
    
         
            +
                  #   scanner.scan(:name).length # => 3
         
     | 
| 
      
 76 
     | 
    
         
            +
                  #
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # @return [Integer] length of the substring
         
     | 
| 
      
 78 
     | 
    
         
            +
                  attr_reader :length
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  # Params parsed from the substring.
         
     | 
| 
      
 81 
     | 
    
         
            +
                  # Will not include params from previous scan results.
         
     | 
| 
      
 82 
     | 
    
         
            +
                  #
         
     | 
| 
      
 83 
     | 
    
         
            +
                  # @example
         
     | 
| 
      
 84 
     | 
    
         
            +
                  #   require 'mustermann/string_scanner'
         
     | 
| 
      
 85 
     | 
    
         
            +
                  #   scanner = Mustermann::StringScanner.new('foo/bar')
         
     | 
| 
      
 86 
     | 
    
         
            +
                  #   scanner.scan(:name).params # => { "name" => "foo" }
         
     | 
| 
      
 87 
     | 
    
         
            +
                  #   scanner.getch.params       # => {}
         
     | 
| 
      
 88 
     | 
    
         
            +
                  #   scanner.scan(:name).params # => { "name" => "bar" }
         
     | 
| 
      
 89 
     | 
    
         
            +
                  #
         
     | 
| 
      
 90 
     | 
    
         
            +
                  # @see Mustermann::StringScanner#params
         
     | 
| 
      
 91 
     | 
    
         
            +
                  # @see Mustermann::StringScanner#[]
         
     | 
| 
      
 92 
     | 
    
         
            +
                  #
         
     | 
| 
      
 93 
     | 
    
         
            +
                  # @return [Hash] params parsed from the substring
         
     | 
| 
      
 94 
     | 
    
         
            +
                  attr_reader :params
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 97 
     | 
    
         
            +
                  def initialize(scanner, position, length, params = {})
         
     | 
| 
      
 98 
     | 
    
         
            +
                    @scanner, @position, @length, @params = scanner, position, length, params
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
                  # @api private
         
     | 
| 
      
 102 
     | 
    
         
            +
                  # @!visibility private
         
     | 
| 
      
 103 
     | 
    
         
            +
                  def __getobj__
         
     | 
| 
      
 104 
     | 
    
         
            +
                    @__getobj__ ||= scanner.to_s[position, length]
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
                end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                # @return [Hash] default pattern options used for {#scan} and similar methods
         
     | 
| 
      
 109 
     | 
    
         
            +
                # @see #initialize
         
     | 
| 
      
 110 
     | 
    
         
            +
                attr_reader :pattern_options
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                # Params from all previous matches from {#scan} and {#scan_until},
         
     | 
| 
      
 113 
     | 
    
         
            +
                # but not from {#check} and {#check_until}. Changes can be reverted
         
     | 
| 
      
 114 
     | 
    
         
            +
                # with {#unscan} and it can be completely cleared via {#reset}.
         
     | 
| 
      
 115 
     | 
    
         
            +
                #
         
     | 
| 
      
 116 
     | 
    
         
            +
                # @return [Hash] current params
         
     | 
| 
      
 117 
     | 
    
         
            +
                attr_reader :params
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                # @return [Integer] current scan position on the input string
         
     | 
| 
      
 120 
     | 
    
         
            +
                attr_accessor :position
         
     | 
| 
      
 121 
     | 
    
         
            +
                alias_method :pos, :position
         
     | 
| 
      
 122 
     | 
    
         
            +
                alias_method :pos=, :position=
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                # @example with different default type
         
     | 
| 
      
 125 
     | 
    
         
            +
                #   require 'mustermann/string_scanner'
         
     | 
| 
      
 126 
     | 
    
         
            +
                #   scanner = Mustermann::StringScanner.new("foo/bar/baz", type: :shell)
         
     | 
| 
      
 127 
     | 
    
         
            +
                #   scanner.scan('*')     # => "foo"
         
     | 
| 
      
 128 
     | 
    
         
            +
                #   scanner.scan('**/*')  # => "/bar/baz"
         
     | 
| 
      
 129 
     | 
    
         
            +
                #
         
     | 
| 
      
 130 
     | 
    
         
            +
                # @param [String] string the string to scan
         
     | 
| 
      
 131 
     | 
    
         
            +
                # @param [Hash] pattern_options default options used for {#scan}
         
     | 
| 
      
 132 
     | 
    
         
            +
                def initialize(string = "", **pattern_options)
         
     | 
| 
      
 133 
     | 
    
         
            +
                  @pattern_options = pattern_options
         
     | 
| 
      
 134 
     | 
    
         
            +
                  @string          = String(string).dup
         
     | 
| 
      
 135 
     | 
    
         
            +
                  reset
         
     | 
| 
      
 136 
     | 
    
         
            +
                end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                # Resets the {#position} to the start and clears all {#params}.
         
     | 
| 
      
 139 
     | 
    
         
            +
                # @return [Mustermann::StringScanner] the scanner itself
         
     | 
| 
      
 140 
     | 
    
         
            +
                def reset
         
     | 
| 
      
 141 
     | 
    
         
            +
                  @position = 0
         
     | 
| 
      
 142 
     | 
    
         
            +
                  @params   = {}
         
     | 
| 
      
 143 
     | 
    
         
            +
                  @history  = []
         
     | 
| 
      
 144 
     | 
    
         
            +
                  self
         
     | 
| 
      
 145 
     | 
    
         
            +
                end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
                # Moves the position to the end of the input string.
         
     | 
| 
      
 148 
     | 
    
         
            +
                # @return [Mustermann::StringScanner] the scanner itself
         
     | 
| 
      
 149 
     | 
    
         
            +
                def terminate
         
     | 
| 
      
 150 
     | 
    
         
            +
                  track_result ScanResult.new(self, @position, size - @position)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  self
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                # Checks if the given pattern matches any substring starting at the current position.
         
     | 
| 
      
 155 
     | 
    
         
            +
                #
         
     | 
| 
      
 156 
     | 
    
         
            +
                # If it does, it will advance the current {#position} to the end of the substring and merges any params parsed
         
     | 
| 
      
 157 
     | 
    
         
            +
                # from the substring into {#params}.
         
     | 
| 
      
 158 
     | 
    
         
            +
                #
         
     | 
| 
      
 159 
     | 
    
         
            +
                # @param (see Mustermann.new)
         
     | 
| 
      
 160 
     | 
    
         
            +
                # @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
         
     | 
| 
      
 161 
     | 
    
         
            +
                def scan(pattern, **options)
         
     | 
| 
      
 162 
     | 
    
         
            +
                  track_result check(pattern, **options)
         
     | 
| 
      
 163 
     | 
    
         
            +
                end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                # Checks if the given pattern matches any substring starting at any position after the current position.
         
     | 
| 
      
 166 
     | 
    
         
            +
                #
         
     | 
| 
      
 167 
     | 
    
         
            +
                # If it does, it will advance the current {#position} to the end of the substring and merges any params parsed
         
     | 
| 
      
 168 
     | 
    
         
            +
                # from the substring into {#params}.
         
     | 
| 
      
 169 
     | 
    
         
            +
                #
         
     | 
| 
      
 170 
     | 
    
         
            +
                # @param (see Mustermann.new)
         
     | 
| 
      
 171 
     | 
    
         
            +
                # @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
         
     | 
| 
      
 172 
     | 
    
         
            +
                def scan_until(pattern, **options)
         
     | 
| 
      
 173 
     | 
    
         
            +
                  result, prefix = check_until_with_prefix(pattern, **options)
         
     | 
| 
      
 174 
     | 
    
         
            +
                  track_result(prefix, result)
         
     | 
| 
      
 175 
     | 
    
         
            +
                end
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                # Reverts the last operation that advanced the position.
         
     | 
| 
      
 178 
     | 
    
         
            +
                #
         
     | 
| 
      
 179 
     | 
    
         
            +
                # Operations advancing the position: {#terminate}, {#scan}, {#scan_until}, {#getch}.
         
     | 
| 
      
 180 
     | 
    
         
            +
                # @return [Mustermann::StringScanner] the scanner itself
         
     | 
| 
      
 181 
     | 
    
         
            +
                def unscan
         
     | 
| 
      
 182 
     | 
    
         
            +
                  raise ScanError, 'unscan failed: previous match record not exist' if @history.empty?
         
     | 
| 
      
 183 
     | 
    
         
            +
                  previous = @history[0..-2]
         
     | 
| 
      
 184 
     | 
    
         
            +
                  reset
         
     | 
| 
      
 185 
     | 
    
         
            +
                  previous.each { |r| track_result(*r) }
         
     | 
| 
      
 186 
     | 
    
         
            +
                  self
         
     | 
| 
      
 187 
     | 
    
         
            +
                end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
                # Checks if the given pattern matches any substring starting at the current position.
         
     | 
| 
      
 190 
     | 
    
         
            +
                #
         
     | 
| 
      
 191 
     | 
    
         
            +
                # Does not affect {#position} or {#params}.
         
     | 
| 
      
 192 
     | 
    
         
            +
                #
         
     | 
| 
      
 193 
     | 
    
         
            +
                # @param (see Mustermann.new)
         
     | 
| 
      
 194 
     | 
    
         
            +
                # @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
         
     | 
| 
      
 195 
     | 
    
         
            +
                def check(pattern, **options)
         
     | 
| 
      
 196 
     | 
    
         
            +
                  params, length = create_pattern(pattern, **options).peek_params(rest)
         
     | 
| 
      
 197 
     | 
    
         
            +
                  ScanResult.new(self, @position, length, params) if params
         
     | 
| 
      
 198 
     | 
    
         
            +
                end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                # Checks if the given pattern matches any substring starting at any position after the current position.
         
     | 
| 
      
 201 
     | 
    
         
            +
                #
         
     | 
| 
      
 202 
     | 
    
         
            +
                # Does not affect {#position} or {#params}.
         
     | 
| 
      
 203 
     | 
    
         
            +
                #
         
     | 
| 
      
 204 
     | 
    
         
            +
                # @param (see Mustermann.new)
         
     | 
| 
      
 205 
     | 
    
         
            +
                # @return [Mustermann::StringScanner::ScanResult, nil] the matched substring, nil if it didn't match
         
     | 
| 
      
 206 
     | 
    
         
            +
                def check_until(pattern, **options)
         
     | 
| 
      
 207 
     | 
    
         
            +
                  check_until_with_prefix(pattern, **options).first
         
     | 
| 
      
 208 
     | 
    
         
            +
                end
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
      
 210 
     | 
    
         
            +
                def check_until_with_prefix(pattern, **options)
         
     | 
| 
      
 211 
     | 
    
         
            +
                  start      = @position
         
     | 
| 
      
 212 
     | 
    
         
            +
                  @position += 1 until eos? or result = check(pattern, **options)
         
     | 
| 
      
 213 
     | 
    
         
            +
                  prefix     = ScanResult.new(self, start, @position - start) if result
         
     | 
| 
      
 214 
     | 
    
         
            +
                  [result, prefix]
         
     | 
| 
      
 215 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 216 
     | 
    
         
            +
                  @position  = start
         
     | 
| 
      
 217 
     | 
    
         
            +
                end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                # Reads a single character and advances the {#position} by one.
         
     | 
| 
      
 220 
     | 
    
         
            +
                # @return [Mustermann::StringScanner::ScanResult, nil] the character, nil if at end of string
         
     | 
| 
      
 221 
     | 
    
         
            +
                def getch
         
     | 
| 
      
 222 
     | 
    
         
            +
                  track_result ScanResult.new(self, @position, 1) unless eos?
         
     | 
| 
      
 223 
     | 
    
         
            +
                end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                # Appends the given string to the string being scanned
         
     | 
| 
      
 226 
     | 
    
         
            +
                #
         
     | 
| 
      
 227 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 228 
     | 
    
         
            +
                #   require 'mustermann/string_scanner'
         
     | 
| 
      
 229 
     | 
    
         
            +
                #   scanner = Mustermann::StringScanner.new
         
     | 
| 
      
 230 
     | 
    
         
            +
                #   scanner << "foo"
         
     | 
| 
      
 231 
     | 
    
         
            +
                #   scanner.scan(/.+/) # => "foo"
         
     | 
| 
      
 232 
     | 
    
         
            +
                #
         
     | 
| 
      
 233 
     | 
    
         
            +
                # @param [String] string will be appended
         
     | 
| 
      
 234 
     | 
    
         
            +
                # @return [Mustermann::StringScanner] the scanner itself
         
     | 
| 
      
 235 
     | 
    
         
            +
                def <<(string)
         
     | 
| 
      
 236 
     | 
    
         
            +
                  @string << string
         
     | 
| 
      
 237 
     | 
    
         
            +
                  self
         
     | 
| 
      
 238 
     | 
    
         
            +
                end
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                # @return [true, false] whether or not the end of the string has been reached
         
     | 
| 
      
 241 
     | 
    
         
            +
                def eos?
         
     | 
| 
      
 242 
     | 
    
         
            +
                  @position >= @string.size
         
     | 
| 
      
 243 
     | 
    
         
            +
                end
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                # @return [true, false] whether or not the current position is at the start of a line
         
     | 
| 
      
 246 
     | 
    
         
            +
                def beginning_of_line?
         
     | 
| 
      
 247 
     | 
    
         
            +
                  @position == 0 or @string[@position - 1] == "\n"
         
     | 
| 
      
 248 
     | 
    
         
            +
                end
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                # @return [String] outstanding string not yet matched, empty string at end of input string
         
     | 
| 
      
 251 
     | 
    
         
            +
                def rest
         
     | 
| 
      
 252 
     | 
    
         
            +
                  @string[@position..-1] || ""
         
     | 
| 
      
 253 
     | 
    
         
            +
                end
         
     | 
| 
      
 254 
     | 
    
         
            +
             
     | 
| 
      
 255 
     | 
    
         
            +
                # @return [Integer] number of character remaining to be scanned
         
     | 
| 
      
 256 
     | 
    
         
            +
                def rest_size
         
     | 
| 
      
 257 
     | 
    
         
            +
                  @position > size ? 0 : size - @position
         
     | 
| 
      
 258 
     | 
    
         
            +
                end
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
      
 260 
     | 
    
         
            +
                # Allows to peek at a number of still unscanned characters without advacing the {#position}.
         
     | 
| 
      
 261 
     | 
    
         
            +
                #
         
     | 
| 
      
 262 
     | 
    
         
            +
                # @param [Integer] length how many characters to look at
         
     | 
| 
      
 263 
     | 
    
         
            +
                # @return [String] the substring
         
     | 
| 
      
 264 
     | 
    
         
            +
                def peek(length = 1)
         
     | 
| 
      
 265 
     | 
    
         
            +
                  @string[@position, length]
         
     | 
| 
      
 266 
     | 
    
         
            +
                end
         
     | 
| 
      
 267 
     | 
    
         
            +
             
     | 
| 
      
 268 
     | 
    
         
            +
                # Shorthand for accessing {#params}. Accepts symbols as keys.
         
     | 
| 
      
 269 
     | 
    
         
            +
                def [](key)
         
     | 
| 
      
 270 
     | 
    
         
            +
                  params[key.to_s]
         
     | 
| 
      
 271 
     | 
    
         
            +
                end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
                # (see #params)
         
     | 
| 
      
 274 
     | 
    
         
            +
                def to_h
         
     | 
| 
      
 275 
     | 
    
         
            +
                  params.dup
         
     | 
| 
      
 276 
     | 
    
         
            +
                end
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                # @return [String] the input string
         
     | 
| 
      
 279 
     | 
    
         
            +
                # @see #initialize
         
     | 
| 
      
 280 
     | 
    
         
            +
                # @see #<<
         
     | 
| 
      
 281 
     | 
    
         
            +
                def to_s
         
     | 
| 
      
 282 
     | 
    
         
            +
                  @string.dup
         
     | 
| 
      
 283 
     | 
    
         
            +
                end
         
     | 
| 
      
 284 
     | 
    
         
            +
             
     | 
| 
      
 285 
     | 
    
         
            +
                # @return [Integer] size of the input string
         
     | 
| 
      
 286 
     | 
    
         
            +
                def size
         
     | 
| 
      
 287 
     | 
    
         
            +
                  @string.size
         
     | 
| 
      
 288 
     | 
    
         
            +
                end
         
     | 
| 
      
 289 
     | 
    
         
            +
             
     | 
| 
      
 290 
     | 
    
         
            +
                # @!visibility private
         
     | 
| 
      
 291 
     | 
    
         
            +
                def inspect
         
     | 
| 
      
 292 
     | 
    
         
            +
                  "#<%p %d/%d @ %p>" % [ self.class, @position, @string.size, @string ]
         
     | 
| 
      
 293 
     | 
    
         
            +
                end
         
     | 
| 
      
 294 
     | 
    
         
            +
             
     | 
| 
      
 295 
     | 
    
         
            +
                # @!visibility private
         
     | 
| 
      
 296 
     | 
    
         
            +
                def create_pattern(pattern, **options)
         
     | 
| 
      
 297 
     | 
    
         
            +
                  PATTERN_CACHE.create_pattern(pattern, **options, **pattern_options)
         
     | 
| 
      
 298 
     | 
    
         
            +
                end
         
     | 
| 
      
 299 
     | 
    
         
            +
             
     | 
| 
      
 300 
     | 
    
         
            +
                # @!visibility private
         
     | 
| 
      
 301 
     | 
    
         
            +
                def track_result(*results)
         
     | 
| 
      
 302 
     | 
    
         
            +
                  results.compact!
         
     | 
| 
      
 303 
     | 
    
         
            +
                  @history << results if results.any?
         
     | 
| 
      
 304 
     | 
    
         
            +
                  results.each do |result|
         
     | 
| 
      
 305 
     | 
    
         
            +
                    @params.merge! result.params
         
     | 
| 
      
 306 
     | 
    
         
            +
                    @position += result.length
         
     | 
| 
      
 307 
     | 
    
         
            +
                  end
         
     | 
| 
      
 308 
     | 
    
         
            +
                  results.last
         
     | 
| 
      
 309 
     | 
    
         
            +
                end
         
     | 
| 
      
 310 
     | 
    
         
            +
             
     | 
| 
      
 311 
     | 
    
         
            +
                private :create_pattern, :track_result, :check_until_with_prefix
         
     | 
| 
      
 312 
     | 
    
         
            +
              end
         
     | 
| 
      
 313 
     | 
    
         
            +
            end
         
     |