mustermann 1.0.1 → 1.0.2.rc1

Sign up to get free protection for your applications and to get access to all the features.
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