cuporter 0.3.2 → 0.3.3

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.
Files changed (77) hide show
  1. data/README.textile +123 -44
  2. data/Rakefile +5 -5
  3. data/bin/cuporter +19 -11
  4. data/lib/cuporter.rb +15 -26
  5. data/lib/cuporter/cli/options.rb +95 -22
  6. data/lib/cuporter/document.rb +23 -0
  7. data/lib/cuporter/document/html_document.rb +82 -0
  8. data/lib/cuporter/extensions/nokogiri.rb +46 -0
  9. data/lib/cuporter/feature_parser.rb +38 -19
  10. data/lib/cuporter/filter.rb +1 -0
  11. data/lib/cuporter/formatters/csv.rb +69 -0
  12. data/lib/cuporter/formatters/text.rb +166 -0
  13. data/lib/cuporter/formatters/xml_to_html.xslt +315 -0
  14. data/lib/cuporter/node.rb +7 -83
  15. data/lib/cuporter/node/node_base.rb +114 -0
  16. data/lib/cuporter/node/numbering.rb +32 -0
  17. data/lib/cuporter/node/sorting.rb +27 -0
  18. data/lib/cuporter/node/tagged_node.rb +22 -0
  19. data/lib/cuporter/node/totalling.rb +19 -0
  20. data/lib/cuporter/node/types.rb +158 -0
  21. data/lib/cuporter/node_parser.rb +62 -0
  22. data/lib/cuporter/report/feature_report.rb +23 -0
  23. data/lib/cuporter/report/report_base.rb +69 -0
  24. data/lib/cuporter/report/tag_report.rb +27 -7
  25. data/lib/cuporter/report/tree_report.rb +24 -0
  26. data/lib/cuporter/tag_nodes_parser.rb +94 -0
  27. data/public/images/ajax-loader.gif +0 -0
  28. data/public/images/file.gif +0 -0
  29. data/public/images/folder-closed.gif +0 -0
  30. data/public/images/folder.gif +0 -0
  31. data/public/images/minus.gif +0 -0
  32. data/public/images/plus.gif +0 -0
  33. data/public/images/treeview-black-line.gif +0 -0
  34. data/public/images/treeview-black.gif +0 -0
  35. data/public/images/treeview-default-line.gif +0 -0
  36. data/public/images/treeview-default.gif +0 -0
  37. data/public/images/treeview-famfamfam-line.gif +0 -0
  38. data/public/images/treeview-famfamfam.gif +0 -0
  39. data/public/images/treeview-gray-line.gif +0 -0
  40. data/public/images/treeview-gray.gif +0 -0
  41. data/public/images/treeview-red-line.gif +0 -0
  42. data/public/images/treeview-red.gif +0 -0
  43. data/public/javascripts/expand-collapse.js +47 -0
  44. data/public/javascripts/jquery-min.js +167 -0
  45. data/public/javascripts/jquery.treeview.js +267 -0
  46. data/public/javascripts/treeview-loader.js +11 -0
  47. data/public/stylesheets/cuporter.css +101 -0
  48. data/{lib/cuporter/formatter/name_report/style.css → public/stylesheets/feature_style.css} +17 -18
  49. data/public/stylesheets/jquery.treeview.css +75 -0
  50. data/{lib/cuporter/formatter/tag_report/style.css → public/stylesheets/tag_style.css} +17 -22
  51. data/public/stylesheets/tree_style.css +104 -0
  52. metadata +52 -37
  53. data/lib/cuporter/example_set_node.rb +0 -15
  54. data/lib/cuporter/formatter/csv_text_methods.rb +0 -20
  55. data/lib/cuporter/formatter/cuporter.css +0 -64
  56. data/lib/cuporter/formatter/html_methods.rb +0 -122
  57. data/lib/cuporter/formatter/html_node_writer.rb +0 -104
  58. data/lib/cuporter/formatter/jquery-min.js +0 -154
  59. data/lib/cuporter/formatter/name_report/csv.rb +0 -12
  60. data/lib/cuporter/formatter/name_report/html.rb +0 -48
  61. data/lib/cuporter/formatter/name_report/html_node_writer.rb +0 -18
  62. data/lib/cuporter/formatter/name_report/text.rb +0 -16
  63. data/lib/cuporter/formatter/name_report/text_node_writer.rb +0 -16
  64. data/lib/cuporter/formatter/pretty_text_methods.rb +0 -22
  65. data/lib/cuporter/formatter/tag_report/csv.rb +0 -12
  66. data/lib/cuporter/formatter/tag_report/html.rb +0 -35
  67. data/lib/cuporter/formatter/tag_report/html_node_writer.rb +0 -19
  68. data/lib/cuporter/formatter/tag_report/text.rb +0 -14
  69. data/lib/cuporter/formatter/tag_report/text_node_writer.rb +0 -17
  70. data/lib/cuporter/formatter/text_methods.rb +0 -19
  71. data/lib/cuporter/formatter/writer.rb +0 -33
  72. data/lib/cuporter/name_list_parser.rb +0 -44
  73. data/lib/cuporter/node_numberer.rb +0 -18
  74. data/lib/cuporter/report/name_report.rb +0 -19
  75. data/lib/cuporter/report/report.rb +0 -22
  76. data/lib/cuporter/tag_list_node.rb +0 -54
  77. data/lib/cuporter/tag_list_parser.rb +0 -39
@@ -0,0 +1,23 @@
1
+ # Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cuporter
3
+ module Document
4
+
5
+ def self.new_xml
6
+ doc = Nokogiri::XML::Document.new
7
+ doc << Nokogiri::XML::Node.new('xml', doc)
8
+ doc.root << Nokogiri::XML::Node.new('body', doc)
9
+ doc
10
+ end
11
+
12
+ def self.new_html(view)
13
+ Nokogiri::XML::Document.send(:include, Cuporter::Document::Html)
14
+ doc = Nokogiri::XML::Document.new
15
+ doc.view = view
16
+ root = Nokogiri::XML::Node.new('html', doc)
17
+ root.create_internal_subset( 'html', "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")
18
+ doc << root
19
+ doc
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,82 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cuporter
3
+ module Document
4
+ module Html
5
+ attr_accessor :view
6
+
7
+ def add_report(node)
8
+ root << head(node['title'])
9
+ body = new_node('body')
10
+ body << node
11
+ root << body
12
+ end
13
+
14
+ # remove CDATA tags so CSS works
15
+ def to_html(options = {})
16
+ to_xhtml(options).gsub(/(<!\[CDATA\[|\]\]>)/,'')
17
+ end
18
+
19
+ private
20
+
21
+ def head(title_text)
22
+ h = new_node(:head)
23
+ title = new_node(:title)
24
+ title.content = title_text
25
+ h << title
26
+
27
+ case view
28
+ when "tree"
29
+ h << link_js("jquery-min.js")
30
+ h << link_js("jquery.treeview.js")
31
+ h << link_css("jquery.treeview.css")
32
+ h << link_js("treeview-loader.js")
33
+ h << link_css("cuporter.css")
34
+ h << link_css("#{view}_style.css")
35
+ else
36
+ h << script_js("jquery-min.js")
37
+ h << style_css("cuporter.css")
38
+ h << style_css("#{view}_style.css")
39
+ h << script_js("expand-collapse.js")
40
+ end
41
+ h
42
+ end
43
+
44
+ def assets_dir
45
+ @assets_dir ||= File.expand_path('public', File.dirname(__FILE__) + "/../../../")
46
+ end
47
+
48
+ def style_css(file)
49
+ style = new_node('style', 'type' => 'text/css')
50
+ style << file_contents("#{assets_dir}/stylesheets/#{file}")
51
+ style
52
+ end
53
+
54
+ def script_js(file)
55
+ script = new_node('script', 'type' => 'text/javascript')
56
+ script << file_contents("#{assets_dir}/javascripts/#{file}")
57
+ script
58
+ end
59
+
60
+ def link_css(file)
61
+ new_node('link', 'type' => 'text/css', 'rel' => 'stylesheet', 'href' => "#{assets_dir}/stylesheets/#{file}")
62
+ end
63
+
64
+ def link_js(file)
65
+ new_node('script', 'type' => 'text/javascript', 'src' => "#{assets_dir}/javascripts/#{file}")
66
+ end
67
+
68
+ def file_contents(file_name)
69
+ create_cdata("\n#{File.read(file_name)}")
70
+ end
71
+
72
+ def new_node(name, attributes = {})
73
+ n = Nokogiri::XML::Node.new(name.to_s, self)
74
+ attributes.each do | attr, value |
75
+ n[attr.to_s] = value.to_s
76
+ end
77
+ n
78
+ end
79
+ end
80
+ end
81
+ end
82
+
@@ -0,0 +1,46 @@
1
+ # Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
2
+ require 'rubygems'
3
+ require 'nokogiri'
4
+ Nokogiri::XML::Node.class_eval do
5
+ # undefine the spaceship (comparison operator used by sort)
6
+ # so our mixed-in versions get used.
7
+ remove_method :<=>
8
+ end
9
+
10
+ module NodeSetExtensions
11
+ def sort
12
+ return self if empty?
13
+ sorted = to_a.sort
14
+ self.class.new(document, sorted)
15
+ end
16
+
17
+ # value equivalence
18
+ def eql?(other)
19
+ return false unless other.is_a?(Nokogiri::XML::NodeSet)
20
+ return false unless length == other.length
21
+ each_with_index do |node, i|
22
+ return false unless node.eql?(other[i])
23
+ end
24
+ true
25
+ end
26
+ end
27
+ Nokogiri::XML::NodeSet.send(:include, NodeSetExtensions)
28
+
29
+ module DocumentExtensions
30
+
31
+ def to_html(options = {})
32
+ to_xml(options)
33
+ end
34
+
35
+ def add_report(report_node)
36
+ root.at(:body) << report_node
37
+ end
38
+
39
+ def add_filter_summary(filter)
40
+ return if filter.empty?
41
+ s = Cuporter::Node.new_node(:FilterSummary, self)
42
+ s.add(filter)
43
+ root.at(:body) << s
44
+ end
45
+ end
46
+ Nokogiri::XML::Document.send(:include, DocumentExtensions)
@@ -2,21 +2,30 @@
2
2
 
3
3
  module Cuporter
4
4
  class FeatureParser
5
- FEATURE_LINE = /^\s*(Feature:[^#]+)/
5
+ FEATURE_LINE = /^\s*(Feature:[^#]*)/
6
6
  TAG_LINE = /^\s*(@\w.+)/
7
- SCENARIO_LINE = /^\s*(Scenario:[^#]+)$/
8
- SCENARIO_OUTLINE_LINE = /^\s*(Scenario Outline:[^#]+)$/
7
+ SCENARIO_LINE = /^\s*(Scenario:[^#]*)$/
8
+ SCENARIO_OUTLINE_LINE = /^\s*(Scenario Outline:[^#]*)$/
9
9
  SCENARIO_SET_LINE = /^\s*(Scenarios:[^#]*)$/
10
10
  EXAMPLE_SET_LINE = /^\s*(Examples:[^#]*)$/
11
11
  EXAMPLE_LINE = /^\s*(\|.*\|)\s*$/
12
+ PY_STRING_LINE = /^\s*"""\s*$/
12
13
 
13
- def self.tag_list(file)
14
- TagListParser.new(file).parse_feature
14
+ # adds a node to the doc for each cucumber '@' tag, populated with features and
15
+ # scenarios
16
+ def self.tag_nodes(file, report, filter, root_dir)
17
+ parser = TagNodesParser.new(file, report, filter)
18
+ parser.root = root_dir
19
+ parser.parse_feature
15
20
  end
16
21
 
17
- def self.name_list(file, filter)
18
- NameListParser.new(file, filter).parse_feature
22
+ # returns a feature node populated with scenarios
23
+ def self.node(file, doc, filter, root_dir)
24
+ parser = NodeParser.new(file, doc, filter)
25
+ parser.root = root_dir
26
+ parser.parse_feature
19
27
  end
28
+ attr_writer :root
20
29
 
21
30
  def initialize(file)
22
31
  @file = file
@@ -24,36 +33,46 @@ module Cuporter
24
33
  @lines = File.read(@file).split(/\n/)
25
34
  end
26
35
 
36
+ def file_relative_path
37
+ @file_relative_path ||= @file.sub(/^.*#{@root}\//,"#{@root}/")
38
+ end
39
+
27
40
  def parse_feature
41
+ @open_comment_block = false
42
+
28
43
  @lines.each do |line|
44
+ next if @open_comment_block && line !~ PY_STRING_LINE
45
+
29
46
  case line
30
- when FeatureParser::TAG_LINE
47
+ when PY_STRING_LINE
48
+ # toggle, to declare the multiline comment 'heredoc' open or closed
49
+ @open_comment_block = !@open_comment_block
50
+ when TAG_LINE
31
51
  # may be more than one tag line
32
52
  @current_tags |= $1.strip.split(/\s+/)
33
- when FeatureParser::FEATURE_LINE
34
- @feature = new_feature_node($1)
35
- @feature.file = @file.sub(/^.*features\//,"features/")
53
+ when FEATURE_LINE
54
+ @feature = new_feature_node($1.strip, file_relative_path)
36
55
  @current_tags = []
37
- when FeatureParser::SCENARIO_LINE
56
+ when SCENARIO_LINE
38
57
  # How do we know when we have read all the lines from a "Scenario Outline:"?
39
58
  # One way is when we encounter a "Scenario:"
40
59
  close_scenario_outline
41
60
 
42
- handle_scenario_line($1)
61
+ handle_scenario_line($1.strip)
43
62
  @current_tags = []
44
- when FeatureParser::SCENARIO_OUTLINE_LINE
63
+ when SCENARIO_OUTLINE_LINE
45
64
  # ... another is when we hit a subsequent "Scenario Outline:"
46
65
  close_scenario_outline
47
66
 
48
- @scenario_outline = new_scenario_outline_node($1)
67
+ @scenario_outline = new_scenario_outline_node($1.strip)
49
68
  @current_tags = []
50
- when FeatureParser::EXAMPLE_SET_LINE, FeatureParser::SCENARIO_SET_LINE
69
+ when EXAMPLE_SET_LINE, SCENARIO_SET_LINE
51
70
  handle_example_set_line if @example_set
52
71
 
53
- @example_set = new_example_set_node($1)
72
+ @example_set = new_example_set_node($1.strip)
54
73
  @current_tags = []
55
- when @example_set && FeatureParser::EXAMPLE_LINE
56
- @example_set.add_child(Node.new($1))
74
+ when @example_set && EXAMPLE_LINE
75
+ new_example_line($1.strip)
57
76
  end
58
77
  end
59
78
 
@@ -26,6 +26,7 @@ module Cuporter
26
26
  unless none.empty?
27
27
  pass = false if !(other_tags & none).empty?
28
28
  end
29
+
29
30
  pass
30
31
  end
31
32
 
@@ -0,0 +1,69 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cuporter
3
+ module Formatters
4
+
5
+ module Csv
6
+ extend Util
7
+ extend NodeFormatters
8
+
9
+ COMMA = ","
10
+
11
+ def tab_stop
12
+ @@tab_stop ||= COMMA
13
+ end
14
+
15
+ def total_column_spacer
16
+ @@total_column_spacer ||= Cuporter.options[:total] ? tab_stop : ""
17
+ end
18
+
19
+ def total_column
20
+ @@total_column ||= total_column_spacer
21
+ end
22
+
23
+ def total(node)
24
+ t = node['total'] ? "[#{node['total']}]," : COMMA
25
+ "#{tab_stop * depth(node)}#{t}"
26
+ end
27
+
28
+ def number(node)
29
+ n = node['number'] ? "#{node['number']})," : COMMA
30
+ "#{tab_stop * depth(node)}#{n}"
31
+ end
32
+
33
+ def blank_for_number(node)
34
+ "#{tab_stop * depth(node)},"
35
+ end
36
+
37
+ def cuke_name(value)
38
+ "#{value},"
39
+ end
40
+
41
+ def fs_name(value)
42
+ "#{value.upcase},"
43
+ end
44
+
45
+ def tags(value)
46
+ return "" unless value
47
+ "#{value},"
48
+ end
49
+
50
+ def file_path(value, length = nil)
51
+ return "" unless value
52
+ "#{value},"
53
+ end
54
+
55
+ def title(value)
56
+ return "" unless value
57
+ "#{value},"
58
+ end
59
+
60
+ def example_name(name)
61
+ name.sub!(/^\s*\|/, '')
62
+ name.sub!(/\|\s*$/, '')
63
+ name.gsub(/\|/,";")
64
+ end
65
+
66
+ extend(Cuporter::Formatters::Csv)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,166 @@
1
+ # Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
2
+ module Cuporter
3
+ module Formatters
4
+
5
+ module Util
6
+ def terminal_width
7
+ @@terminal_width ||= (`tput cols` || 120).to_i
8
+ end
9
+
10
+ def depth(node)
11
+ node.parent.path.sub(/^.*\/report/, 'report').split('/').size#- 2
12
+ end
13
+
14
+ end
15
+
16
+ module NodeFormatters
17
+ def report(l, node)
18
+ return l unless Cuporter.options[:text_summary]
19
+ l += total(node)
20
+ l += cuke_name(node['cuke_name'])
21
+ l += title(node['title'])
22
+ l
23
+ end
24
+ def dir(l, node)
25
+ l += total(node)
26
+ l += fs_name(node['fs_name'])
27
+ l
28
+ end
29
+ def file(l, node)
30
+ l += total(node)
31
+ l += fs_name(node['fs_name'])
32
+ l
33
+ end
34
+ def tag(l, node)
35
+ l += total(node)
36
+ l += cuke_name(node['cuke_name'])
37
+ l
38
+ end
39
+ def feature(l, node)
40
+ l += total(node)
41
+ l += cuke_name(node['cuke_name'])
42
+ l += tags(node['tags'])
43
+ l += file_path(node['file_path'], l.size)
44
+ l
45
+ end
46
+ def scenario(l, node)
47
+ l += total_column_spacer
48
+ l += number(node)
49
+ l += cuke_name(node['cuke_name'])
50
+ l += tags(node['tags'])
51
+ l
52
+ end
53
+ def scenario_outline(l, node)
54
+ l += total(node)
55
+ l += cuke_name(node['cuke_name'])
56
+ l += tags(node['tags'])
57
+ l
58
+ end
59
+ def examples(l, node)
60
+ l += total_column_spacer
61
+ l += normal_indent(node)
62
+ l += cuke_name(node['cuke_name'])
63
+ l += tags(node['tags'])
64
+ l
65
+ end
66
+ def example_header(l, node)
67
+ l += total_column_spacer
68
+ l += blank_for_number(node)
69
+ l += example_name(node['cuke_name'])
70
+ l += tags(node['tags'])
71
+ l
72
+ end
73
+ def example(l, node)
74
+ l += total_column_spacer
75
+ l += number(node)
76
+ l += example_name(node['cuke_name'])
77
+ l += tags(node['tags'])
78
+ l
79
+ end
80
+
81
+ def build_line(node)
82
+ l = send(node.name.to_sym, "", node)
83
+ l += "\n" unless l.empty?
84
+ l
85
+ rescue NoMethodError
86
+ ""
87
+ end
88
+ end
89
+ module Text
90
+ extend Util
91
+ extend NodeFormatters
92
+
93
+
94
+ SPACER = " "
95
+ def tab_stop
96
+ @@tab_stop ||= SPACER * 2
97
+ end
98
+
99
+ def total_column_spacer
100
+ @@total_column_spacer ||= Cuporter.options[:total] ? tab_stop : ""
101
+ end
102
+
103
+ def total_column
104
+ @@total_column ||= Cuporter.options[:total] ? tab_stop * 2: ""
105
+ end
106
+
107
+ def total(node)
108
+ format_number(node, 'total') do |indent_length, value|
109
+ "[#{value}]".ljust(total_column.size, SPACER)
110
+ end
111
+ end
112
+
113
+ def number(node)
114
+ format_number(node, 'number') do |indent_length, value|
115
+ "%#{tab_stop}#{indent_length}s " % "#{value}."
116
+ end
117
+ end
118
+
119
+ def format_number(node, attr)
120
+ d = depth(node)
121
+ indent_length = d * tab_stop.size
122
+ if ( value = node[attr])
123
+ yield(indent_length, value)
124
+ else
125
+ tab_stop * d
126
+ end
127
+ end
128
+ def blank_for_number(node)
129
+ l = tab_stop * depth(node)
130
+ l += SPACER if node.document.at("[@number]")
131
+ l
132
+ end
133
+ def cuke_name(value)
134
+ value || ""
135
+ end
136
+ def fs_name(value)
137
+ return "" unless value
138
+ value.upcase
139
+ end
140
+
141
+ def tags(value)
142
+ return "" unless value
143
+ "#{tab_stop * 2}#{value}"
144
+ end
145
+
146
+ def file_path(value, line_size)
147
+ return "" unless value
148
+ "#{tab_stop}##{value}#{tab_stop * 2}".rjust(terminal_width - line_size)
149
+ end
150
+
151
+ def title(value)
152
+ value || ""
153
+ end
154
+
155
+ def normal_indent(node)
156
+ tab_stop * depth(node)
157
+ end
158
+
159
+ def example_name(value)
160
+ cuke_name value
161
+ end
162
+ extend(Cuporter::Formatters::Text)
163
+ end
164
+
165
+ end
166
+ end