cuporter 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.textile +123 -44
- data/Rakefile +5 -5
- data/bin/cuporter +19 -11
- data/lib/cuporter.rb +15 -26
- data/lib/cuporter/cli/options.rb +95 -22
- data/lib/cuporter/document.rb +23 -0
- data/lib/cuporter/document/html_document.rb +82 -0
- data/lib/cuporter/extensions/nokogiri.rb +46 -0
- data/lib/cuporter/feature_parser.rb +38 -19
- data/lib/cuporter/filter.rb +1 -0
- data/lib/cuporter/formatters/csv.rb +69 -0
- data/lib/cuporter/formatters/text.rb +166 -0
- data/lib/cuporter/formatters/xml_to_html.xslt +315 -0
- data/lib/cuporter/node.rb +7 -83
- data/lib/cuporter/node/node_base.rb +114 -0
- data/lib/cuporter/node/numbering.rb +32 -0
- data/lib/cuporter/node/sorting.rb +27 -0
- data/lib/cuporter/node/tagged_node.rb +22 -0
- data/lib/cuporter/node/totalling.rb +19 -0
- data/lib/cuporter/node/types.rb +158 -0
- data/lib/cuporter/node_parser.rb +62 -0
- data/lib/cuporter/report/feature_report.rb +23 -0
- data/lib/cuporter/report/report_base.rb +69 -0
- data/lib/cuporter/report/tag_report.rb +27 -7
- data/lib/cuporter/report/tree_report.rb +24 -0
- data/lib/cuporter/tag_nodes_parser.rb +94 -0
- data/public/images/ajax-loader.gif +0 -0
- data/public/images/file.gif +0 -0
- data/public/images/folder-closed.gif +0 -0
- data/public/images/folder.gif +0 -0
- data/public/images/minus.gif +0 -0
- data/public/images/plus.gif +0 -0
- data/public/images/treeview-black-line.gif +0 -0
- data/public/images/treeview-black.gif +0 -0
- data/public/images/treeview-default-line.gif +0 -0
- data/public/images/treeview-default.gif +0 -0
- data/public/images/treeview-famfamfam-line.gif +0 -0
- data/public/images/treeview-famfamfam.gif +0 -0
- data/public/images/treeview-gray-line.gif +0 -0
- data/public/images/treeview-gray.gif +0 -0
- data/public/images/treeview-red-line.gif +0 -0
- data/public/images/treeview-red.gif +0 -0
- data/public/javascripts/expand-collapse.js +47 -0
- data/public/javascripts/jquery-min.js +167 -0
- data/public/javascripts/jquery.treeview.js +267 -0
- data/public/javascripts/treeview-loader.js +11 -0
- data/public/stylesheets/cuporter.css +101 -0
- data/{lib/cuporter/formatter/name_report/style.css → public/stylesheets/feature_style.css} +17 -18
- data/public/stylesheets/jquery.treeview.css +75 -0
- data/{lib/cuporter/formatter/tag_report/style.css → public/stylesheets/tag_style.css} +17 -22
- data/public/stylesheets/tree_style.css +104 -0
- metadata +52 -37
- data/lib/cuporter/example_set_node.rb +0 -15
- data/lib/cuporter/formatter/csv_text_methods.rb +0 -20
- data/lib/cuporter/formatter/cuporter.css +0 -64
- data/lib/cuporter/formatter/html_methods.rb +0 -122
- data/lib/cuporter/formatter/html_node_writer.rb +0 -104
- data/lib/cuporter/formatter/jquery-min.js +0 -154
- data/lib/cuporter/formatter/name_report/csv.rb +0 -12
- data/lib/cuporter/formatter/name_report/html.rb +0 -48
- data/lib/cuporter/formatter/name_report/html_node_writer.rb +0 -18
- data/lib/cuporter/formatter/name_report/text.rb +0 -16
- data/lib/cuporter/formatter/name_report/text_node_writer.rb +0 -16
- data/lib/cuporter/formatter/pretty_text_methods.rb +0 -22
- data/lib/cuporter/formatter/tag_report/csv.rb +0 -12
- data/lib/cuporter/formatter/tag_report/html.rb +0 -35
- data/lib/cuporter/formatter/tag_report/html_node_writer.rb +0 -19
- data/lib/cuporter/formatter/tag_report/text.rb +0 -14
- data/lib/cuporter/formatter/tag_report/text_node_writer.rb +0 -17
- data/lib/cuporter/formatter/text_methods.rb +0 -19
- data/lib/cuporter/formatter/writer.rb +0 -33
- data/lib/cuporter/name_list_parser.rb +0 -44
- data/lib/cuporter/node_numberer.rb +0 -18
- data/lib/cuporter/report/name_report.rb +0 -19
- data/lib/cuporter/report/report.rb +0 -22
- data/lib/cuporter/tag_list_node.rb +0 -54
- data/lib/cuporter/tag_list_parser.rb +0 -39
@@ -0,0 +1,32 @@
|
|
1
|
+
# Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
module Node
|
4
|
+
module Numbering
|
5
|
+
class Numberer
|
6
|
+
|
7
|
+
attr_reader :total
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@total = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def number(node)
|
14
|
+
node.children.each do |child|
|
15
|
+
case child.node_name
|
16
|
+
when 'scenario', 'example'
|
17
|
+
child["number"] = (@total += 1).to_s
|
18
|
+
end
|
19
|
+
number(child)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def number_all_descendants
|
26
|
+
@numberer = Numberer.new
|
27
|
+
@numberer.number(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
module Node
|
4
|
+
|
5
|
+
module Sorting
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def sort_all_descendants!
|
9
|
+
sort!
|
10
|
+
children.each {|child| child.sort_all_descendants! }
|
11
|
+
end
|
12
|
+
|
13
|
+
# see #NodeSetExtensions in extensions/nokogiri.rb
|
14
|
+
def sort!
|
15
|
+
return unless has_children?
|
16
|
+
sorted_children = children.sort
|
17
|
+
self.children = sorted_children
|
18
|
+
end
|
19
|
+
|
20
|
+
def <=>(other)
|
21
|
+
(short_cuke_name || name) <=> (other.short_cuke_name || other.name)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
module Node
|
4
|
+
# a node with a list of tags that can be applied to all children
|
5
|
+
module Tagged
|
6
|
+
|
7
|
+
attr_accessor :filter
|
8
|
+
|
9
|
+
def has_tags?
|
10
|
+
tags.size > 0
|
11
|
+
end
|
12
|
+
def tags
|
13
|
+
@tags ||= attributes["tags"].to_s.split(/,\s*/) || []
|
14
|
+
end
|
15
|
+
def filter_child(node, context_tags = [])
|
16
|
+
add_child(node) if @filter.pass?(context_tags | tags | node.tags)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
module Node
|
4
|
+
|
5
|
+
module Totalling
|
6
|
+
def total
|
7
|
+
total!
|
8
|
+
children.each {|child| child.total }
|
9
|
+
end
|
10
|
+
|
11
|
+
def total!
|
12
|
+
t = search("scenario,example").size
|
13
|
+
self["total"] = t.to_s if t > 0
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# Copyright 2011 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
module Node
|
4
|
+
module Types
|
5
|
+
class Report < NodeBase
|
6
|
+
def tag_node(tag)
|
7
|
+
at("tag[cuke_name='#{tag}']")
|
8
|
+
end
|
9
|
+
|
10
|
+
# remove leaf nodes, i.e., the scenario and scenario outline children
|
11
|
+
def defoliate!
|
12
|
+
leaves = search("feature > scenario, feature > scenario_outline")
|
13
|
+
leaves.remove
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove_files!
|
17
|
+
search(:feature).each {|f| f.delete('file_path') }
|
18
|
+
end
|
19
|
+
|
20
|
+
def remove_tags!
|
21
|
+
search("*[@tags]").each {|e| e.delete('tags') }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class FilterSummary < NodeBase
|
26
|
+
def add(filter)
|
27
|
+
self << filter_node(:all, filter.all.join(' AND ')) unless filter.all.empty?
|
28
|
+
self << filter_node(:any, filter.any.join(' OR ')) unless filter.any.empty?
|
29
|
+
self << filter_node(:none, filter.none.join(', ')) unless filter.none.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def filter_node(name, text)
|
33
|
+
fn = NodeBase.new(name.to_s, document)
|
34
|
+
fn['tags'] = text
|
35
|
+
fn
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module FileSystemNode
|
41
|
+
def fs_name
|
42
|
+
@fs_name ||= self['fs_name']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Dir < NodeBase
|
47
|
+
include FileSystemNode
|
48
|
+
|
49
|
+
def <=>(other)
|
50
|
+
return -1 if other.is_a? File # ensure folders sort higher
|
51
|
+
fs_name <=> (other['fs_name'] || other['cuke_name'])
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class File < NodeBase
|
57
|
+
include FileSystemNode
|
58
|
+
|
59
|
+
# just total self, don't let any children total themselves. This is so
|
60
|
+
# the feature won't re-total itself when its containing file already has.
|
61
|
+
def total
|
62
|
+
total!
|
63
|
+
end
|
64
|
+
|
65
|
+
def <=>(other)
|
66
|
+
return 1 if other.is_a? Dir # ensure folders sort higher
|
67
|
+
fs_name <=> (other['fs_name'] || other['cuke_name'])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class Tag < NodeBase
|
72
|
+
def feature_node(feature)
|
73
|
+
at("feature[cuke_name='#{feature[:cuke_name]}'][file_path='#{feature[:file_path]}']")
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class ScenarioOutline < NodeBase
|
78
|
+
include Tagged
|
79
|
+
|
80
|
+
def example_set_node(es)
|
81
|
+
at("examples[cuke_name='#{es[:cuke_name]}']")
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
class Feature < NodeBase
|
87
|
+
include Tagged
|
88
|
+
|
89
|
+
def scenario_outline_node(scenario_outline)
|
90
|
+
at("scenario_outline[cuke_name='#{scenario_outline[:cuke_name]}']")
|
91
|
+
end
|
92
|
+
|
93
|
+
def file_path
|
94
|
+
self["file_path"]
|
95
|
+
end
|
96
|
+
|
97
|
+
def file_name
|
98
|
+
file_path.split(/\//).last
|
99
|
+
end
|
100
|
+
|
101
|
+
# sort on: file path, name, substring of name after any ':'
|
102
|
+
def <=>(other)
|
103
|
+
if other.respond_to?(:file_path)
|
104
|
+
file_path <=> other.file_path
|
105
|
+
else
|
106
|
+
super(other)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def eql?(other)
|
111
|
+
if other.respond_to? :file_path
|
112
|
+
return false if file_path != other.file_path
|
113
|
+
end
|
114
|
+
super(other)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# The set of examples in a scenario outline
|
119
|
+
class Examples < NodeBase
|
120
|
+
include Tagged
|
121
|
+
|
122
|
+
# don't sort scenario outline examples
|
123
|
+
def sort!
|
124
|
+
# no op
|
125
|
+
end
|
126
|
+
|
127
|
+
# don't total at the example set level
|
128
|
+
def total
|
129
|
+
# no op
|
130
|
+
end
|
131
|
+
|
132
|
+
def add_child(other)
|
133
|
+
cn = other['cuke_name'].dup
|
134
|
+
cn.sub!(/^\|/, '')
|
135
|
+
cn.split('|').each do |cell_text|
|
136
|
+
cell = Nokogiri::XML::Node.new("span", document)
|
137
|
+
cell.content = cell_text.strip
|
138
|
+
other << cell
|
139
|
+
end
|
140
|
+
super(other)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
Scenarios = Examples
|
144
|
+
|
145
|
+
# Leaf Nodes: won't have children
|
146
|
+
class Scenario < NodeBase
|
147
|
+
include Tagged
|
148
|
+
end
|
149
|
+
|
150
|
+
class Example < NodeBase
|
151
|
+
end
|
152
|
+
|
153
|
+
class ExampleHeader < NodeBase
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
|
3
|
+
module Cuporter
|
4
|
+
class NodeParser < FeatureParser
|
5
|
+
|
6
|
+
# ++sub_expression++ is the paren group in the regex, dereferenced with $1 in the caller
|
7
|
+
def new_feature_node(sub_expression, file)
|
8
|
+
f = Node.new_node(:Feature, @doc, :cuke_name => sub_expression, :tags => @current_tags, :file_path => file)
|
9
|
+
f.filter = @filter
|
10
|
+
f
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle_scenario_line(sub_expression)
|
14
|
+
if @filter.pass?(@current_tags | @feature.tags)
|
15
|
+
@feature.add_child(Node.new_node(:Scenario, @doc, :cuke_name => sub_expression, :tags => @current_tags))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def new_scenario_outline_node(sub_expression)
|
20
|
+
so = Node.new_node(:ScenarioOutline, @doc, :cuke_name => sub_expression, :tags => @current_tags)
|
21
|
+
so.filter = @filter
|
22
|
+
so
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_example_set_line
|
26
|
+
if @filter.pass?(@feature.tags | @scenario_outline.tags | @example_set.tags)
|
27
|
+
@scenario_outline.add_child @example_set
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_example_set_node(sub_expression)
|
32
|
+
es = Node.new_node(:Examples, @doc, :cuke_name => sub_expression, :tags => @current_tags)
|
33
|
+
es.filter = @filter
|
34
|
+
es
|
35
|
+
end
|
36
|
+
|
37
|
+
def new_example_line(sub_expression)
|
38
|
+
example_type = :ExampleHeader
|
39
|
+
# if the example set has a child already, then it must be the header
|
40
|
+
example_type = :Example if @example_set.has_children?
|
41
|
+
@example_set.add_child(Node.new_node(example_type, @doc, :cuke_name => sub_expression))
|
42
|
+
end
|
43
|
+
|
44
|
+
def close_scenario_outline
|
45
|
+
if @scenario_outline
|
46
|
+
if @example_set
|
47
|
+
handle_example_set_line
|
48
|
+
@example_set = nil
|
49
|
+
end
|
50
|
+
@feature.add_child(@scenario_outline) if @scenario_outline.has_children?
|
51
|
+
@scenario_outline = nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(file, doc, filter)
|
56
|
+
super(file)
|
57
|
+
@filter = filter
|
58
|
+
@doc = doc
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
class FeatureReport < ReportBase
|
4
|
+
|
5
|
+
def report_node
|
6
|
+
unless @report
|
7
|
+
@report = Cuporter::Node.new_node(:Report, @doc, :title => title, :view => view)
|
8
|
+
files.each do |file|
|
9
|
+
feature = FeatureParser.node(file, @doc, @filter, root_dir)
|
10
|
+
if feature && feature.has_children?
|
11
|
+
@report.add_child feature
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
@report
|
16
|
+
end
|
17
|
+
|
18
|
+
def title
|
19
|
+
@title || "Cucumber Features, List View"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
class ReportBase
|
4
|
+
|
5
|
+
attr_reader :filter, :doc, :root_dir, :view
|
6
|
+
|
7
|
+
def initialize(options)
|
8
|
+
@input_file_pattern = options[:input_file_pattern]
|
9
|
+
@doc = Cuporter::Document.new_xml
|
10
|
+
@view = options[:report]
|
11
|
+
@root_dir = options[:root_dir]
|
12
|
+
@filter = Filter.new(options[:filter_args])
|
13
|
+
@title = options[:title]
|
14
|
+
@sort = options[:sort]
|
15
|
+
@total = options[:total]
|
16
|
+
@number = options[:number]
|
17
|
+
@leaves = options[:leaves]
|
18
|
+
@show_files = options[:show_files]
|
19
|
+
@show_tags = options[:show_tags]
|
20
|
+
end
|
21
|
+
|
22
|
+
def no_leaves?
|
23
|
+
!@leaves
|
24
|
+
end
|
25
|
+
|
26
|
+
def sort?
|
27
|
+
@sort
|
28
|
+
end
|
29
|
+
|
30
|
+
def total?
|
31
|
+
@total
|
32
|
+
end
|
33
|
+
|
34
|
+
def number?
|
35
|
+
@number
|
36
|
+
end
|
37
|
+
|
38
|
+
def show_files?
|
39
|
+
@show_files
|
40
|
+
end
|
41
|
+
|
42
|
+
def show_tags?
|
43
|
+
@show_tags
|
44
|
+
end
|
45
|
+
|
46
|
+
def files
|
47
|
+
Dir[@input_file_pattern].collect {|f| File.expand_path f}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create(options)
|
51
|
+
klass = Cuporter.const_get("#{options[:report].downcase}Report".to_class_name)
|
52
|
+
klass.new(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
def build
|
56
|
+
report_node.sort_all_descendants! if sort?
|
57
|
+
report_node.number_all_descendants if number?
|
58
|
+
report_node.total if total?
|
59
|
+
report_node.defoliate! if no_leaves?
|
60
|
+
report_node.remove_files! unless show_files?
|
61
|
+
report_node.remove_tags! unless show_tags?
|
62
|
+
|
63
|
+
doc.add_filter_summary(@filter)
|
64
|
+
doc.add_report report_node
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -1,15 +1,35 @@
|
|
1
1
|
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
2
|
module Cuporter
|
3
|
-
class TagReport <
|
3
|
+
class TagReport < ReportBase
|
4
4
|
|
5
|
-
def
|
6
|
-
tags = TagListNode.new("report",[])
|
5
|
+
def build_report_node
|
7
6
|
files.each do |file|
|
8
|
-
|
9
|
-
tags.merge_tag_nodes(feature) if feature
|
7
|
+
FeatureParser.tag_nodes(file, report, @filter, root_dir)
|
10
8
|
end
|
11
|
-
|
12
|
-
|
9
|
+
end
|
10
|
+
|
11
|
+
def report
|
12
|
+
@report ||= begin
|
13
|
+
r = Cuporter::Node.new_node(:Report, doc, :title => title, :view => view)
|
14
|
+
doc.add_filter_summary(@filter)
|
15
|
+
doc.add_report r
|
16
|
+
r
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def title
|
21
|
+
@title || "Cucumber Tags"
|
22
|
+
end
|
23
|
+
|
24
|
+
def build
|
25
|
+
build_report_node
|
26
|
+
report.sort_all_descendants! if sort?
|
27
|
+
report.search(:tag).each {|f| f.number_all_descendants } if number?
|
28
|
+
report.total if total?
|
29
|
+
report.defoliate! if no_leaves?
|
30
|
+
report.remove_files! unless show_files?
|
31
|
+
report.remove_tags! unless show_tags?
|
32
|
+
self
|
13
33
|
end
|
14
34
|
|
15
35
|
end
|