eyemask 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ require "eyemask/liquid/blockquote"
2
+ require "eyemask/liquid/indent"
3
+ require "eyemask/liquid/markdown"
4
+ require "eyemask/liquid/parse"
5
+ require "eyemask/liquid/relevel"
6
+ require "eyemask/liquid/strip"
7
+ require "eyemask/liquid/uml"
8
+ require "eyemask/liquid/highlight"
9
+ require "eyemask/liquid/note"
@@ -0,0 +1,13 @@
1
+ require 'liquid'
2
+
3
+ module Eyemask
4
+ module Liquid
5
+ module Blockquote
6
+ def blockquote(input)
7
+ input.gsub(/^/, ">")
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ Liquid::Template.register_filter(Eyemask::Liquid::Blockquote)
@@ -0,0 +1,22 @@
1
+ require 'liquid'
2
+ require 'rouge'
3
+
4
+ module Eyemask
5
+ module Liquid
6
+ module Highlight
7
+
8
+ def highlight(input, lang)
9
+ formatter = Rouge::Formatters::HTML.new(inline_theme: "github")
10
+ lexer = Rouge::Lexer.find(lang)
11
+ unless lexer.nil?
12
+ formatter.format(lexer.lex(input))
13
+ else
14
+ "<pre class=\"docstring\">#{input}</pre>"
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+
22
+ Liquid::Template.register_filter(Eyemask::Liquid::Highlight)
@@ -0,0 +1,21 @@
1
+ require 'liquid'
2
+
3
+ module Eyemask
4
+ module Liquid
5
+ class Indent < ::Liquid::Block
6
+
7
+ def initialize(tag_name, markup, tokens)
8
+ super
9
+ @indentation = markup.to_i
10
+ end
11
+
12
+ def render(context)
13
+ prefix = " " * @indentation
14
+ super.gsub(/^/, prefix)
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
21
+ Liquid::Template.register_tag('indent', Eyemask::Liquid::Indent)
@@ -0,0 +1,22 @@
1
+ require 'liquid'
2
+ require 'redcarpet'
3
+ require 'rouge/plugins/redcarpet'
4
+
5
+ module Eyemask
6
+ module Liquid
7
+ module Markdown
8
+ class HTML < Redcarpet::Render::HTML
9
+ include Rouge::Plugins::Redcarpet
10
+ include Redcarpet::Render::SmartyPants
11
+ end
12
+
13
+ MARKDOWN = Redcarpet::Markdown.new(HTML, autolink: true, tables: true, footnotes: false, fenced_code_blocks:true, no_intra_emphasis: true, superscript: true, underline: true, highlight: true)
14
+
15
+ def markdown(input)
16
+ MARKDOWN.render(input)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Liquid::Template.register_filter(Eyemask::Liquid::Markdown)
@@ -0,0 +1,25 @@
1
+ require 'liquid'
2
+
3
+ module Eyemask
4
+ module Liquid
5
+ class Note < ::Liquid::Block
6
+
7
+ MARKDOWN = Redcarpet::Markdown.new(Eyemask::Liquid::Markdown::HTML.new(hard_wrap: true), autolink: true, tables: true, footnotes: false, fenced_code_blocks:true, no_intra_emphasis: true, superscript: true, underline: true, highlight: true)
8
+
9
+ def initialize(tag_name, markup, tokens)
10
+ super
11
+ params = markup.split(" ")
12
+ @note_class = params.first
13
+ @note_data = params.drop(1).join(" ")
14
+ end
15
+
16
+ def render(context)
17
+ content = MARKDOWN.render(::Liquid::Template.parse(super).render(context.registers))
18
+ "<aside class=\"note note-#{@note_class}\" data-note=\"#{@note_data}\">#{content}</aside>"
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+
25
+ Liquid::Template.register_tag('note', Eyemask::Liquid::Note)
@@ -0,0 +1,15 @@
1
+ require 'liquid'
2
+
3
+ module Eyemask
4
+ module Liquid
5
+ module Parse
6
+
7
+ def parse(input)
8
+ ::Liquid::Template.parse(input).render(@context.registers)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
15
+ Liquid::Template.register_filter(Eyemask::Liquid::Parse)
@@ -0,0 +1,29 @@
1
+ require 'liquid'
2
+
3
+ module Eyemask
4
+ module Liquid
5
+ module RelevelFilter
6
+ def relevel(input, markup)
7
+ hash_prefix = "\#" * markup.to_i
8
+ input.gsub(/^(#+) (.*)$/, "#{hash_prefix}\\1 \\2")
9
+ end
10
+ end
11
+
12
+ class Relevel < ::Liquid::Block
13
+
14
+ def initialize(tag_name, markup, tokens)
15
+ super
16
+ @num_up = markup.to_i
17
+ end
18
+
19
+ def render(context)
20
+ hash_prefix = "\#" * @num_up
21
+ super.gsub(/^(#+) (.*)$/, "#{hash_prefix}\\1 \\2")
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+
28
+ Liquid::Template.register_filter(Eyemask::Liquid::RelevelFilter)
29
+ Liquid::Template.register_tag('relevel', Eyemask::Liquid::Relevel)
@@ -0,0 +1,13 @@
1
+ require 'liquid'
2
+
3
+ module Eyemask
4
+ module Liquid
5
+ module Strip
6
+ def strip(input)
7
+ input.strip
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ Liquid::Template.register_filter(Eyemask::Liquid::Strip)
@@ -0,0 +1,72 @@
1
+ require 'liquid'
2
+ require 'base64'
3
+
4
+ module Eyemask
5
+
6
+ module Liquid
7
+
8
+ class Uml < ::Liquid::Block
9
+
10
+ def initialize(tag_name, markup, tokens)
11
+ @tag_name = tag_name
12
+ super
13
+ end
14
+
15
+ def render(context)
16
+ "\n\n<figure><img class=\"resolution-#{resolution}\" src=\"#{get_data_uri_for_diagram(super)}\"></figure>\n\n"
17
+ end
18
+
19
+ def get_data_uri_for_diagram(diagram)
20
+ data = parse_diagram(diagram)
21
+ "data:image/png;base64,#{Base64.encode64(data)}"
22
+ end
23
+
24
+ def parse_diagram(diagram)
25
+ output = ""
26
+ IO.popen(["plantuml", "-pipe"], 'r+') do |f|
27
+ f.puts("@start#{diagram_type}")
28
+ case diagram_type
29
+ when "uml"
30
+ f.puts("skinparam backgroundColor transparent")
31
+ f.puts("skinparam shadowing false")
32
+ f.puts("skinparam dpi 300")
33
+ end
34
+ f.puts(diagram)
35
+ f.puts("@end#{diagram_type}")
36
+ f.close_write
37
+ output = f.read
38
+ end
39
+ output
40
+ end
41
+
42
+ def resolution
43
+ case diagram_type
44
+ when "uml"
45
+ "print"
46
+ else
47
+ "normal"
48
+ end
49
+ end
50
+
51
+ def diagram_type
52
+ case @tag_name
53
+ when "uml"
54
+ "uml"
55
+ when "ditaa"
56
+ "ditaa"
57
+ when "salt"
58
+ "salt"
59
+ else
60
+ "uml"
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ Liquid::Template.register_tag('uml', Eyemask::Liquid::Uml)
70
+ Liquid::Template.register_tag('ditaa', Eyemask::Liquid::Uml)
71
+ Liquid::Template.register_tag('salt', Eyemask::Liquid::Uml)
72
+ Liquid::Template.register_tag('dot', Eyemask::Liquid::Uml)
@@ -0,0 +1,3 @@
1
+ module Eyemask
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,41 @@
1
+ ---
2
+ title: {{title}}
3
+ {%if subtitle %}subtitle: {{subtitle}} {% endif %}
4
+ {%unless authors.empty %}author: {% for author in authors %}{{author}}{%unless forloop.last %}, {% endunless %}{% endfor %} {% endunless %}
5
+ ---
6
+
7
+ # Features
8
+
9
+ {% if contents == empty %}
10
+ No features have been specified.
11
+ {% endif %}
12
+
13
+ {% for feature in contents %}
14
+ ## {{feature.name}}
15
+
16
+ {% relevel 2 %}
17
+ {{feature.description | uml }}
18
+ {% endrelevel %}
19
+
20
+ {% for scenario in feature.elements %}
21
+ ### {{ scenario.name }}
22
+
23
+ {% relevel 3 %}
24
+ {{scenario.description | uml }}
25
+ {% endrelevel %}
26
+
27
+ {% for step in scenario.steps %}
28
+ - **{{step.keyword | strip}}** {{step.name}}
29
+ {% if step.doc_string %}{% indent 4 %}
30
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
31
+ {{ step.doc_string.value }}
32
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33
+ {% endindent %}{% endif %}
34
+ {% if step.rows %}{% indent 4 %}{% for row in step.rows %}
35
+ | {% for cell in row.cells %}{{ cell }} | {% endfor %}{% if forloop.first %}
36
+ |{% for i in row.cells %}---|{% endfor %}{% endif %}{% endfor %}{% endindent %}{% endif %}
37
+ {% endfor %}
38
+
39
+ {% endfor %}
40
+
41
+ {% endfor %}
@@ -0,0 +1,574 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{title}}: {{subtitle}}</title>
5
+ <link href='http://fonts.googleapis.com/css?family=Merriweather:400,300,300italic,700,400italic,700italic,900,900italic&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
6
+ <link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:200,300,400,500,600,700,900' rel='stylesheet' type='text/css'>
7
+ <style>
8
+ /***** BASELINE *****/
9
+ html {
10
+ font-size: 11pt;
11
+ }
12
+
13
+ body {
14
+ font-family: 'Merriweather', serif;
15
+ font-size: 2.4rem;
16
+ line-height: 1.6875;
17
+ }
18
+
19
+ h1,h2,h3,h4 {
20
+ font-family: 'Merriweather', serif;
21
+ margin: 0 0 16.875pt 0;
22
+ }
23
+
24
+ h1 {
25
+ font-size: 37.125pt;
26
+ font-weight: normal;
27
+ }
28
+
29
+ h2 {
30
+ font-size: 24.75pt;
31
+ font-weight: normal;
32
+ }
33
+
34
+ h3 {
35
+ font-size: 16.5pt;
36
+ font-weight: normal;
37
+ }
38
+
39
+ h4 {
40
+ font-size: 11pt;
41
+ }
42
+
43
+ title {
44
+ string-set: title content();
45
+ }
46
+
47
+ .resolution-print {
48
+ prince-image-resolution: 300dpi;
49
+ }
50
+
51
+ /***** COVER *****/
52
+
53
+ #cover {
54
+ padding-top: 33.75pt;
55
+ }
56
+
57
+ #cover .client {
58
+ border-bottom: 1pt solid #999;
59
+ margin-bottom: 0 !important;
60
+ letter-spacing: 0.2em;
61
+ font-family: 'Merriweather', serif;
62
+ font-size: 24.75pt;
63
+ font-variant: small-caps;
64
+ }
65
+
66
+ .doctitle {
67
+ font-family: 'Merriweather', serif;
68
+ font-size: 37.125pt;
69
+ font-weight: normal;
70
+ border-bottom: 1pt solid #999;
71
+ margin-top: 0 !important;
72
+ }
73
+
74
+ .version {
75
+ font-family: 'Merriweather', serif;
76
+ font-size: 16.875pt;
77
+ font-variant: small-caps;
78
+ font-weight: normal;
79
+ }
80
+
81
+ .authors::before {
82
+ display: block;
83
+ content: "Compiled by:";
84
+ font-size: 7.26pt;
85
+ font-weight: 200;
86
+ font-variant: small-caps;
87
+ letter-spacing: 0.2em;
88
+ }
89
+ .authors {
90
+ font-family: 'Merriweather', serif;
91
+ font-size: 11pt;
92
+ list-style: none;
93
+ padding: 0;
94
+ margin: 0;
95
+ }
96
+
97
+ .authors li {
98
+ padding-bottom: 2.6pt;
99
+ font-weight: 600;
100
+ }
101
+
102
+ .logo {
103
+ position: absolute;
104
+ bottom: 0;
105
+ }
106
+
107
+ /***** TABLE OF CONTENTS *****/
108
+
109
+ #frontmatter .chapter h2 {
110
+ string-set: header content();
111
+ }
112
+
113
+ .toc ol {
114
+ list-style: none;
115
+ }
116
+
117
+ .toc a {
118
+ text-decoration: none;
119
+ }
120
+
121
+ .toc a[href]::after {
122
+ content: leader(".") target-counter(attr(href), page)
123
+ }
124
+
125
+ .toc ol a[href]::before {
126
+ content: target-counter(attr(href), feature) ". ";
127
+ }
128
+
129
+ .toc ol ol a[href]::before {
130
+ content: target-counter(attr(href), feature) "." target-counter(attr(href), scenario) ". ";
131
+ }
132
+
133
+ /***** PARTS *****/
134
+
135
+ .part {
136
+ counter-increment: part;
137
+ counter-reset: feature 1 scenario;
138
+ }
139
+ .part h1 {
140
+ text-align: center;
141
+ margin-top: 88mm;
142
+ font-weight: normal;
143
+ font-variant: small-caps;
144
+ }
145
+
146
+ .part h1::before {
147
+ display: block;
148
+ margin: 0 auto;
149
+ font-size: 24.75pt;
150
+ font-weight: 200;
151
+ content: counter(part, upper-roman);
152
+ }
153
+
154
+ /***** FEATURES *****/
155
+
156
+ .feature + .feature {
157
+ counter-increment: feature;
158
+ counter-reset: scenario;
159
+ }
160
+
161
+ .feature header h2 {
162
+ page-break-after: avoid;
163
+ margin-top: 33.75pt;
164
+ font-weight: normal;
165
+ string-set: header "Feature " counter(feature) ". " content();
166
+ }
167
+
168
+ .feature h2::before {
169
+ page-break-after: avoid;
170
+ display: block;
171
+ margin: 0 auto;
172
+ font-size: 16.5pt;
173
+ font-variant: small-caps;
174
+ content: "Feature " counter(feature) "." ;
175
+ letter-spacing: 0.2em;
176
+ }
177
+
178
+ .scenario {
179
+ counter-increment: scenario;
180
+ margin-bottom: 16.875pt;
181
+ }
182
+ .scenario h3 {
183
+ page-break-after: avoid;
184
+ font-weight: normal;
185
+ }
186
+
187
+ .scenario h3::before {
188
+ page-break-after: avoid;
189
+ display: block;
190
+ margin: 0 auto;
191
+ font-size: 11pt;
192
+ font-variant: small-caps;
193
+ content: "Scenario " counter(feature) "." counter(scenario) ". ";
194
+ letter-spacing: 0.2em;
195
+ }
196
+
197
+ .steps ol {
198
+ margin-left: 0;
199
+ list-style: lower-roman;
200
+ }
201
+
202
+ .steps ol li p {
203
+ margin-top: 0 !important;
204
+ }
205
+
206
+ .steps ol li p:only-child {
207
+ margin: 0;
208
+ }
209
+
210
+ .highlight {
211
+ page-break-before: avoid;
212
+ }
213
+ .docstring {
214
+ page-break-before: avoid;
215
+ }
216
+
217
+ .steptable {
218
+ page-break-before: avoid;
219
+ }
220
+
221
+ /***** MATTER *****/
222
+
223
+ #cover {
224
+ page: blank;
225
+ }
226
+
227
+ #frontmatter {
228
+ page: frontmatter;
229
+ page-break-before: right;
230
+ }
231
+
232
+ #mainmatter {
233
+ page: auto;
234
+ counter-reset: page 1;
235
+ }
236
+
237
+ /***** SECTIONS *****/
238
+ .part {
239
+ page-break-before: right;
240
+ page: blank;
241
+ }
242
+ .feature {
243
+ page-break-before: right;
244
+ page: auto;
245
+ }
246
+
247
+ /***** SYNTAX HIGHLIGHTING *****/
248
+
249
+ .highlight table {
250
+ border: none;
251
+ margin: 0;
252
+ }
253
+
254
+ .highlight tr {
255
+ border: none;
256
+ }
257
+
258
+ .highlight pre {
259
+ background: none;
260
+ border: none;
261
+ margin: 0;
262
+ }
263
+
264
+ /***** PAGES *****/
265
+
266
+ @page {
267
+ size: a4;
268
+ margin: 33mm 23.33mm 66mm 46.66mm;
269
+ }
270
+
271
+ @page :left {
272
+ margin: 33mm 23.33mm 66mm 46.66mm;
273
+ @top-left {
274
+ font: 11pt 'Merriweather', serif;
275
+ content: string(title);
276
+ font-variant: small-caps;
277
+ vertical-align: bottom;
278
+ padding-bottom: 2em;
279
+ }
280
+
281
+ @bottom-left {
282
+ font: 11pt 'Merriweather', serif;
283
+ content: counter(page);
284
+ padding-top: 2em;
285
+ vertical-align: top;
286
+ }
287
+ }
288
+
289
+ @page :right {
290
+ margin: 33mm 46.66mm 66mm 23.33mm;
291
+ @top-right {
292
+ font: 11pt 'Merriweather', serif;
293
+ font-variant: small-caps;
294
+ content: string(header, first);
295
+ vertical-align: bottom;
296
+ padding-bottom: 2em;
297
+ }
298
+
299
+ @bottom-right {
300
+ font: 11pt 'Merriweather', serif;
301
+ content: counter(page);
302
+ text-align: right;
303
+ vertical-align: top;
304
+ padding-top: 2em;
305
+ }
306
+ }
307
+
308
+ @page frontmatter :left {
309
+ @top-left {
310
+ font: 11pt 'Merriweather', serif;
311
+ font-variant: small-caps;
312
+ content: string(title);
313
+ vertical-align: bottom;
314
+ padding-bottom: 2em;
315
+ }
316
+
317
+ @bottom-left {
318
+ font: 11pt 'Merriweather', serif;
319
+ content: counter(page, lower-roman);
320
+ padding-top: 2em;
321
+ vertical-align: top;
322
+ }
323
+ }
324
+
325
+ @page frontmatter :right {
326
+ @top-right {
327
+ font: 11pt 'Merriweather', serif;
328
+ font-variant: small-caps;
329
+ content: string(header, first);
330
+ vertical-align: bottom;
331
+ padding-bottom: 2em;
332
+ }
333
+
334
+ @bottom-right {
335
+ font: 11pt 'Merriweather', serif;
336
+ content: counter(page, lower-roman);
337
+ text-align: right;
338
+ vertical-align: top;
339
+ padding-top: 2em;
340
+ }
341
+ }
342
+
343
+ @page blank :left {
344
+ @top-left { content: normal }
345
+ @bottom-left { content: normal }
346
+ }
347
+
348
+ @page blank :right {
349
+ @top-right { content: normal }
350
+ @bottom-right { content: normal }
351
+ }
352
+
353
+ /***** OTHER *****/
354
+ /* ---- Tables ---- */
355
+
356
+ /* A clean textbook-like style with horizontal lines above and below and under
357
+ the header. Rows highlight on hover to help scanning the table on screen.
358
+ */
359
+
360
+ table
361
+ {
362
+ border-collapse: collapse;
363
+ border-spacing: 0; /* IE 6 */
364
+
365
+ border-bottom: 2pt solid #000;
366
+ border-top: 2pt solid #000; /* The caption on top will not have a bottom-border */
367
+
368
+ /* Center */
369
+ margin-left: auto;
370
+ margin-right: auto;
371
+ margin-bottom: 16.875pt;
372
+ }
373
+
374
+ thead /* Entire table header */
375
+ {
376
+ border-bottom: 1pt solid #000;
377
+ background-color: #ccc; /* Does this BG print well? */
378
+ }
379
+
380
+ tr.header /* Each header row */
381
+ {
382
+ }
383
+
384
+ tbody /* Entire table body */
385
+ {
386
+ }
387
+
388
+ /* Table body rows */
389
+
390
+ tr {
391
+ border-bottom: 0.5pt solid #000;
392
+ }
393
+ .steptable tbody tr:nth-child(2n)
394
+ {
395
+ /*background-color: #eee;*/
396
+ }
397
+
398
+
399
+ td, th /* Table cells and table header cells */
400
+ {
401
+ vertical-align: top; /* Word */
402
+ vertical-align: baseline; /* Others */
403
+ padding-left: 0.5em;
404
+ padding-right: 0.5em;
405
+ padding-top: 0.2em;
406
+ padding-bottom: 0.2em;
407
+ }
408
+
409
+ /* Code */
410
+
411
+ /* code {
412
+ display: block;
413
+ padding: 0.5em;
414
+ background-color: #ccc;
415
+ } */
416
+
417
+ pre,code,.highlight {
418
+ font-family: "Source Code Pro";
419
+ background-color: #eee;
420
+ }
421
+
422
+ pre, code
423
+ {
424
+ /* BEGIN word wrap */
425
+ /* Need all the following to word wrap instead of scroll box */
426
+ /* This will override the overflow:auto if present */
427
+ white-space: pre-wrap; /* css-3 */
428
+ white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
429
+ white-space: -pre-wrap; /* Opera 4-6 */
430
+ white-space: -o-pre-wrap; /* Opera 7 */
431
+ word-wrap: break-word; /* Internet Explorer 5.5+ */
432
+ /* END word wrap */
433
+ }
434
+
435
+ pre, .highlight /* Code blocks */
436
+ {
437
+ /* Distinguish pre blocks from other text by more than the font with a background tint. */
438
+ padding: 0.5em; /* Since we have a background color */
439
+ border-radius: 5px; /* Softens it */
440
+ /* Give it a some definition */
441
+ border: 1px solid #ccc;
442
+ /* Set it off left and right, seems to look a bit nicer when we have a background */
443
+ margin-left: 0.5em;
444
+ margin-right: 0.5em;
445
+ }
446
+
447
+ figure img {
448
+ display: block;
449
+ margin-left: auto;
450
+ margin-right: auto;
451
+ }
452
+ </style>
453
+ </head>
454
+ <body>
455
+ <section id="cover">
456
+ <p class="client">{{title}}</p>
457
+ <h1 class="doctitle">{{subtitle}}</h1>
458
+ {% unless authors.empty %}<ul class="authors">
459
+ {% for author in authors %}<li>{{author}}</li>
460
+ {% endfor %}
461
+ </ul>{% endunless %}
462
+
463
+ {% if logo %}<div class="logo">
464
+ <img alt="" src="{{logo}}">
465
+ </div>{% endif %}
466
+
467
+ </section>
468
+
469
+ <section id="frontmatter">
470
+ <section class="chapter no-toc">
471
+ <header>
472
+ <h2>Table of Features</h2>
473
+ </header>
474
+
475
+ <nav class="toc table-of-features">
476
+ <ol class="features-list">
477
+ {% for feature in contents %}
478
+ <li><a href="#{{feature.id}}">{{feature.name}}</a>
479
+ <ol class="scenarios-list">
480
+ {% for scenario in feature.elements %}
481
+ <li><a href="#{{scenario.id}}">{{scenario.name}}</a></li>
482
+ {% endfor %}
483
+ </ol>
484
+ </li>
485
+ {% endfor %}
486
+ </ol>
487
+ </nav>
488
+
489
+ </section>
490
+
491
+ <section id="mainmatter">
492
+ <section class="part" id="features">
493
+ <header>
494
+ <h1>Features</h1>
495
+ </header>
496
+
497
+ {% for feature in contents %}
498
+ <section class="feature" id="{{feature.id}}">
499
+ <header>
500
+ <h2>{{feature.name}}</h2>
501
+ </header>
502
+
503
+ <div class="description">
504
+ {{ feature.description | parse | relevel:2 | markdown }}
505
+ </div>
506
+
507
+ {% for scenario in feature.elements %}
508
+ <section class="scenario" id="{{scenario.id}}">
509
+ <header>
510
+ <h3>{{scenario.name}}</h3>
511
+ </header>
512
+
513
+ <div class="description">
514
+ {{ scenario.description | parse | relevel:3 | markdown }}
515
+ </div>
516
+
517
+ {% unless scenario.steps.empty %}
518
+ <section class="steps">
519
+ {% unless scenario.description=="" %}
520
+ <header>
521
+ <h4>Steps</h4>
522
+ </header>
523
+ {% endunless %}
524
+ <ol>
525
+ {% for step in scenario.steps %}
526
+ <li class="step {{step.keyword | strip | downcase}}">
527
+ <p>
528
+ <strong class="keyword">{{step.keyword | strip}}</strong> {{step.name}}
529
+ </p>
530
+
531
+ {% if step.doc_string %}
532
+ {{ step.doc_string.value | highlight: step.doc_string.content_type }}
533
+ {% endif %}
534
+
535
+ {% if step.rows %}
536
+ <table class="steptable">
537
+ <thead>
538
+ <tr>
539
+ {% for cell in step.rows[0].cells %}
540
+ <th>{{ cell }}</th>
541
+ {% endfor %}
542
+ </tr>
543
+ </thead>
544
+ <tbody>
545
+ {% for row in step.rows %}
546
+ {% unless forloop.first %}
547
+ <tr>
548
+ {% for cell in row.cells %}
549
+ <td>{{ cell | escape_once }}</td>
550
+ {% endfor %}
551
+ </tr>
552
+ {% endunless %}
553
+ {% endfor %}
554
+ </tbody>
555
+ </table>
556
+ {% endif %}
557
+
558
+ </li>
559
+ {% endfor %}
560
+ </ol>
561
+ </section> <!-- Steps -->
562
+ {% endunless %}
563
+
564
+ </section> <!-- Scenario -->
565
+ {% endfor %}
566
+
567
+ </section> <!-- Feature -->
568
+ {% endfor %}
569
+
570
+ </section> <!-- Part -->
571
+
572
+ </section>
573
+ </body>
574
+ </html>