mustermann-contrib 1.0.0.beta2

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