cuporter 0.1.0
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.
- 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
|
+
|