mustermann-visualizer 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d028b3c70fb1171e8ffd5afe880b163866e972bf
4
+ data.tar.gz: edea773ff5a6c259e378b4156c1e5c4566d50a57
5
+ SHA512:
6
+ metadata.gz: 8adb6e5e73eea9f6bf5b1550b28f74242b349e7a8400f7f5c4cb9346edeb69de6a9e540aaf1e385ad2936c01f54b8f0b23444dd814523f9a870a72b8402afa48
7
+ data.tar.gz: bbfba32e4b53c7e581de1958b30eda063faa4fc076fa31ece683f13cc5a1d84ec866ddb0ae402f8398e2b2deb0e5868e41550f5af6aa68c37433b53671b91e25
@@ -0,0 +1,196 @@
1
+ # Mustermann Pattern Visualizer
2
+
3
+ With this gem, you can visualize the internal structure of a Mustermann pattern:
4
+
5
+ * You can generate a **syntax highlighted** version of a pattern object. Both HTML/CSS based highlighting and ANSI color code based highlighting is supported.
6
+ * You can turn a pattern object into a **tree** (with ANSI color codes) representing the internal AST. This of course only works for AST based patterns.
7
+
8
+ ## Syntax Highlighting
9
+
10
+ ![](highlighting.png)
11
+
12
+ Loading `mustermann/visualizer` will automatically add `to_html` and `to_ansi` to pattern objects.
13
+
14
+ ``` ruby
15
+ require 'mustermann/visualizer'
16
+ puts Mustermann.new('/:name').to_ansi
17
+ puts Mustermann.new('/:name').to_html
18
+ ```
19
+
20
+ Alternatively, you can also create a separate `highlight` object, which allows finer grained control and more formats:
21
+
22
+ ``` ruby
23
+ require 'mustermann/visualizer'
24
+
25
+ pattern = Mustermann.new('/:name')
26
+ highlight = Mustermann::Visualizer.highlight(pattern)
27
+
28
+ puts highlight.to_ansi
29
+ ```
30
+ ### `inspect` mode
31
+
32
+ By default, the highlighted string will be a colored version of `to_s`. It is also possible to produce a colored version of `inspect`
33
+
34
+ ``` ruby
35
+ require 'mustermann/visualizer'
36
+
37
+ pattern = Mustermann.new('/:name')
38
+
39
+ # directly from the pattern
40
+ puts pattern.to_ansi(inspect: true)
41
+
42
+ # via the highlighter
43
+ highlight = Mustermann::Visualizer.highlight(pattern, inspect: true)
44
+ puts highlight.to_ansi
45
+ ```
46
+
47
+ ### Themes
48
+
49
+ ![](theme.png)
50
+
51
+ element | inherits style from | default theme | note
52
+ -------------|---------------------|---------------|-------------------------
53
+ default | | #839496 | ANSI `\e[10m` if not set
54
+ special | default | #268bd2 |
55
+ capture | special | #cb4b16 |
56
+ name | | #b58900 | always inside `capture`
57
+ char | default | |
58
+ expression | capture | | only exists in URI templates
59
+ composition | special | | meta style, does not exist directly
60
+ group | composition | |
61
+ union | composition | |
62
+ optional | special | |
63
+ root | default | | wraps the whole pattern
64
+ separator | char | #93a1a1 |
65
+ splat | capture | |
66
+ named_splat | splat | |
67
+ variable | capture | | always inside `expression`
68
+ escaped | char | #93a1a1 |
69
+ escaped_char | | | always inside `escaped`
70
+ quote | special | |
71
+ illegal | special | #8b0000 |
72
+
73
+ You can set theme any of the above elements. The default theme will only be applied if no custom theming is used.
74
+
75
+ ``` ruby
76
+ # custom theme with highlight object
77
+ highlight = Mustermann::Visualizer.highlight(pattern, special: "#08f")
78
+ puts highlight.to_ansi
79
+ ```
80
+
81
+ Themes apply both to ANSI and to HTML/CSS output. The exact ANSI code used depends on the terminal and its capabilities.
82
+
83
+ ### HTML and CSS
84
+
85
+ By default, the syntax elements will be translated into `span` tags with `style` attributes.
86
+
87
+ ``` ruby
88
+ Mustermann.new('/:name').to_html
89
+ ```
90
+
91
+ ``` html
92
+ <span style="color: #839496;"><span style="color: #93a1a1;">/</span><span style="color: #cb4b16;">:<span style="color: #b58900;">name</span></span></span></span>
93
+ ```
94
+
95
+ You can also set the `css` option to `true` to make it include a stylesheet instead.
96
+
97
+ ``` ruby
98
+ Mustermann.new('/:name').to_html(css: true)
99
+ ```
100
+
101
+ ``` html
102
+ <span class="mustermann_pattern"><style type="text/css">
103
+ .mustermann_pattern .mustermann_name {
104
+ color: #b58900;
105
+ }
106
+ /* ... etc ... */
107
+ </style><span class="mustermann_root"><span class="mustermann_separator">/</span><span class="mustermann_capture">:<span class="mustermann_name">name</span></span></span></span>
108
+ ```
109
+
110
+ Or you can set it to `false`, which will omit `style` attributes, but include `class` attributes.
111
+
112
+ ``` html
113
+ <span class="mustermann_pattern"><span class="mustermann_root"><span class="mustermann_separator">/</span><span class="mustermann_capture">:<span class="mustermann_name">name</span></span></span></span>
114
+ ```
115
+
116
+ It is possible to change the class prefix and the tag used.
117
+
118
+ ``` ruby
119
+ Mustermann.new('/:name').to_html(css: false, class_prefix: "mm_", tag: "tt")
120
+ ```
121
+
122
+ ``` html
123
+ <tt class="mm_pattern"><tt class="mm_root"><tt class="mm_separator">/</tt><tt class="mm_capture">:<tt class="mm_name">name</tt></tt></tt></tt>
124
+ ```
125
+
126
+ If you create a highlight object, you can ask it for its `stylesheet`.
127
+
128
+ ``` erb
129
+ <% highlight = Mustermann::Visualizer.highlight("/:name") %>
130
+
131
+ <html>
132
+ <head>
133
+ <style type="text/css">
134
+ <%= highlight.stylesheet %>
135
+ </style>
136
+ </head>
137
+ <body>
138
+ <%= highlight.to_html(css: false) %>
139
+ </body>
140
+ </html>
141
+ ```
142
+
143
+
144
+ ### Other formats
145
+
146
+ If you create a highlight object, you have two other formats available: Hansi template strings and s-expression like strings. These might be useful if you want to check how a theme will be applied or as intermediate format for highlighting by other means.
147
+
148
+ ``` ruby
149
+ require 'mustermann/visualizer'
150
+ highlight = Mustermann::Visualizer.highlight("/:page")
151
+ puts highlight.to_hansi_template
152
+ puts highlight.to_sexp
153
+ ```
154
+
155
+ **Hansi template strings** wrap elements in tags that are similar to XML tags (though they are not, entity encoding and attributes are not supported, escaping works with a slash, so an escaped `>` would be `\>`, not `&gt;`).
156
+
157
+ ``` xml
158
+ <pattern><root><separator>/</separator><capture>:<name>page</name></capture></root></pattern>
159
+ ```
160
+
161
+ The **s-expression like syntax** looks as follows:
162
+
163
+ ```
164
+ (root (separator /) (capture : (name page)))
165
+ ```
166
+
167
+ * An expression is enclosed by parens and contains elements separated by spaces. The first element in the expression type (corresponding to themeable elements). These are simple strings. The other elements are either expressions, simple strings or full strings.
168
+ * Simple strings do not contain spaces, parens, single or double quotes or any character that needs to be escaped.
169
+ * Full strings are Ruby strings enclosed by double quotes.
170
+ * Spaces before or after parens are optional.
171
+
172
+ ### IRB/Pry integration
173
+
174
+ When `mustermann` is being loaded from within an IRB or Pry session, it will automatically load `mustermann/visualizer` too, if possible.
175
+ When displayed as result, it will be highlighted.
176
+
177
+ ![](irb.png)
178
+
179
+ In Pry, this will even work when nested inside other objects (like as element on an array).
180
+
181
+ ## Tree Rendering
182
+
183
+ ![](tree.png)
184
+
185
+ Loading `mustermann/visualizer` will automatically add `to_tree` to pattern objects.
186
+
187
+ ``` ruby
188
+ require 'mustermann/visualizer'
189
+ puts Mustermann.new("/:page(.:ext)?/*action").to_tree
190
+ ```
191
+
192
+ For patterns not based on an AST (shell, simple, regexp), it will print out a single line:
193
+
194
+ pattern (not AST based) "/example"
195
+
196
+ It will display a tree for identity patterns. While these are not based on an AST internally, Mustermann supports generating an AST for these patterns.
@@ -0,0 +1,27 @@
1
+ require 'bundler/setup'
2
+ require 'mustermann/visualizer'
3
+
4
+ def self.example(type, *patterns)
5
+ print Hansi.render(:bold, " #{type}: ".ljust(14))
6
+ patterns.each do |pattern|
7
+ pattern = Mustermann.new(pattern, type: type)
8
+ space_after = pattern.to_s.size > 24 ? " " : " " * (25 - pattern.to_s.size)
9
+ highlight = Mustermann::Visualizer.highlight(pattern)
10
+ print highlight.to_ansi + space_after
11
+ end
12
+ puts
13
+ end
14
+
15
+ puts
16
+ example(:cake, '/:prefix/**')
17
+ example(:express, '/:prefix+/:id(\d+)', '/:page/:slug+')
18
+ example(:flask, '/<prefix>/<int:id>', '/user/<int(min=0):id>')
19
+ example(:identity, '/image.png')
20
+ example(:pyramid, '/{prefix:.*}/{id}', '/{page}/*slug')
21
+ example(:rails, '/:slug(.:ext)')
22
+ example(:regexp, '/(?<slug>[^/]+)', '/(?:page|user)/(\d+)')
23
+ example(:shell, '/**/*', '/\{a,b\}/{a,b}')
24
+ example(:simple, '/:page/*slug')
25
+ example(:sinatra, '/:page/*slug', '/users/{id}?')
26
+ example(:template, '/{+pre}/{page}{?q,p}', '/users/{id}?')
27
+ puts
Binary file
data/irb.png ADDED
Binary file
@@ -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,136 @@
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: :char,
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
+ illegal: :special
47
+ })
48
+
49
+ # @!visibility private
50
+ def initialize(pattern, type: nil, inspect: false, **theme)
51
+ @pattern = Mustermann.new(pattern, type: type)
52
+ @inspect = inspect
53
+ theme = theme.any? ? Hansi::Theme.new(theme) : DEFAULT_THEME
54
+ @theme = BASE_THEME.merge(theme)
55
+ end
56
+
57
+ # @example
58
+ # require 'mustermann/visualizer'
59
+ #
60
+ # pattern = Mustermann.new('/:name')
61
+ # highlight = Mustermann::Visualizer.highlight(pattern)
62
+ #
63
+ # puts highlight.to_hansi_template
64
+ #
65
+ # @return [String] Hansi template representation of the pattern
66
+ def to_hansi_template(**options)
67
+ render_with(Renderer::HansiTemplate, **options)
68
+ end
69
+
70
+ # @example
71
+ # require 'mustermann/visualizer'
72
+ #
73
+ # pattern = Mustermann.new('/:name')
74
+ # highlight = Mustermann::Visualizer.highlight(pattern)
75
+ #
76
+ # puts highlight.to_ansi
77
+ #
78
+ # @return [String] ANSI colorized version of the pattern
79
+ def to_ansi(**options)
80
+ render_with(Renderer::ANSI, **options)
81
+ end
82
+
83
+ # @example
84
+ # require 'mustermann/visualizer'
85
+ #
86
+ # pattern = Mustermann.new('/:name')
87
+ # highlight = Mustermann::Visualizer.highlight(pattern)
88
+ #
89
+ # puts highlight.to_html
90
+ #
91
+ # @return [String] HTML rendering of the pattern
92
+ def to_html(**options)
93
+ render_with(Renderer::HTML, **options)
94
+ end
95
+
96
+ # @example
97
+ # require 'mustermann/visualizer'
98
+ #
99
+ # pattern = Mustermann.new('/:name')
100
+ # highlight = Mustermann::Visualizer.highlight(pattern)
101
+ #
102
+ # puts highlight.to_sexp
103
+ #
104
+ # @return [String] s-expression like representation of the pattern
105
+ def to_sexp(**options)
106
+ render_with(Renderer::Sexp, **options)
107
+ end
108
+
109
+ # @return [Mustermann::Pattern] the pattern used to create the highlight object
110
+ def to_pattern
111
+ pattern
112
+ end
113
+
114
+ # @return [String] string representation of the pattern
115
+ def to_s
116
+ pattern.to_s
117
+ end
118
+
119
+ # @return [String] stylesheet for HTML output from the pattern
120
+ def stylesheet(**options)
121
+ Renderer::HTML.new(self, **options).stylesheet
122
+ end
123
+
124
+ # @!visibility private
125
+ def render_with(renderer, **options)
126
+ options[:inspect] = @inspect if options[:inspect].nil?
127
+ renderer.new(self, **options).render
128
+ end
129
+
130
+ # @!visibility private
131
+ def render(renderer)
132
+ Highlighter.highlight(pattern, renderer)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,36 @@
1
+ require 'mustermann/visualizer/highlighter/ast'
2
+ require 'mustermann/visualizer/highlighter/ad_hoc'
3
+ require 'mustermann/visualizer/highlighter/dummy'
4
+ require 'mustermann/visualizer/highlighter/regular'
5
+
6
+ module Mustermann
7
+ module Visualizer
8
+ # @!visibility private
9
+ module Highlighter
10
+ extend self
11
+
12
+ # @return [String] highlighted string
13
+ # @!visibility private
14
+ def highlight(pattern, renderer)
15
+ highlighter_for(pattern).highlight(pattern, renderer)
16
+ end
17
+
18
+ # @return [#highlight] Highlighter for given pattern
19
+ # @!visibility private
20
+ def highlighter_for(pattern)
21
+ return pattern.highlighter if pattern.respond_to? :highlighter and pattern.highlighter
22
+ consts = constants.map { |name| const_get(name) }
23
+ highlighter = consts.detect { |c| c.respond_to? :highlight? and c.highlight? pattern }
24
+ highlighter || Dummy
25
+ end
26
+
27
+ # Used to generate highlighting rules on the fly.
28
+ # @see {Mustermann::Shell#highlighter}
29
+ # @see {Mustermann::Simple#highlighter}
30
+ # @!visibility private
31
+ def create(&block)
32
+ Class.new(AdHoc, &block)
33
+ end
34
+ end
35
+ end
36
+ end