mustermann 1.0.1 → 1.0.2.rc1

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