mustermann-visualizer 0.4.0
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.
- checksums.yaml +7 -0
- data/README.md +196 -0
- data/examples/highlighting.rb +27 -0
- data/highlighting.png +0 -0
- data/irb.png +0 -0
- data/lib/mustermann/visualizer.rb +38 -0
- data/lib/mustermann/visualizer/highlight.rb +136 -0
- data/lib/mustermann/visualizer/highlighter.rb +36 -0
- data/lib/mustermann/visualizer/highlighter/ad_hoc.rb +94 -0
- data/lib/mustermann/visualizer/highlighter/ast.rb +102 -0
- data/lib/mustermann/visualizer/highlighter/dummy.rb +18 -0
- data/lib/mustermann/visualizer/highlighter/regular.rb +104 -0
- data/lib/mustermann/visualizer/pattern_extension.rb +66 -0
- data/lib/mustermann/visualizer/renderer/ansi.rb +23 -0
- data/lib/mustermann/visualizer/renderer/generic.rb +49 -0
- data/lib/mustermann/visualizer/renderer/hansi_template.rb +34 -0
- data/lib/mustermann/visualizer/renderer/html.rb +50 -0
- data/lib/mustermann/visualizer/renderer/sexp.rb +37 -0
- data/lib/mustermann/visualizer/tree.rb +63 -0
- data/lib/mustermann/visualizer/tree_renderer.rb +78 -0
- data/mustermann-visualizer.gemspec +19 -0
- data/spec/pattern_extension_spec.rb +48 -0
- data/spec/visualizer_spec.rb +179 -0
- data/theme.png +0 -0
- data/tree.png +0 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -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
|
data/README.md
ADDED
@@ -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
|
+

|
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
|
+

|
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 `>`).
|
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
|
+

|
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
|
+

|
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
|
data/highlighting.png
ADDED
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
|