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 @@
|
|
1
|
+
require 'mustermann/string_scanner'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
require 'mustermann/ast/pattern'
|
3
|
+
|
4
|
+
module Mustermann
|
5
|
+
# URI template pattern implementation.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Mustermann.new('/{foo}') === '/bar' # => true
|
9
|
+
#
|
10
|
+
# @see Mustermann::Pattern
|
11
|
+
# @see file:README.md#template Syntax description in the README
|
12
|
+
# @see http://tools.ietf.org/html/rfc6570 RFC 6570
|
13
|
+
class Template < AST::Pattern
|
14
|
+
include Concat::Native
|
15
|
+
register :template, :uri_template
|
16
|
+
|
17
|
+
on ?{ do |char|
|
18
|
+
variable = proc do
|
19
|
+
start = pos
|
20
|
+
match = expect(/(?<name>\w+)(?:\:(?<prefix>\d{1,4})|(?<explode>\*))?/)
|
21
|
+
node(:variable, match[:name], prefix: match[:prefix], explode: match[:explode], start: start)
|
22
|
+
end
|
23
|
+
|
24
|
+
operator = buffer.scan(/[\+\#\.\/;\?\&\=\,\!\@\|]/)
|
25
|
+
expression = node(:expression, [variable[]], operator: operator) { variable[] if scan(?,) }
|
26
|
+
expression if expect(?})
|
27
|
+
end
|
28
|
+
|
29
|
+
on(?}) { |c| unexpected(c) }
|
30
|
+
|
31
|
+
# @!visibility private
|
32
|
+
def compile(*args, **options)
|
33
|
+
@split_params = {}
|
34
|
+
super(*args, split_params: @split_params, **options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
def map_param(key, value)
|
39
|
+
return super unless variable = @split_params[key]
|
40
|
+
value = value.split variable[:separator]
|
41
|
+
value.map! { |e| e.sub(/\A#{key}=/, '') } if variable[:parametric]
|
42
|
+
value.map! { |e| super(key, e) }
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!visibility private
|
46
|
+
def always_array?(key)
|
47
|
+
@split_params.include? key
|
48
|
+
end
|
49
|
+
|
50
|
+
# Identity patterns support generating templates (the logic is quite complex, though).
|
51
|
+
#
|
52
|
+
# @example (see Mustermann::Pattern#to_templates)
|
53
|
+
# @param (see Mustermann::Pattern#to_templates)
|
54
|
+
# @return (see Mustermann::Pattern#to_templates)
|
55
|
+
# @see Mustermann::Pattern#to_templates
|
56
|
+
def to_templates
|
57
|
+
[to_s]
|
58
|
+
end
|
59
|
+
|
60
|
+
private :compile, :map_param, :always_array?
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'mustermann/template'
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Mustermann
|
2
|
+
# Mixin that adds support for multiple versions of the same type.
|
3
|
+
# @see Mustermann::Rails
|
4
|
+
# @!visibility private
|
5
|
+
module Versions
|
6
|
+
# Checks if class has mulitple versions available and picks one that matches the version option.
|
7
|
+
# @!visibility private
|
8
|
+
def new(*args, version: nil, **options)
|
9
|
+
return super(*args, **options) unless versions.any?
|
10
|
+
self[version].new(*args, **options)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Hash] version to subclass mapping.
|
14
|
+
# @!visibility private
|
15
|
+
def versions
|
16
|
+
@versions ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Defines a new version.
|
20
|
+
# @!visibility private
|
21
|
+
def version(*list, inherit_from: nil, &block)
|
22
|
+
superclass = self[inherit_from] || self
|
23
|
+
subclass = Class.new(superclass, &block)
|
24
|
+
list.each { |v| versions[v] = subclass }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Resolve a subclass for a given version string.
|
28
|
+
# @!visibility private
|
29
|
+
def [](version)
|
30
|
+
return versions.values.last unless version
|
31
|
+
detected = versions.detect { |v,_| version.start_with?(v) }
|
32
|
+
raise ArgumentError, 'unsupported version %p' % version unless detected
|
33
|
+
detected.last
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!visibility private
|
37
|
+
def name
|
38
|
+
super || superclass.name
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!visibility private
|
42
|
+
def inspect
|
43
|
+
name
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'mustermann'
|
2
|
+
require 'mustermann/visualizer/highlight'
|
3
|
+
require 'mustermann/visualizer/tree_renderer'
|
4
|
+
require 'mustermann/visualizer/pattern_extension'
|
5
|
+
|
6
|
+
module Mustermann
|
7
|
+
# Namespace for Mustermann visualization logic.
|
8
|
+
module Visualizer
|
9
|
+
extend self
|
10
|
+
|
11
|
+
# @example creating a highlight object
|
12
|
+
# require 'mustermann/visualizer'
|
13
|
+
#
|
14
|
+
# pattern = Mustermann.new('/:name')
|
15
|
+
# highlight = Mustermann::Visualizer.highlight(pattern)
|
16
|
+
#
|
17
|
+
# puts highlight.to_ansi
|
18
|
+
#
|
19
|
+
# @return [Mustermann::Visualizer::Highlight] highlight object for given pattern
|
20
|
+
# @param (see Mustermann::Visualizer::Highlight#initialize)
|
21
|
+
def highlight(pattern, **options)
|
22
|
+
Highlight.new(pattern, **options)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @example creating a tree object
|
26
|
+
# require 'mustermann/visualizer'
|
27
|
+
#
|
28
|
+
# pattern = Mustermann.new('/:name')
|
29
|
+
# tree = Mustermann::Visualizer.tree(pattern)
|
30
|
+
#
|
31
|
+
# puts highlight.to_s
|
32
|
+
#
|
33
|
+
# @return [Mustermann::Visualizer::Tree] tree object for given pattern
|
34
|
+
def tree(pattern, **options)
|
35
|
+
TreeRenderer.render(pattern, **options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'hansi'
|
2
|
+
require 'mustermann'
|
3
|
+
require 'mustermann/visualizer/highlighter'
|
4
|
+
require 'mustermann/visualizer/renderer/ansi'
|
5
|
+
require 'mustermann/visualizer/renderer/hansi_template'
|
6
|
+
require 'mustermann/visualizer/renderer/html'
|
7
|
+
require 'mustermann/visualizer/renderer/sexp'
|
8
|
+
|
9
|
+
module Mustermann
|
10
|
+
module Visualizer
|
11
|
+
# Meta class for highlight objects.
|
12
|
+
# @see Mustermann::Visualizer#highlight
|
13
|
+
class Highlight
|
14
|
+
# @!visibility private
|
15
|
+
attr_reader :pattern, :theme
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
DEFAULT_THEME = Hansi::Theme.new(:solarized, {
|
19
|
+
default: :base0,
|
20
|
+
separator: :base1,
|
21
|
+
escaped: :base1,
|
22
|
+
capture: :orange,
|
23
|
+
name: :yellow,
|
24
|
+
special: :blue,
|
25
|
+
quote: :red,
|
26
|
+
illegal: :darkred
|
27
|
+
})
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
BASE_THEME = Hansi::Theme.new({
|
31
|
+
special: :default,
|
32
|
+
capture: :special,
|
33
|
+
char: :default,
|
34
|
+
expression: :capture,
|
35
|
+
composition: :special,
|
36
|
+
group: :composition,
|
37
|
+
union: :composition,
|
38
|
+
optional: :special,
|
39
|
+
root: :default,
|
40
|
+
separator: :char,
|
41
|
+
splat: :capture,
|
42
|
+
named_splat: :splat,
|
43
|
+
variable: :capture,
|
44
|
+
escaped: :char,
|
45
|
+
quote: :special,
|
46
|
+
type: :special,
|
47
|
+
illegal: :special
|
48
|
+
})
|
49
|
+
|
50
|
+
# @!visibility private
|
51
|
+
def initialize(pattern, type: nil, inspect: nil, **theme)
|
52
|
+
@pattern = Mustermann.new(pattern, type: type)
|
53
|
+
@inspect = inspect.nil? ? pattern.is_a?(Mustermann::Composite) : inspect
|
54
|
+
theme = theme.any? ? Hansi::Theme.new(theme) : DEFAULT_THEME
|
55
|
+
@theme = BASE_THEME.merge(theme)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @example
|
59
|
+
# require 'mustermann/visualizer'
|
60
|
+
#
|
61
|
+
# pattern = Mustermann.new('/:name')
|
62
|
+
# highlight = Mustermann::Visualizer.highlight(pattern)
|
63
|
+
#
|
64
|
+
# puts highlight.to_hansi_template
|
65
|
+
#
|
66
|
+
# @return [String] Hansi template representation of the pattern
|
67
|
+
def to_hansi_template(**options)
|
68
|
+
render_with(Renderer::HansiTemplate, **options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @example
|
72
|
+
# require 'mustermann/visualizer'
|
73
|
+
#
|
74
|
+
# pattern = Mustermann.new('/:name')
|
75
|
+
# highlight = Mustermann::Visualizer.highlight(pattern)
|
76
|
+
#
|
77
|
+
# puts highlight.to_ansi
|
78
|
+
#
|
79
|
+
# @return [String] ANSI colorized version of the pattern
|
80
|
+
def to_ansi(**options)
|
81
|
+
render_with(Renderer::ANSI, **options)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @example
|
85
|
+
# require 'mustermann/visualizer'
|
86
|
+
#
|
87
|
+
# pattern = Mustermann.new('/:name')
|
88
|
+
# highlight = Mustermann::Visualizer.highlight(pattern)
|
89
|
+
#
|
90
|
+
# puts highlight.to_html
|
91
|
+
#
|
92
|
+
# @return [String] HTML rendering of the pattern
|
93
|
+
def to_html(**options)
|
94
|
+
render_with(Renderer::HTML, **options)
|
95
|
+
end
|
96
|
+
|
97
|
+
# @example
|
98
|
+
# require 'mustermann/visualizer'
|
99
|
+
#
|
100
|
+
# pattern = Mustermann.new('/:name')
|
101
|
+
# highlight = Mustermann::Visualizer.highlight(pattern)
|
102
|
+
#
|
103
|
+
# puts highlight.to_sexp
|
104
|
+
#
|
105
|
+
# @return [String] s-expression like representation of the pattern
|
106
|
+
def to_sexp(**options)
|
107
|
+
render_with(Renderer::Sexp, **options)
|
108
|
+
end
|
109
|
+
|
110
|
+
# @return [Mustermann::Pattern] the pattern used to create the highlight object
|
111
|
+
def to_pattern
|
112
|
+
pattern
|
113
|
+
end
|
114
|
+
|
115
|
+
# @return [String] string representation of the pattern
|
116
|
+
def to_s
|
117
|
+
pattern.to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [String] stylesheet for HTML output from the pattern
|
121
|
+
def stylesheet(**options)
|
122
|
+
Renderer::HTML.new(self, **options).stylesheet
|
123
|
+
end
|
124
|
+
|
125
|
+
# @!visibility private
|
126
|
+
def render_with(renderer, **options)
|
127
|
+
options[:inspect] = @inspect if options[:inspect].nil?
|
128
|
+
renderer.new(self, **options).render
|
129
|
+
end
|
130
|
+
|
131
|
+
# @!visibility private
|
132
|
+
def render(renderer)
|
133
|
+
Highlighter.highlight(pattern, renderer)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'mustermann/visualizer/highlighter/ast'
|
2
|
+
require 'mustermann/visualizer/highlighter/ad_hoc'
|
3
|
+
require 'mustermann/visualizer/highlighter/composite'
|
4
|
+
require 'mustermann/visualizer/highlighter/dummy'
|
5
|
+
require 'mustermann/visualizer/highlighter/regular'
|
6
|
+
|
7
|
+
module Mustermann
|
8
|
+
module Visualizer
|
9
|
+
# @!visibility private
|
10
|
+
module Highlighter
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# @return [String] highlighted string
|
14
|
+
# @!visibility private
|
15
|
+
def highlight(pattern, renderer)
|
16
|
+
highlighter_for(pattern).highlight(pattern, renderer)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [#highlight] Highlighter for given pattern
|
20
|
+
# @!visibility private
|
21
|
+
def highlighter_for(pattern)
|
22
|
+
return pattern.highlighter if pattern.respond_to? :highlighter and pattern.highlighter
|
23
|
+
consts = constants.map { |name| const_get(name) }
|
24
|
+
highlighter = consts.detect { |c| c.respond_to? :highlight? and c.highlight? pattern }
|
25
|
+
highlighter || Dummy
|
26
|
+
end
|
27
|
+
|
28
|
+
# Used to generate highlighting rules on the fly.
|
29
|
+
# @see {Mustermann::Shell#highlighter}
|
30
|
+
# @see {Mustermann::Simple#highlighter}
|
31
|
+
# @!visibility private
|
32
|
+
def create(&block)
|
33
|
+
Class.new(AdHoc, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Visualizer
|
5
|
+
# @!visibility private
|
6
|
+
module Highlighter
|
7
|
+
# Used to generate highlighting rules on the fly.
|
8
|
+
# @see {Mustermann::Shell#highlighter}
|
9
|
+
# @see {Mustermann::Simple#highlighter}
|
10
|
+
# @!visibility private
|
11
|
+
class AdHoc
|
12
|
+
# @!visibility private
|
13
|
+
def self.highlight(pattern, renderer)
|
14
|
+
new(pattern, renderer).highlight
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!visibility private
|
18
|
+
def self.rules
|
19
|
+
@rules ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!visibility private
|
23
|
+
def self.on(regexp, type = nil, &callback)
|
24
|
+
return regexp.map { |key, value| on(key, value, &callback) } if regexp.is_a? Hash
|
25
|
+
raise ArgumentError, 'needs type or callback' unless type or callback
|
26
|
+
callback ||= proc { |matched| element(type, matched) }
|
27
|
+
regexp = Regexp.new(Regexp.escape(regexp)) unless regexp.is_a? Regexp
|
28
|
+
rules[regexp] = callback
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!visibility private
|
32
|
+
attr_reader :pattern, :renderer, :rules, :output, :scanner
|
33
|
+
def initialize(pattern, renderer)
|
34
|
+
@pattern = pattern
|
35
|
+
@renderer = renderer
|
36
|
+
@output = ""
|
37
|
+
@rules = self.class.rules
|
38
|
+
@scanner = ::StringScanner.new(pattern.to_s)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @!visibility private
|
42
|
+
def highlight(stop = /\Z/)
|
43
|
+
output << renderer.pre(:root)
|
44
|
+
until scanner.eos? or scanner.check(stop)
|
45
|
+
position = scanner.pos
|
46
|
+
apply(scanner)
|
47
|
+
read_char(scanner) if position == scanner.pos and not scanner.check(stop)
|
48
|
+
end
|
49
|
+
output << renderer.post(:root)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
def apply(scanner)
|
54
|
+
rules.each do |regexp, callback|
|
55
|
+
next unless result = scanner.scan(regexp)
|
56
|
+
instance_exec(result, &callback)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @!visibility private
|
61
|
+
def read_char(scanner)
|
62
|
+
return unless char = scanner.getch
|
63
|
+
type = char == ?/ ? :separator : :char
|
64
|
+
element(type, char)
|
65
|
+
end
|
66
|
+
|
67
|
+
# @!visibility private
|
68
|
+
def escaped(content = ?\\, char)
|
69
|
+
element(:escaped, content) { element(:escaped_char, char) }
|
70
|
+
end
|
71
|
+
|
72
|
+
# @!visibility private
|
73
|
+
def nested(type, opening, closing, *separators)
|
74
|
+
element(type, opening) do
|
75
|
+
char = nil
|
76
|
+
until char == closing or scanner.eos?
|
77
|
+
highlight(Regexp.union(closing, *separators))
|
78
|
+
char = scanner.getch
|
79
|
+
output << char if char
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!visibility private
|
85
|
+
def element(type, content = nil)
|
86
|
+
output << renderer.pre(type)
|
87
|
+
output << renderer.escape(content) if content
|
88
|
+
yield if block_given?
|
89
|
+
output << renderer.post(type)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'mustermann/ast/translator'
|
2
|
+
|
3
|
+
module Mustermann
|
4
|
+
module Visualizer
|
5
|
+
# @!visibility private
|
6
|
+
module Highlighter
|
7
|
+
# Provides highlighting for AST based patterns
|
8
|
+
# @!visibility private
|
9
|
+
class AST
|
10
|
+
Index = Struct.new(:type, :start, :stop, :payload) { undef :to_a }
|
11
|
+
Indexer = Mustermann::AST::Translator.create do
|
12
|
+
translate(:node) { |i| Index.new(type, start, stop, Array(t(payload, i)).flatten.compact) }
|
13
|
+
translate(Array) { |i| map { |e| t(e, i) } }
|
14
|
+
translate(Object) { |i| }
|
15
|
+
|
16
|
+
translate(:with_look_ahead) do |input|
|
17
|
+
[t(head, input), *t(payload, input)]
|
18
|
+
end
|
19
|
+
|
20
|
+
translate(:expression) do |input|
|
21
|
+
index = Index.new(type, start, stop, Array(t(payload, input)).compact)
|
22
|
+
index.payload.delete_if { |e| e.type == :separator }
|
23
|
+
index
|
24
|
+
end
|
25
|
+
|
26
|
+
translate(:capture) do |input|
|
27
|
+
substring = input[start, length]
|
28
|
+
if substart = substring.index(name)
|
29
|
+
substart += start
|
30
|
+
substop = substart + name.length
|
31
|
+
payload = [Index.new(:name, substart, substop, [])]
|
32
|
+
end
|
33
|
+
Index.new(type, start, stop, payload || [])
|
34
|
+
end
|
35
|
+
|
36
|
+
translate(:char) do |input|
|
37
|
+
substring = input[start, length]
|
38
|
+
if payload == substring
|
39
|
+
Index.new(type, start, stop, [])
|
40
|
+
elsif substart = substring.index(payload)
|
41
|
+
substart += start
|
42
|
+
substop = substart + payload.length
|
43
|
+
Index.new(:escaped, start, stop, [Index.new(:escaped_char, substart, substop, [])])
|
44
|
+
else
|
45
|
+
Index.new(:escaped, start, stop, [])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private_constant(:Index, :Indexer)
|
51
|
+
|
52
|
+
# @!visibility private
|
53
|
+
def self.highlight?(pattern)
|
54
|
+
pattern.respond_to? :to_ast
|
55
|
+
end
|
56
|
+
|
57
|
+
# @!visibility private
|
58
|
+
def self.highlight(pattern, renderer)
|
59
|
+
new(pattern, renderer).highlight
|
60
|
+
end
|
61
|
+
|
62
|
+
# @!visibility private
|
63
|
+
def initialize(pattern, renderer)
|
64
|
+
@ast = pattern.to_ast
|
65
|
+
@string = pattern.to_s
|
66
|
+
@renderer = renderer
|
67
|
+
end
|
68
|
+
|
69
|
+
# @!visibility private
|
70
|
+
def highlight
|
71
|
+
index = Indexer.translate(@ast, @string)
|
72
|
+
inject_literals(index)
|
73
|
+
render(index)
|
74
|
+
end
|
75
|
+
|
76
|
+
# @!visibility private
|
77
|
+
def render(index)
|
78
|
+
return @renderer.escape(@string[index.start..index.stop-1]) if index.type == :literal
|
79
|
+
payload = index.payload.map { |i| render(i) }.join
|
80
|
+
"#{ @renderer.pre(index.type) }#{ payload }#{ @renderer.post(index.type) }"
|
81
|
+
end
|
82
|
+
|
83
|
+
# @!visibility private
|
84
|
+
def inject_literals(index)
|
85
|
+
start, old_payload, index.payload = index.start, index.payload, []
|
86
|
+
old_payload.each do |element|
|
87
|
+
index.payload << literal(start, element.start) if start < element.start
|
88
|
+
index.payload << element
|
89
|
+
inject_literals(element)
|
90
|
+
start = element.stop
|
91
|
+
end
|
92
|
+
index.payload << literal(start, index.stop) if start < index.stop
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
def literal(start, stop)
|
97
|
+
Index.new(:literal, start, stop, [])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|