daddy 0.0.3 → 0.0.4

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.
@@ -0,0 +1,70 @@
1
+ #menu {
2
+ width: 120px;
3
+ float: left;
4
+ background: #FFFFFF;
5
+ color: #000000;
6
+ }
7
+
8
+ #menu ol {
9
+ margin: 0;
10
+ padding: 0;
11
+ }
12
+
13
+ #menu ol li.sprint {
14
+ margin: 0;
15
+ padding: 0 5px;
16
+ border-left: 5px solid #65c400;
17
+ border-bottom: 1px solid #65c400;
18
+ line-height: 40px;
19
+ vertical-align: middle;
20
+ font-size: 1.2em;
21
+ font-weight: bold;
22
+ }
23
+
24
+ #menu ol li.diary {
25
+ margin: 0;
26
+ padding: 0 20px 0 40px;
27
+ border-left: 5px solid #65c400;
28
+ border-bottom: 1px solid #65c400;
29
+ line-height: 40px;
30
+ vertical-align: middle;
31
+ font-size: 1.2em;
32
+ font-weight: bold;
33
+ }
34
+
35
+ .contents {
36
+ margin-left: 120px;
37
+ }
38
+
39
+ .cucumber .scenario h3, td .scenario h3, th .scenario h3, .background h3 {
40
+ font-size: 11px;
41
+ padding: 3px;
42
+ margin: 0;
43
+ background: #FFFFFF;
44
+ color: #000000;
45
+ font-weight: bold;
46
+ }
47
+
48
+ .cucumber ol li.passed, td ol li.passed, th ol li.passed {
49
+ border-left: 5px solid #65c400;
50
+ border-bottom: 1px solid #65c400;
51
+ background: #FFFFFF;
52
+ color: #000000;
53
+ }
54
+
55
+ .step_contents {
56
+ display: none;
57
+ background: #FFFFFF;
58
+ border: solid 1px #999999;
59
+ margin-left: 10px;
60
+ padding: 10px;
61
+ }
62
+
63
+ div.scenario table {
64
+ margin: 10px 0;
65
+ }
66
+
67
+ div.scenario td {
68
+ border: solid 1px #999999;
69
+ padding: 5px;
70
+ }
@@ -0,0 +1,745 @@
1
+ # coding: UTF-8
2
+
3
+ require 'erb'
4
+ require 'cucumber/formatter/ordered_xml_markup'
5
+ require 'cucumber/formatter/duration'
6
+ require 'cucumber/formatter/io'
7
+
8
+ module Daddy
9
+ module Formatter
10
+ class Html
11
+ include ERB::Util # for the #h method
12
+ include ::Cucumber::Formatter::Duration
13
+ include ::Cucumber::Formatter::Io
14
+
15
+ def initialize(runtime, path_or_io, options)
16
+ @path_or_io = path_or_io
17
+ @io = ensure_io(path_or_io, "html")
18
+ @runtime = runtime
19
+ @options = options
20
+ @buffer = {}
21
+ @builder = create_builder(@io)
22
+ @feature_number = 0
23
+ @scenario_number = 0
24
+ @step_number = 0
25
+ @header_red = nil
26
+ @delayed_messages = []
27
+ @img_id = 0
28
+ end
29
+
30
+ def embed(src, mime_type, label)
31
+ case(mime_type)
32
+ when /^image\/(png|gif|jpg|jpeg)/
33
+ embed_image(src, label)
34
+ end
35
+ end
36
+
37
+ def embed_image(src, label)
38
+ id = "img_#{@img_id}"
39
+ @img_id += 1
40
+ @builder.span(:class => 'embed') do |pre|
41
+ pre << %{<a href="" onclick="img=document.getElementById('#{id}'); img.style.display = (img.style.display == 'none' ? 'block' : 'none');return false">#{label}</a><br>&nbsp;
42
+ <img id="#{id}" style="display: none" src="#{src}"/>}
43
+ end
44
+ end
45
+
46
+ def before_features(features)
47
+ @step_count = get_step_count(features)
48
+
49
+ @builder.declare!(:DOCTYPE, :html)
50
+ @builder << '<html>'
51
+ @builder << '<head>'
52
+ @builder.meta('http-equiv' => 'Content-Type', :content => 'text/html;charset=utf-8')
53
+ @builder.title 'Daddy'
54
+ inline_css
55
+ inline_js
56
+ @builder << '</head>'
57
+ @builder << '<body>'
58
+ @builder << "<!-- Step count #{@step_count}-->"
59
+ @builder << '<div class="cucumber">'
60
+ @builder.div(:id => 'cucumber-header') do
61
+ @builder.div(:id => 'label') do
62
+ @builder.h1('Daddy')
63
+ end
64
+ @builder.div(:id => 'summary') do
65
+ @builder.p('',:id => 'totals')
66
+ @builder.p('',:id => 'duration')
67
+ @builder.div(:id => 'expand-collapse') do
68
+ @builder.p('すべて開く', :id => 'expander')
69
+ @builder.p('すべて閉じる', :id => 'collapser')
70
+ end
71
+ end
72
+ end
73
+
74
+ before_menu
75
+ end
76
+
77
+ def before_menu
78
+ if ENV['PUBLISH']
79
+ @builder << "<div>"
80
+
81
+ @builder.div(:id => 'menu') do
82
+ @builder << make_menu_for_publish
83
+ end
84
+
85
+ @builder << "<div class='contents'>"
86
+ end
87
+ end
88
+
89
+ def after_menu
90
+ if ENV['PUBLISH']
91
+ @builder << '</div>'
92
+ @builder << '</div>'
93
+ end
94
+ end
95
+
96
+ def make_menu_for_publish
97
+ menu = 'tmp/menu.html'
98
+ system("erb -T - #{File.dirname(__FILE__)}/menu.html.erb > #{menu}")
99
+ File.readlines(menu).join
100
+ end
101
+
102
+ def after_features(features)
103
+ after_menu
104
+ print_stats(features)
105
+ @builder << '</div>'
106
+ @builder << '</body>'
107
+ @builder << '</html>'
108
+ end
109
+
110
+ def before_feature(feature)
111
+ @feature = feature
112
+ @exceptions = []
113
+ @builder << '<div class="feature">'
114
+ end
115
+
116
+ def after_feature(feature)
117
+ @builder << '</div>'
118
+ end
119
+
120
+ def before_comment(comment)
121
+ return if comment == '# language: ja'
122
+ @builder << '<pre class="comment">'
123
+ end
124
+
125
+ def after_comment(comment)
126
+ return if comment == '# language: ja'
127
+ @builder << '</pre>'
128
+ end
129
+
130
+ def comment_line(comment_line)
131
+ return if comment_line == '# language: ja'
132
+ @builder.text!(comment_line)
133
+ @builder.br
134
+ end
135
+
136
+ def after_tags(tags)
137
+ @tag_spacer = nil
138
+ end
139
+
140
+ def tag_name(tag_name)
141
+ @builder.text!(@tag_spacer) if @tag_spacer
142
+ @tag_spacer = ' '
143
+ @builder.span(tag_name, :class => 'tag')
144
+ end
145
+
146
+ def feature_name(keyword, name)
147
+ title = @feature.file.split('/').last.gsub(/\.feature/, '')
148
+
149
+ @builder.h2 do |h2|
150
+ @builder.span(title, :class => 'val')
151
+ end
152
+
153
+ lines = name.split(/\r?\n/)
154
+ unless lines.empty?
155
+ @builder.p(:class => 'narrative') do
156
+ lines.each do |line|
157
+ @builder.text!(line.strip)
158
+ @builder.br
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ def before_background(background)
165
+ @in_background = true
166
+ @builder << '<div class="background">'
167
+ end
168
+
169
+ def after_background(background)
170
+ @in_background = nil
171
+ @builder << '</div>'
172
+ end
173
+
174
+ def background_name(keyword, name, file_colon_line, source_indent)
175
+ @listing_background = true
176
+ @builder.h3(:id => "background_#{@scenario_number}") do |h3|
177
+ @builder.span(keyword, :class => 'keyword')
178
+ @builder.text!(' ')
179
+ @builder.span(name, :class => 'val')
180
+ end
181
+ end
182
+
183
+ def before_feature_element(feature_element)
184
+ @scenario_number+=1
185
+ @scenario_red = false
186
+ css_class = {
187
+ ::Cucumber::Ast::Scenario => 'scenario',
188
+ ::Cucumber::Ast::ScenarioOutline => 'scenario outline'
189
+ }[feature_element.class]
190
+ @builder << "<div class='#{css_class}'>"
191
+ end
192
+
193
+ def after_feature_element(feature_element)
194
+ @builder << '</div>'
195
+ @open_step_list = true
196
+ end
197
+
198
+ def scenario_name(keyword, name, file_colon_line, source_indent)
199
+ @step_number_in_scenario = 0
200
+
201
+ @builder.span(:class => 'scenario_file') do
202
+ @builder << file_colon_line
203
+ end
204
+ @listing_background = false
205
+ @builder.h3(:id => "scenario_#{@scenario_number}") do
206
+ @builder.span(keyword + ':', :class => 'keyword')
207
+ @builder.text!(' ')
208
+ @builder.span(name, :class => 'val')
209
+ end
210
+ end
211
+
212
+ def before_outline_table(outline_table)
213
+ @outline_row = 0
214
+ @builder << '<table>'
215
+ end
216
+
217
+ def after_outline_table(outline_table)
218
+ @builder << '</table>'
219
+ @outline_row = nil
220
+ end
221
+
222
+ def before_examples(examples)
223
+ @builder << '<div class="examples">'
224
+ end
225
+
226
+ def after_examples(examples)
227
+ @builder << '</div>'
228
+ end
229
+
230
+ def examples_name(keyword, name)
231
+ @builder.h4 do
232
+ @builder.span(keyword, :class => 'keyword')
233
+ @builder.text!(' ')
234
+ @builder.span(name, :class => 'val')
235
+ end
236
+ end
237
+
238
+ def before_steps(steps)
239
+ @step_count_in_scenario = steps.count
240
+ @builder << '<ol>'
241
+ end
242
+
243
+ def after_steps(steps)
244
+ @builder << '</ol>'
245
+ end
246
+
247
+ def before_step(step)
248
+ @step_id = step.dom_id
249
+ @step_number += 1
250
+ @step = step
251
+ end
252
+
253
+ def after_step(step)
254
+ move_progress
255
+ end
256
+
257
+ def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
258
+ @step_match = step_match
259
+ @hide_this_step = false
260
+ if exception
261
+ if @exceptions.include?(exception)
262
+ @hide_this_step = true
263
+ return
264
+ end
265
+ @exceptions << exception
266
+ end
267
+ if status != :failed && @in_background ^ background
268
+ @hide_this_step = true
269
+ return
270
+ end
271
+ @status = status
272
+ return if @hide_this_step
273
+ set_scenario_color(status)
274
+ @builder << "<li id='#{@step_id}' class='step #{status}'>"
275
+ end
276
+
277
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
278
+ return if @hide_this_step
279
+ # print snippet for undefined steps
280
+ if status == :undefined
281
+ keyword = @step.actual_keyword if @step.respond_to?(:actual_keyword)
282
+ step_multiline_class = @step.multiline_arg ? @step.multiline_arg.class : nil
283
+ @builder.pre do |pre|
284
+ pre << @runtime.snippet_text(keyword,step_match.instance_variable_get("@name") || '',step_multiline_class)
285
+ end
286
+ end
287
+ @builder << '</li>'
288
+
289
+ unless status == :undefined
290
+ step_file = step_match.file_colon_line
291
+ step_contents = "<div class=\"step_contents\"><pre>"
292
+ step_file.gsub(/^([^:]*\.rb):(\d*)/) do
293
+ line_index = $2.to_i - 1
294
+ File.readlines(File.expand_path($1.force_encoding('UTF-8')))[line_index..-1].each do |line|
295
+ step_contents << line
296
+ break if line.chop == 'end' or line.chop.start_with?('end ')
297
+ end
298
+ end
299
+ step_contents << "</pre></div>"
300
+ @builder << step_contents
301
+ end
302
+
303
+ print_messages
304
+ end
305
+
306
+ def step_name(keyword, step_match, status, source_indent, background, file_colon_line)
307
+ background_in_scenario = background && !@listing_background
308
+ @skip_step = background_in_scenario
309
+
310
+ unless @skip_step
311
+ build_step(keyword, step_match, status)
312
+ end
313
+ end
314
+
315
+ def exception(exception, status)
316
+ build_exception_detail(exception)
317
+ end
318
+
319
+ def extra_failure_content(file_colon_line)
320
+ @snippet_extractor ||= SnippetExtractor.new
321
+ "<pre class=\"ruby\"><code>#{@snippet_extractor.snippet(file_colon_line)}</code></pre>"
322
+ end
323
+
324
+ def before_multiline_arg(multiline_arg)
325
+ return if @hide_this_step || @skip_step
326
+ if ::Cucumber::Ast::Table === multiline_arg
327
+ @builder << '<table>'
328
+ end
329
+ end
330
+
331
+ def after_multiline_arg(multiline_arg)
332
+ return if @hide_this_step || @skip_step
333
+ if ::Cucumber::Ast::Table === multiline_arg
334
+ @builder << '</table>'
335
+ end
336
+ end
337
+
338
+ def doc_string(string)
339
+ return if @hide_this_step
340
+ @builder.pre(:class => 'val') do |pre|
341
+ @builder << h(string).gsub("\n", '&#x000A;')
342
+ end
343
+ end
344
+
345
+
346
+ def before_table_row(table_row)
347
+ @row_id = table_row.dom_id
348
+ @col_index = 0
349
+ return if @hide_this_step
350
+ @builder << "<tr class='step' id='#{@row_id}'>"
351
+ end
352
+
353
+ def after_table_row(table_row)
354
+ return if @hide_this_step
355
+ print_table_row_messages
356
+ @builder << '</tr>'
357
+ if table_row.exception
358
+ @builder.tr do
359
+ @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
360
+ @builder.pre do |pre|
361
+ pre << h(format_exception(table_row.exception))
362
+ end
363
+ end
364
+ end
365
+ set_scenario_color_failed
366
+ end
367
+ if @outline_row
368
+ @outline_row += 1
369
+ end
370
+ @step_number += 1
371
+ move_progress
372
+ end
373
+
374
+ def table_cell_value(value, status)
375
+ return if @hide_this_step
376
+
377
+ @cell_type = @outline_row == 0 ? :th : :td
378
+ attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'step'}
379
+ attributes[:class] += " #{status}" if status
380
+ build_cell(@cell_type, value, attributes)
381
+ set_scenario_color(status)
382
+ @col_index += 1
383
+ end
384
+
385
+ def puts(message)
386
+ @delayed_messages << message
387
+ #@builder.pre(message, :class => 'message')
388
+ end
389
+
390
+ def print_messages
391
+ return if @delayed_messages.empty?
392
+
393
+ #@builder.ol do
394
+ @delayed_messages.each_with_index do |ann, i|
395
+ @builder.li(:id => "#{@step_number}_#{i}", :class => 'step message') do
396
+ @builder << ann
397
+ end
398
+ end
399
+ @builder.script do |script|
400
+ script << "$(function() {"
401
+ script << " $('##{@step_id}').css('cursor', 'pointer').click(function() {"
402
+ script << " $(this).nextAll('li[id^=\"#{@step_number}_\"]').toggle(250);"
403
+ script << " });"
404
+ script << "});"
405
+ end
406
+ #end
407
+ empty_messages
408
+ end
409
+
410
+ def print_table_row_messages
411
+ end
412
+
413
+ def empty_messages
414
+ @delayed_messages = []
415
+ end
416
+
417
+ protected
418
+
419
+ def build_exception_detail(exception)
420
+ backtrace = Array.new
421
+ @builder.div(:class => 'message') do
422
+ message = exception.message
423
+ if defined?(RAILS_ROOT) && message.include?('Exception caught')
424
+ matches = message.match(/Showing <i>(.+)<\/i>(?:.+) #(\d+)/)
425
+ backtrace += ["#{RAILS_ROOT}/#{matches[1]}:#{matches[2]}"] if matches
426
+ matches = message.match(/<code>([^(\/)]+)<\//m)
427
+ message = matches ? matches[1] : ""
428
+ end
429
+
430
+ unless exception.instance_of?(RuntimeError)
431
+ message = "#{message} (#{exception.class})"
432
+ end
433
+
434
+ @builder.pre do
435
+ @builder.text!(message)
436
+ end
437
+ end
438
+ @builder.div(:class => 'backtrace') do
439
+ @builder.pre do
440
+ backtrace = exception.backtrace
441
+ backtrace.delete_if { |x| x =~ /\/gems\/(cucumber|rspec)/ }
442
+ @builder << backtrace_line(backtrace.join("\n"))
443
+ end
444
+ end
445
+ extra = extra_failure_content(backtrace)
446
+ @builder << extra unless extra == ""
447
+ end
448
+
449
+ def set_scenario_color(status)
450
+ if status == :undefined or status == :pending
451
+ set_scenario_color_pending
452
+ end
453
+ if status == :failed
454
+ set_scenario_color_failed
455
+ end
456
+ end
457
+
458
+ def set_scenario_color_failed
459
+ @builder.script do
460
+ unless @header_red
461
+ @builder.text!("makeRed('cucumber-header');")
462
+ @builder.text!("makeMenuRed();")
463
+ @header_red = true
464
+ end
465
+ @builder.text!("makeRed('scenario_#{@scenario_number}');") unless @scenario_red
466
+ @scenario_red = true
467
+ end
468
+ end
469
+
470
+ def set_scenario_color_pending
471
+ @builder.script do
472
+ unless @header_red
473
+ @builder.text!("makeYellow('cucumber-header');")
474
+ @builder.text!("makeMenuYellow();")
475
+ end
476
+ @builder.text!("makeYellow('scenario_#{@scenario_number}');") unless @scenario_red
477
+ end
478
+ end
479
+
480
+ def get_step_count(features)
481
+ count = 0
482
+ features = features.instance_variable_get("@features")
483
+ features.each do |feature|
484
+ #get background steps
485
+ if feature.instance_variable_get("@background")
486
+ background = feature.instance_variable_get("@background")
487
+ background.init
488
+ background_steps = background.instance_variable_get("@steps").instance_variable_get("@steps")
489
+ count += background_steps.size
490
+ end
491
+ #get scenarios
492
+ feature.instance_variable_get("@feature_elements").each do |scenario|
493
+ scenario.init
494
+ #get steps
495
+ steps = scenario.instance_variable_get("@steps").instance_variable_get("@steps")
496
+ count += steps.size
497
+
498
+ #get example table
499
+ examples = scenario.instance_variable_get("@examples_array")
500
+ unless examples.nil?
501
+ examples.each do |example|
502
+ example_matrix = example.instance_variable_get("@outline_table").instance_variable_get("@cell_matrix")
503
+ count += example_matrix.size
504
+ end
505
+ end
506
+
507
+ #get multiline step tables
508
+ steps.each do |step|
509
+ multi_arg = step.instance_variable_get("@multiline_arg")
510
+ next if multi_arg.nil?
511
+ matrix = multi_arg.instance_variable_get("@cell_matrix")
512
+ count += matrix.size unless matrix.nil?
513
+ end
514
+ end
515
+ end
516
+ return count
517
+ end
518
+
519
+ def build_step(keyword, step_match, status)
520
+ @step_number_in_scenario += 1
521
+ formatted_step_number = sprintf("%0#{@step_count_in_scenario.to_s.size}d", @step_number_in_scenario)
522
+
523
+ step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
524
+ @builder.div(:class => 'step_name') do |div|
525
+ @builder.span("#{formatted_step_number}. ", :class => 'keyword')
526
+ @builder.span(:class => 'step val') do |name|
527
+ name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
528
+ end
529
+ end
530
+
531
+ step_file = step_match.file_colon_line
532
+ step_file.gsub(/^([^:]*\.rb):(\d*)/) do
533
+ if index = $1.index('lib/daddy/cucumber/step_definitions/')
534
+ step_file = "daddy: " + $1[index..-1]
535
+ end
536
+
537
+ step_file = "<span id=\"step_file_#{@step_number}\">#{step_file}:#{$2}</span>"
538
+ end
539
+
540
+ @builder.div(:class => 'step_file') do |div|
541
+ @builder.span do
542
+ @builder << step_file
543
+ @builder.script do |script|
544
+ script << "$(function() {"
545
+ script << " $('#step_file_#{@step_number}').css('cursor', 'pointer').click(function(event) {"
546
+ script << " $(this).closest('li').next('.step_contents').toggle(250);"
547
+ script << " event.stopImmediatePropagation();"
548
+ script << " });"
549
+ script << "});"
550
+ end
551
+ end
552
+ end
553
+ end
554
+
555
+ def build_cell(cell_type, value, attributes)
556
+ @builder.__send__(cell_type, attributes) do
557
+ @builder.div do
558
+ @builder.span(value,:class => 'step param')
559
+ end
560
+ end
561
+ end
562
+
563
+ def inline_css
564
+ @builder.style do
565
+ @builder << File.read(File.dirname(__FILE__) + '/cucumber.css')
566
+ @builder << File.read(File.dirname(__FILE__) + '/daddy.css')
567
+ end
568
+ end
569
+
570
+ def inline_js
571
+ @builder.script do
572
+ @builder << inline_jquery
573
+ @builder << inline_js_content
574
+ if ENV['EXPAND']
575
+ @builder << %w{
576
+ $(document).ready(function() {
577
+ $(SCENARIOS).siblings().show();
578
+ $('li.message').show();
579
+ });
580
+ }.join
581
+ else
582
+ @builder << %w{
583
+ $(document).ready(function() {
584
+ $(SCENARIOS).siblings().show();
585
+ $('li.message').hide();
586
+ });
587
+ }.join
588
+ end
589
+ end
590
+ end
591
+
592
+ def inline_jquery
593
+ File.read(File.dirname(__FILE__) + '/jquery-min.js')
594
+ end
595
+
596
+ def inline_js_content
597
+ <<-EOF
598
+
599
+ SCENARIOS = "h3[id^='scenario_'],h3[id^=background_]";
600
+
601
+ $(document).ready(function() {
602
+ $(SCENARIOS).css('cursor', 'pointer');
603
+ $(SCENARIOS).click(function() {
604
+ $(this).siblings().toggle(250);
605
+ });
606
+
607
+ $("#collapser").css('cursor', 'pointer');
608
+ $("#collapser").click(function() {
609
+ $(SCENARIOS).siblings().hide();
610
+ $('li.message').hide();
611
+ });
612
+
613
+ $("#expander").css('cursor', 'pointer');
614
+ $("#expander").click(function() {
615
+ $(SCENARIOS).siblings().show();
616
+ });
617
+ })
618
+
619
+ function moveProgressBar(percentDone) {
620
+ $("cucumber-header").css('width', percentDone +"%");
621
+ }
622
+ function makeRed(element_id) {
623
+ $('#'+element_id).css('background', '#C40D0D');
624
+ $('#'+element_id).css('color', '#FFFFFF');
625
+ }
626
+ function makeMenuRed() {
627
+ $('#menu .sprint').css('border-color', '#C40D0D');
628
+ }
629
+ function makeYellow(element_id) {
630
+ $('#'+element_id).css('background', '#FAF834');
631
+ $('#'+element_id).css('color', '#000000');
632
+ }
633
+ function makeMenuYellow() {
634
+ $('#menu .sprint').css('border-color', '#FAF834');
635
+ }
636
+
637
+ EOF
638
+ end
639
+
640
+ def move_progress
641
+ @builder << " <script>moveProgressBar('#{percent_done}');</script>"
642
+ end
643
+
644
+ def percent_done
645
+ result = 100.0
646
+ if @step_count != 0
647
+ result = ((@step_number).to_f / @step_count.to_f * 1000).to_i / 10.0
648
+ end
649
+ result
650
+ end
651
+
652
+ def format_exception(exception)
653
+ (["#{exception.message}"] + exception.backtrace).join("\n")
654
+ end
655
+
656
+ def backtrace_line(line)
657
+ line.gsub(/\A([^:]*\.(?:rb|feature|haml)):(\d*).*\z/) do
658
+ if ENV['TM_PROJECT_DIRECTORY']
659
+ "<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
660
+ else
661
+ line
662
+ end
663
+ end
664
+ end
665
+
666
+ def print_stats(features)
667
+ @builder << "<script>document.getElementById('duration').innerHTML = \"Finished in <strong>#{format_duration(features.duration)} seconds</strong>\";</script>"
668
+ @builder << "<script>document.getElementById('totals').innerHTML = \"#{print_stat_string(features)}\";</script>"
669
+ end
670
+
671
+ def print_stat_string(features)
672
+ string = String.new
673
+ string << dump_count(@runtime.scenarios.length, "scenario")
674
+ scenario_count = print_status_counts{|status| @runtime.scenarios(status)}
675
+ string << scenario_count if scenario_count
676
+ string << "<br />"
677
+ string << dump_count(@runtime.steps.length, "step")
678
+ step_count = print_status_counts{|status| @runtime.steps(status)}
679
+ string << step_count if step_count
680
+ end
681
+
682
+ def print_status_counts
683
+ counts = [:failed, :skipped, :undefined, :pending, :passed].map do |status|
684
+ elements = yield status
685
+ elements.any? ? "#{elements.length} #{status.to_s}" : nil
686
+ end.compact
687
+ return " (#{counts.join(', ')})" if counts.any?
688
+ end
689
+
690
+ def dump_count(count, what, state=nil)
691
+ [count, state, "#{what}#{count == 1 ? '' : 's'}"].compact.join(" ")
692
+ end
693
+
694
+ def create_builder(io)
695
+ ::Cucumber::Formatter::OrderedXmlMarkup.new(:target => io, :indent => 0)
696
+ end
697
+
698
+ class SnippetExtractor #:nodoc:
699
+ class NullConverter; def convert(code, pre); code; end; end #:nodoc:
700
+ begin; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end
701
+
702
+ def snippet(error)
703
+ raw_code, line = snippet_for(error[0])
704
+ highlighted = @@converter.convert(raw_code, false)
705
+ highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
706
+ post_process(highlighted, line)
707
+ end
708
+
709
+ def snippet_for(error_line)
710
+ if error_line =~ /(.*):(\d+)/
711
+ file = $1
712
+ line = $2.to_i
713
+ [lines_around(file, line), line]
714
+ else
715
+ ["# Couldn't get snippet for #{error_line}", 1]
716
+ end
717
+ end
718
+
719
+ def lines_around(file, line)
720
+ if File.file?(file)
721
+ lines = File.open(file).read.split("\n")
722
+ min = [0, line-3].max
723
+ max = [line+1, lines.length-1].min
724
+ selected_lines = []
725
+ selected_lines.join("\n")
726
+ lines[min..max].join("\n")
727
+ else
728
+ "# Couldn't get snippet for #{file}"
729
+ end
730
+ end
731
+
732
+ def post_process(highlighted, offending_line)
733
+ new_lines = []
734
+ highlighted.split("\n").each_with_index do |line, i|
735
+ new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
736
+ new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
737
+ new_lines << new_line
738
+ end
739
+ new_lines.join("\n")
740
+ end
741
+
742
+ end
743
+ end
744
+ end
745
+ end