features 0.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of features might be problematic. Click here for more details.

data/Manifest ADDED
@@ -0,0 +1,21 @@
1
+ Manifest
2
+ README.markdown
3
+ Rakefile
4
+ features.gemspec
5
+ features.rb
6
+ lib/ObjC/objc_feature.rb
7
+ lib/ObjC/objc_scenario.rb
8
+ lib/ObjC/objc_step.rb
9
+ lib/ObjC/objc_suite.rb
10
+ lib/Rails/rails_feature.rb
11
+ lib/Rails/rails_scenario.rb
12
+ lib/Rails/rails_step.rb
13
+ lib/Rails/rails_suite.rb
14
+ lib/feature.rb
15
+ lib/objc.rb
16
+ lib/parser.rb
17
+ lib/rails.rb
18
+ lib/scenario.rb
19
+ lib/step.rb
20
+ lib/string_extension.rb
21
+ lib/suite.rb
data/README.markdown ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+
5
+ Echoe.new('features', '0.1.0') do |p|
6
+ p.description = "Plaintext User Stories Parser supporting native programming languages."
7
+ p.url = "http://features.rubyforge.org"
8
+ p.author = "Matthias Hennemeyer"
9
+ p.email = "mhennemeyer@me.com"
10
+ p.ignore_pattern = ["spec/**/*"]
11
+ p.development_dependencies = []
12
+ end
13
+
14
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
data/features.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{features}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Matthias Hennemeyer"]
9
+ s.date = %q{2009-08-30}
10
+ s.description = %q{Plaintext User Stories Parser supporting native programming languages.}
11
+ s.email = %q{mhennemeyer@me.com}
12
+ s.extra_rdoc_files = ["README.markdown", "lib/ObjC/objc_feature.rb", "lib/ObjC/objc_scenario.rb", "lib/ObjC/objc_step.rb", "lib/ObjC/objc_suite.rb", "lib/Rails/rails_feature.rb", "lib/Rails/rails_scenario.rb", "lib/Rails/rails_step.rb", "lib/Rails/rails_suite.rb", "lib/feature.rb", "lib/objc.rb", "lib/parser.rb", "lib/rails.rb", "lib/scenario.rb", "lib/step.rb", "lib/string_extension.rb", "lib/suite.rb"]
13
+ s.files = ["Manifest", "README.markdown", "Rakefile", "features.gemspec", "features.rb", "lib/ObjC/objc_feature.rb", "lib/ObjC/objc_scenario.rb", "lib/ObjC/objc_step.rb", "lib/ObjC/objc_suite.rb", "lib/Rails/rails_feature.rb", "lib/Rails/rails_scenario.rb", "lib/Rails/rails_step.rb", "lib/Rails/rails_suite.rb", "lib/feature.rb", "lib/objc.rb", "lib/parser.rb", "lib/rails.rb", "lib/scenario.rb", "lib/step.rb", "lib/string_extension.rb", "lib/suite.rb"]
14
+ s.homepage = %q{http://features.rubyforge.org}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Features", "--main", "README.markdown"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{features}
18
+ s.rubygems_version = %q{1.3.5}
19
+ s.summary = %q{Plaintext User Stories Parser supporting native programming languages.}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
+ else
27
+ end
28
+ else
29
+ end
30
+ end
data/features.rb ADDED
@@ -0,0 +1,10 @@
1
+ lib = File.dirname(__FILE__) + "/lib"
2
+
3
+ require lib + "/string_extension.rb"
4
+ require lib + "/feature.rb"
5
+ require lib + "/scenario.rb"
6
+ require lib + "/step.rb"
7
+ require lib + "/suite.rb"
8
+ require lib + "/parser.rb"
9
+ require lib + "/rails.rb"
10
+ require lib + "/objc.rb"
@@ -0,0 +1,64 @@
1
+ class ObjcFeature < Feature
2
+ attr_reader :scenarios, :scenario_keyword,
3
+ :title, :body, :parent, :parser,
4
+ :given_scenario_keyword, :keyword
5
+
6
+ def initialize(hash={})
7
+ @title = hash[:title]
8
+ @body = hash[:body]
9
+ @parent = hash[:parent]
10
+ @keyword = hash[:keyword] || "Feature:"
11
+ @scenario_keyword = hash[:scenario_keyword] || "Scenario:"
12
+ @given_scenario_keyword = hash[:given_scenario_keyword] || "GivenScenario:"
13
+
14
+ raise "No title given" unless title
15
+ raise "No body given" unless body
16
+ end
17
+
18
+ def story
19
+ body.split(/#{scenario_keyword}/)[0].split(/#{keyword}\s#{title}/).join(" ").strip
20
+ end
21
+
22
+ def story_html
23
+ story.split("\n").join(" <br />")
24
+ end
25
+
26
+ def parse_scenarios
27
+ title_body_arr = Parser.title_and_body_by_keyword_from_string({
28
+ :string => body,
29
+ :keyword => scenario_keyword
30
+ })
31
+ @scenarios = title_body_arr.map {|hash| ObjcScenario.new(hash.update({
32
+ :parent => self,
33
+ :given_scenario_keyword => given_scenario_keyword
34
+ }))}
35
+ @scenarios.each {|scenario| scenario.collect_steps}
36
+ self
37
+ end
38
+
39
+ def to_s
40
+ <<-END
41
+ @interface #{test_case_name} : OMFeature
42
+ @end
43
+ @implementation #{test_case_name}
44
+ #{scenarios.map {|s| s.to_s}.join(" ")}
45
+ @end
46
+ END
47
+ end
48
+
49
+ def to_html
50
+ <<-END
51
+ <div class="feature">
52
+ <h2 class="feature_title">#{keyword} #{title}</h2>
53
+ <p class="story">
54
+ #{story_html}
55
+ </p>
56
+ #{scenarios.map {|s| s.to_html }.join(" \n")}
57
+ </div>
58
+ END
59
+ end
60
+
61
+ def test_case_name
62
+ "#{title.remove_invalid_chars.split(/\s+/).map {|w| w.capitalize}.join('')}Test"
63
+ end
64
+ end
@@ -0,0 +1,89 @@
1
+ class ObjcScenario < Scenario
2
+ attr_reader :steps, :lines, :given_scenario_keyword,
3
+ :title, :body, :parent, :passed
4
+
5
+ def initialize(hash)
6
+ @title = hash[:title]
7
+ @body = hash[:body]
8
+ @parent = hash[:parent]
9
+ @given_scenario_keyword = hash[:given_scenario_keyword] || "GivenScenario:"
10
+
11
+ raise "No title given" unless title
12
+ raise "No body given" unless body
13
+ end
14
+
15
+ def keyword
16
+ parent.scenario_keyword
17
+ end
18
+
19
+ def to_html
20
+ <<-END
21
+ <div class="scenario #{passed? ? "passed" : "failed"}">
22
+ <h2 class="scenario_title">#{keyword} #{title}</h2>
23
+ #{steps.map {|s| s.to_html}.join(" \n")}
24
+ </div>
25
+ END
26
+ end
27
+
28
+ def passed?
29
+ !!@passed
30
+ end
31
+
32
+ def verify_status(results="")
33
+ test_case_name = parent.test_case_name
34
+ #Test Case '-[SayHelloTest testWithABlankObject]' failed (0.001 seconds).
35
+ results =~ /Test\sCase\s'-\[#{test_case_name}\s#{test_name}\]'\s(\w+)/
36
+ match = $1
37
+ if match =~ /failed/
38
+ @passed = false
39
+ elsif match =~ /passed/
40
+ @passed = true
41
+ else
42
+ raise "Can't read results File"
43
+ end
44
+ end
45
+
46
+ def collect_steps
47
+
48
+ if has_given_scenarios?
49
+ @body = expand_given_scenarios_in_body.strip
50
+ end
51
+
52
+
53
+ @lines = body.split(/\n+/).map {|s| s.strip}
54
+ raise "No Steps found" if lines.empty?
55
+
56
+ @steps = parse_lines
57
+ self
58
+ end
59
+
60
+ def expand_given_scenarios_in_body
61
+ raise "No associated Feature" unless parent
62
+ raise "Associated Feature has no Scenarios" unless parent.scenarios
63
+ body.gsub(/#{given_scenario_keyword}(.*)/) do |m|
64
+ existing_scenario = parent.scenarios.detect {|s| s.title == $1.strip }
65
+ existing_scenario ? existing_scenario.body.strip : ""
66
+ end
67
+ end
68
+
69
+ def parse_lines
70
+ lines.map {|l| ObjcStep.new({:title => l, :body => l}).aggregate!}
71
+ end
72
+
73
+ def has_given_scenarios?
74
+ !!(body =~ /#{given_scenario_keyword}(.*)/)
75
+ end
76
+
77
+ def to_s
78
+ <<-END
79
+ -(void) #{test_name}
80
+ {
81
+ #{steps.map {|s| s.to_s}.join(" ")}
82
+ }
83
+ END
84
+ end
85
+
86
+ def test_name
87
+ "test#{title.remove_invalid_chars.split(/\s+/).map {|w| w.capitalize}.join('')}"
88
+ end
89
+ end
@@ -0,0 +1,70 @@
1
+ class ObjcStep < Step
2
+ attr_reader :message
3
+
4
+ attr_reader :title, :body, :parent
5
+ def initialize(hash)
6
+ @title = hash[:title]
7
+ @body = hash[:body]
8
+ @parent = hash[:parent]
9
+ raise "No title given" unless title
10
+ raise "No body given" unless body
11
+ end
12
+
13
+ def to_html
14
+ s = <<-END
15
+ <h3 class="step">#{title}</h3>
16
+ END
17
+ s.strip
18
+ end
19
+
20
+ def aggregate!
21
+ @message = first_part + args_string
22
+ self
23
+ end
24
+
25
+ def first_part
26
+ body.gsub(/\s+/,"_").gsub(/'[^']*'/, "__").remove_invalid_chars
27
+ end
28
+
29
+ def has_args?
30
+ !args.empty?
31
+ end
32
+
33
+ def args
34
+ @args ||= body.scan(/'([^']*)'/).map {|a| a[0]}
35
+ end
36
+
37
+ def args_string
38
+ if has_args?
39
+ ([":@\"#{args[0]}\""] +
40
+ (args[1..args.length] || []).map { |a| "arg:@\"#{a}\"" }).join(" ")
41
+ else
42
+ ""
43
+ end
44
+ end
45
+
46
+ def to_s
47
+ "[self #{message}];"
48
+ end
49
+
50
+ def parameter_string
51
+ if has_args?
52
+ s = ":(NSString *)arg "
53
+ (args[1..args.length] || []).each_with_index do |a, i|
54
+ s << "arg:(NSString *)arg#{i+2} "
55
+ end
56
+ s
57
+ else
58
+ ""
59
+ end
60
+ end
61
+
62
+ def to_ocmethod
63
+ <<-END
64
+ -(void) #{first_part + parameter_string}
65
+ {
66
+
67
+ }
68
+ END
69
+ end
70
+ end
@@ -0,0 +1,152 @@
1
+ class ObjcSuite < Suite
2
+ attr_reader :feature_files, :feature_files_path,
3
+ :feature_file_suffix, :feature_files_as_strings,
4
+ :features, :test_cases_file,
5
+ :feature_class_header_files, :given_scenario_keyword,
6
+ :feature_keyword, :scenario_keyword,
7
+ :project_name, :passed
8
+
9
+ def initialize(hash)
10
+ @feature_files_path = hash[:feature_files_path]
11
+ @feature_file_suffix = hash[:feature_file_suffix] || "feature"
12
+ @test_cases_file = hash[:test_cases_file]
13
+ @feature_class_header_files = hash[:feature_class_header_files] || ["OMFeature.h"]
14
+ @feature_keyword = hash[:feature_keyword] || "Feature:"
15
+ @scenario_keyword = hash[:scenario_keyword] || "Scenario:"
16
+ @given_scenario_keyword = hash[:given_scenario_keyword] || "GivenScenario:"
17
+ @project_name = hash[:project_name] || "Features"
18
+ @feature_files = all_feature_files
19
+ @feature_files_as_strings = all_feature_files_as_strings
20
+ end
21
+
22
+ def parse_output_file_and_open_in_browser(file)
23
+ results = ""
24
+ File.open(file) do |f|
25
+ f.readlines.each do |l|
26
+ results << l
27
+ end
28
+ end
29
+
30
+ html = parse_results(results).html
31
+
32
+ %x(touch '/tmp/out.html' && echo '#{html}' > /tmp/out.html && open '/tmp/out.html' )
33
+
34
+ end
35
+
36
+
37
+ def parse_results(results="")
38
+ parse
39
+ @passed = true
40
+ features.each do |f|
41
+ f.scenarios.each do |s|
42
+ s.verify_status(results)
43
+ @passed &&= s.passed?
44
+ end
45
+ end
46
+ self
47
+ end
48
+
49
+ def passed?
50
+ @passed
51
+ end
52
+
53
+ def html
54
+ <<-END
55
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
56
+ <html>
57
+ <head>
58
+ <style type="text/css">
59
+ body {
60
+ font-family: Lucida Grande;
61
+ background-color: #FEFEFE;
62
+ padding: 5px;
63
+ }
64
+ * {-webkit-border-radius: 10px;}
65
+ body {background-color: #EEE; -webkit-border-radius: 0px;}
66
+ .feature { background-color: #CCC;padding: 20px; margin: 20px;}
67
+ .scenario { background-color: #EEE; padding: 20px; margin: 20px}
68
+ .failed {border: 1px solid #C88; background-color: #B77; }
69
+ .passed {border: 1px solid #8C8; background-color: #7B7; }
70
+ .feature_title {font-size: 24pt; }
71
+ .story { font-size: 18pt; padding-left: 40px;}
72
+ #indicator { width: 100%; height: 20px; }
73
+ #wrap { background-color: #EEE; padding: 20px }
74
+ </style></head>
75
+ </head>
76
+ <body>
77
+ <div id="wrap">
78
+ <div id="header">
79
+ <div id="indicator" class="#{passed? ? "passed" : "failed"}">&nbsp;</div>
80
+ <h1 class="project_name">#{project_name}</h1>
81
+
82
+ </div>
83
+ #{features.map {|f| f.to_html }.join(" \n")}
84
+ </div>
85
+ </body>
86
+ </html>
87
+ END
88
+ end
89
+
90
+ def valid?
91
+ unique_feature_test_case_names?
92
+ end
93
+
94
+ def unique_feature_test_case_names?
95
+ feature_test_case_name = features.map {|f| f.test_case_name }
96
+ feature_test_case_name == feature_test_case_name.uniq
97
+ end
98
+
99
+ def run
100
+ parse
101
+ raise "Invalid: Duplicated Titles" unless valid?
102
+ File.open(test_cases_file, "w") { |f| f.puts self }
103
+ end
104
+
105
+ def parse
106
+ parse_features
107
+ parse_feature_scenarios
108
+ end
109
+
110
+ def to_s
111
+ <<-END
112
+ #{feature_class_header_files.map { |f| "#import \"" + f + "\"" }.join(" ")}
113
+ #{features.map {|f| f.to_s }.join(" ")}
114
+ END
115
+ end
116
+
117
+ def parse_features
118
+ title_body_arr = Parser.title_and_body_by_keyword_from_string({
119
+ :string => feature_files_as_strings.join(" "),
120
+ :keyword => feature_keyword
121
+ })
122
+ @features = title_body_arr.map {|hash| ObjcFeature.new(hash.update({:keyword => feature_keyword}))}
123
+ end
124
+
125
+ def parse_feature_scenarios
126
+ features.each do |feature|
127
+ feature.parse_scenarios
128
+ end
129
+ end
130
+
131
+ def all_feature_files
132
+ all_entries_in_feature_files_path = Dir.new(feature_files_path).entries
133
+ feature_entries = all_entries_in_feature_files_path.select do |file|
134
+ !!(file =~ /\.#{feature_file_suffix}$/)
135
+ end
136
+ feature_entries.map do |file|
137
+ feature_files_path + "/" + file
138
+ end
139
+ end
140
+
141
+ def all_feature_files_as_strings
142
+ feature_files.map do |file|
143
+ feature_string = ""
144
+ File.open(file) do |f|
145
+ f.readlines.each do |l|
146
+ feature_string << l
147
+ end
148
+ end
149
+ feature_string
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,62 @@
1
+ class RailsFeature < Feature
2
+ attr_reader :scenarios, :scenario_keyword,
3
+ :title, :body, :parent, :parser,
4
+ :given_scenario_keyword, :keyword
5
+
6
+ def initialize(hash={})
7
+ @title = hash[:title]
8
+ @body = hash[:body]
9
+ @parent = hash[:parent]
10
+ @keyword = hash[:keyword] || "Feature:"
11
+ @scenario_keyword = hash[:scenario_keyword] || "Scenario:"
12
+ @given_scenario_keyword = hash[:given_scenario_keyword] || "GivenScenario:"
13
+
14
+ raise "No title given" unless title
15
+ raise "No body given" unless body
16
+ end
17
+
18
+ def story
19
+ body.split(/#{scenario_keyword}/)[0].split(/#{keyword}\s#{title}/).join(" ").strip
20
+ end
21
+
22
+ def story_html
23
+ story.split("\n").join(" <br />")
24
+ end
25
+
26
+ def parse_scenarios
27
+ title_body_arr = Parser.title_and_body_by_keyword_from_string({
28
+ :string => body,
29
+ :keyword => scenario_keyword
30
+ })
31
+ @scenarios = title_body_arr.map {|hash| RailsScenario.new(hash.update({
32
+ :parent => self,
33
+ :given_scenario_keyword => given_scenario_keyword
34
+ }))}
35
+ @scenarios.each {|scenario| scenario.collect_steps}
36
+ self
37
+ end
38
+
39
+ def to_s
40
+ <<-END
41
+ class #{test_case_name} < FeaturesTestCaseClass
42
+ #{scenarios.map {|s| s.to_s}.join(" ")}
43
+ end
44
+ END
45
+ end
46
+
47
+ def to_html
48
+ <<-END
49
+ <div class="feature">
50
+ <h2 class="feature_title">#{keyword} #{title}</h2>
51
+ <p class="story">
52
+ #{story_html}
53
+ </p>
54
+ #{scenarios.map {|s| s.to_html }.join(" \n")}
55
+ </div>
56
+ END
57
+ end
58
+
59
+ def test_case_name
60
+ "#{title.remove_invalid_chars.split(/\s+/).map {|w| w.capitalize}.join('')}Test"
61
+ end
62
+ end
@@ -0,0 +1,85 @@
1
+ class RailsScenario < Scenario
2
+ attr_reader :steps, :lines, :given_scenario_keyword,
3
+ :title, :body, :parent, :passed
4
+
5
+ def initialize(hash)
6
+ @title = hash[:title]
7
+ @body = hash[:body]
8
+ @parent = hash[:parent]
9
+ @given_scenario_keyword = hash[:given_scenario_keyword] || "GivenScenario:"
10
+
11
+ raise "No title given" unless title
12
+ raise "No body given" unless body
13
+ end
14
+
15
+ def keyword
16
+ parent.scenario_keyword
17
+ end
18
+
19
+ def to_html
20
+ <<-END
21
+ <div class="scenario #{passed? ? "passed" : "failed"}">
22
+ <h2 class="scenario_title">#{keyword} #{title}</h2>
23
+ #{steps.map {|s| s.to_html}.join(" \n")}
24
+ </div>
25
+ END
26
+ end
27
+
28
+ def passed?
29
+ !!@passed
30
+ end
31
+
32
+ def verify_status(results="")
33
+ test_case_name = parent.test_case_name
34
+
35
+ # 2) Failure:
36
+ # test_AnotherFailingOne(FeatureTest) [/test/integration/feature_test.rb:16]:
37
+ # <false> is not true.
38
+
39
+ result = (results =~ /^\s*#{test_name}\(#{test_case_name}\)/)
40
+ @passed = result ? false : true
41
+ end
42
+
43
+ def collect_steps
44
+
45
+ if has_given_scenarios?
46
+ @body = expand_given_scenarios_in_body.strip
47
+ end
48
+
49
+
50
+ @lines = body.split(/\n+/).map {|s| s.strip}
51
+ raise "No Steps found" if lines.empty?
52
+
53
+ @steps = parse_lines
54
+ self
55
+ end
56
+
57
+ def expand_given_scenarios_in_body
58
+ raise "No associated Feature" unless parent
59
+ raise "Associated Feature has no Scenarios" unless parent.scenarios
60
+ body.gsub(/#{given_scenario_keyword}(.*)/) do |m|
61
+ existing_scenario = parent.scenarios.detect {|s| s.title == $1.strip }
62
+ existing_scenario ? existing_scenario.body.strip : ""
63
+ end
64
+ end
65
+
66
+ def parse_lines
67
+ lines.map {|l| RailsStep.new({:body => l}).aggregate!}
68
+ end
69
+
70
+ def has_given_scenarios?
71
+ !!(body =~ /#{given_scenario_keyword}(.*)/)
72
+ end
73
+
74
+ def to_s
75
+ <<-END
76
+ def #{test_name}
77
+ #{steps.map {|s| s.to_s}.join("; ")}
78
+ end
79
+ END
80
+ end
81
+
82
+ def test_name
83
+ "test_#{title.remove_invalid_chars.split(/\s+/).map {|w| w.capitalize}.join('')}"
84
+ end
85
+ end
@@ -0,0 +1,60 @@
1
+ class RailsStep < Step
2
+ attr_reader :message
3
+
4
+ attr_reader :body, :parent
5
+ def initialize(hash)
6
+ @body = hash[:body]
7
+ @parent = hash[:parent]
8
+ raise "No body given" unless body
9
+ end
10
+
11
+ def to_html
12
+ s = <<-END
13
+ <h3 class="step">#{body}</h3>
14
+ END
15
+ s.strip
16
+ end
17
+
18
+ def aggregate!
19
+ @message = first_part + args_string
20
+ self
21
+ end
22
+
23
+ def first_part
24
+ body.gsub(/\s+/,"_").gsub(/'[^']*'/, "__").remove_invalid_chars.sub(/./) do |first_char|
25
+ first_char.downcase
26
+ end
27
+ end
28
+
29
+ def has_args?
30
+ !args.empty?
31
+ end
32
+
33
+ def args
34
+ @args ||= body.scan(/'([^']*)'/).map {|a| a[0]}
35
+ end
36
+
37
+ def args_string
38
+ if has_args?
39
+ "(" + args.map {|a| '"' + a.to_s + '"'}.join(", ") + ")"
40
+ else
41
+ ""
42
+ end
43
+ end
44
+
45
+ def to_s
46
+ message
47
+ end
48
+
49
+ def parameter_string
50
+ if has_args?
51
+ s = "(arg"
52
+ (args[1..args.length] || []).each_with_index do |a, i|
53
+ s << ", arg#{i+2}"
54
+ end
55
+ s + ")"
56
+ else
57
+ ""
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,161 @@
1
+ class RailsSuite < Suite
2
+ attr_reader :feature_files, :feature_files_path,
3
+ :feature_file_suffix, :feature_files_as_strings,
4
+ :features, :test_cases_file,
5
+ :feature_keyword, :scenario_keyword,
6
+ :project_name, :passed,
7
+ :given_scenario_keyword, :features_helper
8
+
9
+ def initialize(hash)
10
+ @feature_files_path = hash[:feature_files_path]
11
+ @feature_file_suffix = hash[:feature_file_suffix] || "feature"
12
+ @test_cases_file = hash[:test_cases_file]
13
+ @features_helper = File.expand_path(hash[:features_helper])
14
+ @feature_keyword = hash[:feature_keyword] || "Feature:"
15
+ @scenario_keyword = hash[:scenario_keyword] || "Scenario:"
16
+ @given_scenario_keyword = hash[:given_scenario_keyword] || "GivenScenario:"
17
+ @project_name = hash[:project_name] || "Features"
18
+ @feature_files = all_feature_files
19
+ @feature_files_as_strings = all_feature_files_as_strings
20
+ end
21
+
22
+ def parse_output_file_and_open_in_browser(file)
23
+ results = ""
24
+ File.open(file) do |f|
25
+ f.readlines.each do |l|
26
+ results << l
27
+ end
28
+ end
29
+
30
+ html = parse_results(results).html
31
+
32
+ %x(touch '/tmp/out.html' && echo '#{html}' > /tmp/out.html && open '/tmp/out.html' )
33
+
34
+ end
35
+
36
+ def parse_results_and_open_in_safari(results)
37
+ html = parse_results(results).html
38
+ open_in_safari(html)
39
+ end
40
+
41
+ def open_in_safari(html)
42
+ %x(touch '/tmp/out.html' && echo '#{html}' > /tmp/out.html && open '/tmp/out.html' )
43
+ end
44
+
45
+
46
+ def parse_results(results="")
47
+ parse
48
+ @passed = true
49
+ features.each do |f|
50
+ f.scenarios.each do |s|
51
+ s.verify_status(results)
52
+ @passed &&= s.passed?
53
+ end
54
+ end
55
+ self
56
+ end
57
+
58
+ def passed?
59
+ @passed
60
+ end
61
+
62
+ def html
63
+ <<-END
64
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
65
+ <html>
66
+ <head>
67
+ <style type="text/css">
68
+ body {
69
+ font-family: Lucida Grande;
70
+ background-color: #FEFEFE;
71
+ padding: 5px;
72
+ }
73
+ * {-webkit-border-radius: 10px;}
74
+ body {background-color: #EEE; -webkit-border-radius: 0px;}
75
+ .feature { background-color: #E0E0E0;padding: 20px; margin: 20px;}
76
+ .scenario { background-color: #EEE; padding: 20px; margin: 20px}
77
+ .failed {border: 1px solid #C88; background-color: #B77; }
78
+ .passed {border: 1px solid #8C8; background-color: #7B7; }
79
+ .feature_title {font-size: 24pt; }
80
+ .story { font-size: 18pt; padding-left: 40px;}
81
+ #indicator { width: 100%; height: 20px; }
82
+ #wrap { background-color: #EEE; padding: 20px }
83
+ </style></head>
84
+ </head>
85
+ <body>
86
+ <div id="wrap">
87
+ <div id="header">
88
+ <div id="indicator" class="#{passed? ? "passed" : "failed"}">&nbsp;</div>
89
+ <h1 class="project_name">#{project_name}</h1>
90
+
91
+ </div>
92
+ #{features.map {|f| f.to_html }.join(" \n")}
93
+ </div>
94
+ </body>
95
+ </html>
96
+ END
97
+ end
98
+
99
+ def valid?
100
+ unique_feature_test_case_names?
101
+ end
102
+
103
+ def unique_feature_test_case_names?
104
+ feature_test_case_name = features.map {|f| f.test_case_name }
105
+ feature_test_case_name == feature_test_case_name.uniq
106
+ end
107
+
108
+ def run
109
+ parse
110
+ raise "Invalid: Duplicated Titles" unless valid?
111
+ File.open(test_cases_file, "w") { |f| f.puts self }
112
+ end
113
+
114
+ def parse
115
+ parse_features
116
+ parse_feature_scenarios
117
+ end
118
+
119
+ def to_s
120
+ <<-END
121
+ require "#{features_helper}"
122
+ #{features.map {|f| f.to_s }.join(" ")}
123
+ END
124
+ end
125
+
126
+ def parse_features
127
+ title_body_arr = Parser.title_and_body_by_keyword_from_string({
128
+ :string => feature_files_as_strings.join(" "),
129
+ :keyword => feature_keyword
130
+ })
131
+ @features = title_body_arr.map {|hash| RailsFeature.new(hash.update({:keyword => feature_keyword}))}
132
+ end
133
+
134
+ def parse_feature_scenarios
135
+ features.each do |feature|
136
+ feature.parse_scenarios
137
+ end
138
+ end
139
+
140
+ def all_feature_files
141
+ all_entries_in_feature_files_path = Dir.new(feature_files_path).entries
142
+ feature_entries = all_entries_in_feature_files_path.select do |file|
143
+ !!(file =~ /\.#{feature_file_suffix}$/)
144
+ end
145
+ feature_entries.map do |file|
146
+ feature_files_path + "/" + file
147
+ end
148
+ end
149
+
150
+ def all_feature_files_as_strings
151
+ feature_files.map do |file|
152
+ feature_string = ""
153
+ File.open(file) do |f|
154
+ f.readlines.each do |l|
155
+ feature_string << l
156
+ end
157
+ end
158
+ feature_string
159
+ end
160
+ end
161
+ end
data/lib/feature.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Feature
2
+ end
data/lib/objc.rb ADDED
@@ -0,0 +1,6 @@
1
+ lib = File.dirname(__FILE__) + "/ObjC"
2
+
3
+ require lib + "/objc_feature.rb"
4
+ require lib + "/objc_scenario.rb"
5
+ require lib + "/objc_step.rb"
6
+ require lib + "/objc_suite.rb"
data/lib/parser.rb ADDED
@@ -0,0 +1,37 @@
1
+ class Parser
2
+ attr_reader :string, :keyword, :create, :parsing
3
+ def initialize(hash)
4
+ @string = hash[:string]
5
+ @keyword = hash[:keyword]
6
+ raise "No Keyword given" unless keyword
7
+ raise "Nothing to parse given" unless string
8
+ raise "No #{keyword} given." unless /#{keyword}(.*)/.match(string)
9
+ end
10
+
11
+ def self.title_and_body_by_keyword_from_string(hash)
12
+ parser = self.new(hash)
13
+ titles = parser.parse_titles
14
+ bodies = parser.parse_bodies
15
+ arr = []
16
+ titles.each_with_index do |t,i|
17
+ arr << {:title => t, :body => bodies[i]}
18
+ end
19
+ arr
20
+ end
21
+
22
+ def parse_titles
23
+ string.scan(/(^|\s+)#{keyword}(.*)/).map {|a| a[1].strip}
24
+ end
25
+
26
+ def parse_bodies
27
+ bodies = string.split(/(^|\s+)(#{keyword}.*)/).reject {|s| s.empty? }.reject {|s| s.only_whitespace? }.map {|s| s.strip}
28
+ start = 0
29
+ bodies.each_with_index do |o,i|
30
+ if (o =~ /^\s*#{keyword}.*\s*$/)
31
+ start = i
32
+ break
33
+ end
34
+ end
35
+ bodies[start..bodies.length].select {|s| !(s =~ /^\s*#{keyword}.*\s*$/) }
36
+ end
37
+ end
data/lib/rails.rb ADDED
@@ -0,0 +1,6 @@
1
+ lib = File.dirname(__FILE__) + "/Rails"
2
+
3
+ require lib + "/rails_feature.rb"
4
+ require lib + "/rails_scenario.rb"
5
+ require lib + "/rails_step.rb"
6
+ require lib + "/rails_suite.rb"
data/lib/scenario.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Scenario
2
+ end
data/lib/step.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Step
2
+ end
@@ -0,0 +1,9 @@
1
+ class String
2
+ def remove_invalid_chars
3
+ self.gsub(/[^\w\s]/, "")
4
+ end
5
+
6
+ def only_whitespace?
7
+ !!(self =~ /\A[\s]+\Z/)
8
+ end
9
+ end
data/lib/suite.rb ADDED
@@ -0,0 +1,2 @@
1
+ class Suite
2
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: features
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Hennemeyer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-30 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Plaintext User Stories Parser supporting native programming languages.
17
+ email: mhennemeyer@me.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ - lib/ObjC/objc_feature.rb
25
+ - lib/ObjC/objc_scenario.rb
26
+ - lib/ObjC/objc_step.rb
27
+ - lib/ObjC/objc_suite.rb
28
+ - lib/Rails/rails_feature.rb
29
+ - lib/Rails/rails_scenario.rb
30
+ - lib/Rails/rails_step.rb
31
+ - lib/Rails/rails_suite.rb
32
+ - lib/feature.rb
33
+ - lib/objc.rb
34
+ - lib/parser.rb
35
+ - lib/rails.rb
36
+ - lib/scenario.rb
37
+ - lib/step.rb
38
+ - lib/string_extension.rb
39
+ - lib/suite.rb
40
+ files:
41
+ - Manifest
42
+ - README.markdown
43
+ - Rakefile
44
+ - features.gemspec
45
+ - features.rb
46
+ - lib/ObjC/objc_feature.rb
47
+ - lib/ObjC/objc_scenario.rb
48
+ - lib/ObjC/objc_step.rb
49
+ - lib/ObjC/objc_suite.rb
50
+ - lib/Rails/rails_feature.rb
51
+ - lib/Rails/rails_scenario.rb
52
+ - lib/Rails/rails_step.rb
53
+ - lib/Rails/rails_suite.rb
54
+ - lib/feature.rb
55
+ - lib/objc.rb
56
+ - lib/parser.rb
57
+ - lib/rails.rb
58
+ - lib/scenario.rb
59
+ - lib/step.rb
60
+ - lib/string_extension.rb
61
+ - lib/suite.rb
62
+ has_rdoc: true
63
+ homepage: http://features.rubyforge.org
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --line-numbers
69
+ - --inline-source
70
+ - --title
71
+ - Features
72
+ - --main
73
+ - README.markdown
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: "0"
81
+ version:
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "1.2"
87
+ version:
88
+ requirements: []
89
+
90
+ rubyforge_project: features
91
+ rubygems_version: 1.3.5
92
+ signing_key:
93
+ specification_version: 3
94
+ summary: Plaintext User Stories Parser supporting native programming languages.
95
+ test_files: []
96
+