cuporter 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.textile +72 -0
- data/Rakefile +86 -0
- data/bin/cuporter +9 -0
- data/features/pretty_print.feature +7 -0
- data/features/step_definitions/cuporter_steps.rb +8 -0
- data/features/support/env.rb +2 -0
- data/lib/cuporter/cli/options.rb +46 -0
- data/lib/cuporter/extensions/string.rb +10 -0
- data/lib/cuporter/feature_parser.rb +63 -0
- data/lib/cuporter/formatters/csv.rb +11 -0
- data/lib/cuporter/formatters/html.rb +70 -0
- data/lib/cuporter/formatters/text.rb +11 -0
- data/lib/cuporter/formatters/text_methods.rb +28 -0
- data/lib/cuporter/formatters/writer.rb +17 -0
- data/lib/cuporter/node.rb +72 -0
- data/lib/cuporter/tag_list_node.rb +25 -0
- data/lib/cuporter/tag_report.rb +23 -0
- data/lib/cuporter.rb +12 -0
- data/spec/cuporter/feature_parser_spec.rb +53 -0
- data/spec/cuporter/functional/single_feature_spec.rb +348 -0
- data/spec/cuporter/node_spec.rb +55 -0
- data/spec/cuporter/support/functional/cli.rb +10 -0
- data/spec/cuporter/tag_list_node_spec.rb +289 -0
- data/spec/spec_helper.rb +8 -0
- metadata +96 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2010 ThoughtWorks, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
h1. Cuporter
|
2
|
+
|
3
|
+
Scrapes your feature files and shows scenarios per tag, with their context. Formats Pretty Text, HTML (not styled yet) and CSV.
|
4
|
+
|
5
|
+
Consider this a stop-gap until we get this functionality in a proper cucumber formatter.
|
6
|
+
|
7
|
+
---------
|
8
|
+
h3. Example Output
|
9
|
+
|
10
|
+
<pre>
|
11
|
+
@failing
|
12
|
+
Feature: Abominable Aardvark
|
13
|
+
Scenario: An Aardvark eats ants
|
14
|
+
Scenario: Zee Zebra eats zee aardvark
|
15
|
+
Feature: Wired
|
16
|
+
Scenario: Everybody's Wired
|
17
|
+
Scenario Outline: Why is everybody so wired?
|
18
|
+
Examples: loosely wired
|
19
|
+
Examples: tightly wired
|
20
|
+
@ignore
|
21
|
+
Feature: Wired
|
22
|
+
Scenario Outline: Why is everybody so wired?
|
23
|
+
Examples: tightly wired
|
24
|
+
@wip
|
25
|
+
Feature: HTML formatter
|
26
|
+
Scenario: Everything in fixtures/self_test
|
27
|
+
Feature: not everyone is involved
|
28
|
+
Scenario: Failure is not an option, people
|
29
|
+
Feature: sample
|
30
|
+
Scenario: And yet another Example
|
31
|
+
Feature: search examples
|
32
|
+
Scenario: Generate PDF with pdf formatter
|
33
|
+
|
34
|
+
</pre>
|
35
|
+
|
36
|
+
---------
|
37
|
+
|
38
|
+
h3. Command Lines
|
39
|
+
|
40
|
+
h4. help
|
41
|
+
|
42
|
+
<pre>
|
43
|
+
$ ./bin/cuporter.rb -h
|
44
|
+
|
45
|
+
Usage: cuporter.rb [options]
|
46
|
+
|
47
|
+
-i, --in DIR directory of *.feature files
|
48
|
+
Default: features/**/*.feature
|
49
|
+
|
50
|
+
--input-file FILE full file name with extension: 'path/to/file.feature'
|
51
|
+
|
52
|
+
-o, --out FILE Output file path
|
53
|
+
|
54
|
+
-f, --format [pretty|html|csv] Output format
|
55
|
+
Default: pretty text
|
56
|
+
</pre>
|
57
|
+
|
58
|
+
h4. run script directly
|
59
|
+
|
60
|
+
<pre>
|
61
|
+
$ ./bin/cuporter.rb -i fixtures/self_text # pretty-print demo report to stdout
|
62
|
+
|
63
|
+
$ ./bin/cuporter.rb -f html -o feature_tag_report.html # default input features/**/*.feature to named output file
|
64
|
+
</pre>
|
65
|
+
|
66
|
+
h4. run via rake
|
67
|
+
|
68
|
+
<pre>
|
69
|
+
$ rake cuporter:run["-i fixtures/self_text"]
|
70
|
+
|
71
|
+
$ rake cuporter:run["-f html -o feature_tag_report.html"]
|
72
|
+
</pre>
|
data/Rakefile
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
|
7
|
+
task :default do
|
8
|
+
Rake.application.tasks_in_scope(["cuporter:test"]).each do |t|
|
9
|
+
t.invoke
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :cuporter do
|
14
|
+
|
15
|
+
# $ rake cuporter:run["-f html cucumber_tag_report.html"]
|
16
|
+
desc "run cuporter command line with options"
|
17
|
+
task :run, [:options] do |t, args|
|
18
|
+
sh "ruby ./bin/cuporter #{args.options}"
|
19
|
+
end
|
20
|
+
|
21
|
+
task :readme do
|
22
|
+
require 'redcloth'
|
23
|
+
puts RedCloth.new(File.read("README.textile")).to_html
|
24
|
+
end
|
25
|
+
|
26
|
+
namespace :test do
|
27
|
+
desc "unit specs"
|
28
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
29
|
+
t.pattern = "spec/cuporter/*_spec.rb"
|
30
|
+
t.spec_opts = ["--color" , "--format" , "doc" ]
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "functional specs against feature fixtures"
|
34
|
+
RSpec::Core::RakeTask.new(:functional) do |t|
|
35
|
+
t.pattern = "spec/cuporter/functional/**/*_spec.rb"
|
36
|
+
t.spec_opts = ["--color" , "--format" , "doc" ]
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "cucumber features"
|
40
|
+
task :cucumber do
|
41
|
+
sh "cucumber"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
RDOC_OPTS = ["--all" , "--quiet" , "--line-numbers" , "--inline-source",
|
46
|
+
"--main", "README.textile",
|
47
|
+
"--title", "Cuporter: cucumber tag reporting"]
|
48
|
+
XTRA_RDOC = %w{README.textile LICENSE }
|
49
|
+
|
50
|
+
Rake::RDocTask.new do |rd|
|
51
|
+
rd.rdoc_dir = "doc/rdoc"
|
52
|
+
rd.rdoc_files.include("**/*.rb")
|
53
|
+
rd.rdoc_files.add(XTRA_RDOC)
|
54
|
+
rd.options = RDOC_OPTS
|
55
|
+
end
|
56
|
+
|
57
|
+
spec = Gem::Specification.new do |s|
|
58
|
+
s.name = 'cuporter'
|
59
|
+
s.version = '0.1.0'
|
60
|
+
s.rubyforge_project = s.name
|
61
|
+
|
62
|
+
s.platform = Gem::Platform::RUBY
|
63
|
+
s.has_rdoc = true
|
64
|
+
s.extra_rdoc_files = XTRA_RDOC
|
65
|
+
s.rdoc_options += RDOC_OPTS
|
66
|
+
s.summary = "Scrapes Cucumber *.feature files to build report on tag usage"
|
67
|
+
s.description = s.summary
|
68
|
+
s.author = "Tim Camper"
|
69
|
+
s.email = 'twcamper@thoughtworks.com'
|
70
|
+
s.homepage = 'http://github.com/twcamper/cuporter'
|
71
|
+
s.required_ruby_version = '>= 1.8.7'
|
72
|
+
s.default_executable = "cuporter"
|
73
|
+
s.executables = [s.default_executable]
|
74
|
+
|
75
|
+
s.files = %w(LICENSE README.textile Rakefile) +
|
76
|
+
FileList["lib/**/*.rb", "spec/**/*.rb", "features/**/*.{feature,rb}", "bin/*"].to_a
|
77
|
+
|
78
|
+
s.require_path = "lib"
|
79
|
+
end
|
80
|
+
|
81
|
+
Rake::GemPackageTask.new(spec) do |p|
|
82
|
+
p.need_zip = true
|
83
|
+
p.need_tar = true
|
84
|
+
p.gem_spec = spec
|
85
|
+
end
|
86
|
+
end
|
data/bin/cuporter
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
require 'cuporter'
|
5
|
+
|
6
|
+
tag_report = Cuporter::TagReport.new(Cuporter::Options[:input_file] || Cuporter::Options[:input_dir])
|
7
|
+
|
8
|
+
formatter = Cuporter::Formatters.const_get(Cuporter::Options[:format])
|
9
|
+
formatter.new(tag_report.scenarios_per_tag, Cuporter::Options[:output]).write
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
require 'optparse'
|
3
|
+
require 'fileutils'
|
4
|
+
|
5
|
+
module Cuporter
|
6
|
+
class Options
|
7
|
+
|
8
|
+
def self.[](key)
|
9
|
+
self.options[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.options
|
13
|
+
self.parse unless @options
|
14
|
+
@options
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.parse
|
18
|
+
@options = {}
|
19
|
+
OptionParser.new(ARGV.dup) do |opts|
|
20
|
+
opts.banner = "Usage: cuporter [options]\n\n"
|
21
|
+
|
22
|
+
opts.on("-i", "--in DIR", "directory of *.feature files\n\t\t\t\t\tDefault: features/**/*.feature\n\n") do |i|
|
23
|
+
@options[:input_dir] = "#{i}/**/*.feature"
|
24
|
+
end
|
25
|
+
opts.on("--input-file FILE", "full file name with extension: 'path/to/file.feature'\n\n") do |file|
|
26
|
+
@options[:input_file] = file
|
27
|
+
end
|
28
|
+
opts.on("-o", "--out FILE", "Output file path\n\n") do |o|
|
29
|
+
full_path = File.expand_path(o)
|
30
|
+
path = full_path.split(File::SEPARATOR)
|
31
|
+
file = path.pop
|
32
|
+
FileUtils.makedirs(path.join(File::SEPARATOR))
|
33
|
+
|
34
|
+
@options[:output] = full_path
|
35
|
+
end
|
36
|
+
opts.on("-f", "--format [pretty|html|csv]", "Output format\n\t\t\t\t\tDefault: pretty text\n\n") do |f|
|
37
|
+
@options[:format] = f.downcase.to_class_name
|
38
|
+
end
|
39
|
+
@options[:format] ||= :Text
|
40
|
+
|
41
|
+
end.parse!
|
42
|
+
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
|
3
|
+
module Cuporter
|
4
|
+
class FeatureParser
|
5
|
+
FEATURE_LINE = /^\s*(Feature:[^#]+)/
|
6
|
+
TAG_LINE = /^\s*(@\w.+)/
|
7
|
+
SCENARIO_LINE = /^\s*(Scenario:[^#]+)$/
|
8
|
+
SCENARIO_OUTLINE_LINE = /^\s*(Scenario Outline:[^#]+)$/
|
9
|
+
SCENARIOS_LINE = /^\s*(Scenarios:[^#]*)$/
|
10
|
+
EXAMPLES_LINE = /^\s*(Examples:[^#]*)$/
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@current_tags = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.parse(feature_content)
|
17
|
+
self.new.parse(feature_content)
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(feature_content)
|
21
|
+
lines = feature_content.split(/\n/)
|
22
|
+
|
23
|
+
lines.each do |line|
|
24
|
+
case line
|
25
|
+
when TAG_LINE
|
26
|
+
# may be more than one tag line
|
27
|
+
@current_tags |= $1.strip.split(/\s+/)
|
28
|
+
when FEATURE_LINE
|
29
|
+
@feature = TagListNode.new($1.strip, @current_tags)
|
30
|
+
@current_tags = []
|
31
|
+
when SCENARIO_LINE
|
32
|
+
# How do we know when we have read all the lines from a "Scenario Outline:"?
|
33
|
+
# One way is when we encounter a "Scenario:"
|
34
|
+
if @scenario_outline
|
35
|
+
@feature.merge(@scenario_outline)
|
36
|
+
@scenario_outline = nil
|
37
|
+
end
|
38
|
+
|
39
|
+
@feature.add_to_tag_node(Node.new($1.strip), @current_tags)
|
40
|
+
@current_tags = []
|
41
|
+
when SCENARIO_OUTLINE_LINE
|
42
|
+
# ... another is when we hit a subsequent "Scenario Outline:"
|
43
|
+
if @scenario_outline
|
44
|
+
@feature.merge(@scenario_outline)
|
45
|
+
@scenario_outline = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
@scenario_outline = TagListNode.new($1.strip, @current_tags)
|
49
|
+
@current_tags = []
|
50
|
+
when EXAMPLES_LINE, SCENARIOS_LINE
|
51
|
+
@scenario_outline.add_to_tag_node(Node.new($1.strip), @feature.universal_tags | @current_tags)
|
52
|
+
@current_tags = []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# EOF is the final way that we know we are finished with a "Scenario Outline"
|
57
|
+
@feature.merge(@scenario_outline) if @scenario_outline
|
58
|
+
return @feature
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
require 'rubygems'
|
3
|
+
require 'erb'
|
4
|
+
require 'builder'
|
5
|
+
|
6
|
+
module Cuporter
|
7
|
+
module Formatters
|
8
|
+
class Html < Writer
|
9
|
+
|
10
|
+
NODE_CLASS = [:tag, :feature, :scenario, :example]
|
11
|
+
|
12
|
+
def write_nodes
|
13
|
+
@report.children.sort.each do |tag_node|
|
14
|
+
write_node(tag_node, 0)
|
15
|
+
end
|
16
|
+
builder
|
17
|
+
end
|
18
|
+
|
19
|
+
def builder
|
20
|
+
@builder ||= Builder::XmlMarkup.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_node(node, indent_level)
|
24
|
+
builder.li do |list_item|
|
25
|
+
list_item.span(node.name, :class => NODE_CLASS[indent_level])
|
26
|
+
if node.has_children?
|
27
|
+
list_item.ul do |list|
|
28
|
+
node.children.sort.each do |child|
|
29
|
+
if child.has_children?
|
30
|
+
write_node(child, indent_level + 1)
|
31
|
+
else
|
32
|
+
list.li(child.name, :class => NODE_CLASS[indent_level + 1])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_binding
|
41
|
+
binding
|
42
|
+
end
|
43
|
+
|
44
|
+
def rhtml
|
45
|
+
ERB.new(RHTML)
|
46
|
+
end
|
47
|
+
|
48
|
+
def write
|
49
|
+
@output.puts rhtml.result(get_binding).reject {|line| /^\s+$/ =~ line}
|
50
|
+
end
|
51
|
+
|
52
|
+
RHTML = %{
|
53
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
54
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
55
|
+
<head>
|
56
|
+
<title>Cucumber Tags</title>
|
57
|
+
<style type="text/css"></style>
|
58
|
+
</head>
|
59
|
+
<body>
|
60
|
+
<ul>
|
61
|
+
<%= write_nodes%>
|
62
|
+
</ul>
|
63
|
+
</body>
|
64
|
+
</html>
|
65
|
+
}
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
module Formatters
|
4
|
+
module TextMethods
|
5
|
+
|
6
|
+
def write
|
7
|
+
@report.children.sort.each do |tag_node|
|
8
|
+
write_node(tag_node, 0)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_node(node, tab_stops)
|
13
|
+
@output.puts "#{self.class::TAB * tab_stops}#{node.name}"
|
14
|
+
node.children.sort.each do |child|
|
15
|
+
@output.puts "#{self.class::TAB + (self.class::TAB * tab_stops)}#{child.name}"
|
16
|
+
child.children.sort.each do |grand_child|
|
17
|
+
if grand_child.has_children?
|
18
|
+
write_node(grand_child, tab_stops + 2)
|
19
|
+
else
|
20
|
+
@output.puts "#{self.class::TAB * tab_stops}#{self.class::TAB * 2}#{grand_child.name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
|
3
|
+
module Cuporter
|
4
|
+
module Formatters
|
5
|
+
class Writer
|
6
|
+
|
7
|
+
def initialize(report, output)
|
8
|
+
@report = report
|
9
|
+
if output
|
10
|
+
@output = File.open(output, "w")
|
11
|
+
else
|
12
|
+
@output = STDOUT
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
class Node
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
attr_reader :name, :children
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
@children = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_children?
|
14
|
+
@children.size > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
# will not add duplicate
|
18
|
+
def add_child(node)
|
19
|
+
@children << node unless has_child?(node)
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_or_create_child(name)
|
23
|
+
child_node = self[name]
|
24
|
+
unless child_node
|
25
|
+
children << Node.new(name)
|
26
|
+
child_node = children.last
|
27
|
+
end
|
28
|
+
child_node
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_by_name(name)
|
32
|
+
children.find {|c| c.name == name.to_s}
|
33
|
+
end
|
34
|
+
alias :[] :find_by_name
|
35
|
+
|
36
|
+
def find(node)
|
37
|
+
children.find {|c| c == node}
|
38
|
+
end
|
39
|
+
alias :has_child? :find
|
40
|
+
|
41
|
+
def name_without_title
|
42
|
+
@name_without_title ||= name.split(/:\s+/).last
|
43
|
+
end
|
44
|
+
|
45
|
+
# sort on name or substring of name after any ':'
|
46
|
+
def <=>(other)
|
47
|
+
name_without_title <=> other.name_without_title
|
48
|
+
end
|
49
|
+
|
50
|
+
# value equivalence
|
51
|
+
def eql?(other)
|
52
|
+
name == other.name && children == other.children
|
53
|
+
end
|
54
|
+
alias :== :eql?
|
55
|
+
|
56
|
+
# Have my children adopt the other node's grandchildren.
|
57
|
+
#
|
58
|
+
# Copy children of other node's top-level, direct descendants to this
|
59
|
+
# node's direct descendants of the same name.
|
60
|
+
def merge(other)
|
61
|
+
other.children.each do |other_child|
|
62
|
+
direct_child = find_or_create_child(other_child.name)
|
63
|
+
new_grandchild = Node.new(other.name)
|
64
|
+
other_child.children.collect do |c|
|
65
|
+
new_grandchild.add_child(c)
|
66
|
+
end
|
67
|
+
direct_child.add_child(new_grandchild)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
# a node with a list of tags that apply to all children
|
4
|
+
class TagListNode < Node
|
5
|
+
|
6
|
+
attr_reader :universal_tags
|
7
|
+
|
8
|
+
def initialize(name, universal_tags)
|
9
|
+
super(name)
|
10
|
+
@universal_tags = universal_tags
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_universal_tags?
|
14
|
+
@universal_tags.size > 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_to_tag_node(node, childs_tags = [])
|
18
|
+
(universal_tags | childs_tags).each do |tag|
|
19
|
+
tag_node = find_or_create_child(tag)
|
20
|
+
tag_node.add_child(node)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
module Cuporter
|
3
|
+
class TagReport
|
4
|
+
|
5
|
+
def initialize(input_file_pattern)
|
6
|
+
@input_file_pattern = input_file_pattern || "features/**/*.feature"
|
7
|
+
end
|
8
|
+
|
9
|
+
def files
|
10
|
+
Dir[@input_file_pattern].collect {|f| File.expand_path f}
|
11
|
+
end
|
12
|
+
|
13
|
+
def scenarios_per_tag
|
14
|
+
tags = TagListNode.new("report",[])
|
15
|
+
files.each do |file|
|
16
|
+
content = File.read(file)
|
17
|
+
tags.merge(FeatureParser.parse(content)) unless content.empty?
|
18
|
+
end
|
19
|
+
tags
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/cuporter.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Copyright 2010 ThoughtWorks, Inc. Licensed under the MIT License
|
2
|
+
require 'cuporter/node'
|
3
|
+
require 'cuporter/tag_list_node'
|
4
|
+
require 'cuporter/feature_parser'
|
5
|
+
require 'cuporter/extensions/string'
|
6
|
+
require 'cuporter/cli/options'
|
7
|
+
require 'cuporter/tag_report'
|
8
|
+
require 'cuporter/formatters/writer'
|
9
|
+
require 'cuporter/formatters/text_methods'
|
10
|
+
require 'cuporter/formatters/text'
|
11
|
+
require 'cuporter/formatters/csv'
|
12
|
+
require 'cuporter/formatters/html'
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Cuporter
|
4
|
+
describe FeatureParser do
|
5
|
+
context "#universal_tags" do
|
6
|
+
context "one tag" do
|
7
|
+
it "returns one tag" do
|
8
|
+
feature = FeatureParser.parse("@wip\nFeature: foo")
|
9
|
+
feature.universal_tags.should == ["@wip"]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "two tags on one line" do
|
14
|
+
it "returns two tags" do
|
15
|
+
feature = FeatureParser.parse(" \n@smoke @wip\nFeature: foo")
|
16
|
+
feature.universal_tags.sort.should == %w[@smoke @wip].sort
|
17
|
+
end
|
18
|
+
end
|
19
|
+
context "two tags on two lines" do
|
20
|
+
it "returns two tags" do
|
21
|
+
feature = FeatureParser.parse(" \n@smoke\n @wip\nFeature: foo")
|
22
|
+
feature.universal_tags.sort.should == %w[@smoke @wip].sort
|
23
|
+
end
|
24
|
+
end
|
25
|
+
context "no tags" do
|
26
|
+
it "returns no tags" do
|
27
|
+
feature = FeatureParser.parse("\nFeature: foo")
|
28
|
+
feature.universal_tags.should == []
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#name" do
|
35
|
+
let(:name) {"Feature: consume a fairly typical feature name, and barf it back up"}
|
36
|
+
context "sentence with comma" do
|
37
|
+
it "returns the full name" do
|
38
|
+
feature = FeatureParser.parse("\n#{name}\n Background: blah")
|
39
|
+
feature.name.should == name
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context "name followed by comment" do
|
43
|
+
it "returns only the full name" do
|
44
|
+
feature = FeatureParser.parse("# Here is a feature comment\n# And another comment\n #{name} # comment text here\n Background: blah")
|
45
|
+
feature.name.should == name
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|