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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1239 -0
  3. data/examples/highlighting.rb +35 -0
  4. data/highlighting.png +0 -0
  5. data/irb.png +0 -0
  6. data/lib/mustermann/cake.rb +18 -0
  7. data/lib/mustermann/express.rb +37 -0
  8. data/lib/mustermann/file_utils.rb +217 -0
  9. data/lib/mustermann/file_utils/glob_pattern.rb +39 -0
  10. data/lib/mustermann/fileutils.rb +1 -0
  11. data/lib/mustermann/flask.rb +198 -0
  12. data/lib/mustermann/grape.rb +35 -0
  13. data/lib/mustermann/pyramid.rb +28 -0
  14. data/lib/mustermann/rails.rb +46 -0
  15. data/lib/mustermann/shell.rb +56 -0
  16. data/lib/mustermann/simple.rb +50 -0
  17. data/lib/mustermann/string_scanner.rb +313 -0
  18. data/lib/mustermann/strscan.rb +1 -0
  19. data/lib/mustermann/template.rb +62 -0
  20. data/lib/mustermann/uri_template.rb +1 -0
  21. data/lib/mustermann/versions.rb +46 -0
  22. data/lib/mustermann/visualizer.rb +38 -0
  23. data/lib/mustermann/visualizer/highlight.rb +137 -0
  24. data/lib/mustermann/visualizer/highlighter.rb +37 -0
  25. data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
  26. data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
  27. data/lib/mustermann/visualizer/highlighter/composite.rb +45 -0
  28. data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
  29. data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
  30. data/lib/mustermann/visualizer/pattern_extension.rb +68 -0
  31. data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
  32. data/lib/mustermann/visualizer/renderer/generic.rb +46 -0
  33. data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
  34. data/lib/mustermann/visualizer/renderer/html.rb +50 -0
  35. data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
  36. data/lib/mustermann/visualizer/tree.rb +63 -0
  37. data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
  38. data/mustermann-contrib.gemspec +19 -0
  39. data/spec/cake_spec.rb +90 -0
  40. data/spec/express_spec.rb +209 -0
  41. data/spec/file_utils_spec.rb +119 -0
  42. data/spec/flask_spec.rb +361 -0
  43. data/spec/flask_subclass_spec.rb +368 -0
  44. data/spec/grape_spec.rb +747 -0
  45. data/spec/pattern_extension_spec.rb +49 -0
  46. data/spec/pyramid_spec.rb +101 -0
  47. data/spec/rails_spec.rb +647 -0
  48. data/spec/shell_spec.rb +147 -0
  49. data/spec/simple_spec.rb +268 -0
  50. data/spec/string_scanner_spec.rb +271 -0
  51. data/spec/template_spec.rb +841 -0
  52. data/spec/visualizer_spec.rb +199 -0
  53. data/theme.png +0 -0
  54. data/tree.png +0 -0
  55. 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