cukehead 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ require 'rexml/document'
2
+ require 'cukehead/feature_node_tags'
3
+ require 'cukehead/feature_node_child'
4
+
5
+ module Cukehead
6
+
7
+ # Responsible for extracting the attributes of a feature from a XML node
8
+ # (REXML::Element) and providing those attributes as text in Cucumber
9
+ # feature file format.
10
+ #
11
+ class FeatureNode
12
+
13
+ # Extracts the attributes of a feature from a mind map node.
14
+ # ===Parameters
15
+ # <tt>node</tt> - REXML::Element
16
+ #
17
+ def initialize(node)
18
+ @feature_filename = ""
19
+ @description = []
20
+ @backgrounds = []
21
+ @scenarios = []
22
+ @tags = FeatureNodeTags.new
23
+ @title = node.attributes["TEXT"]
24
+ from_mm_node node
25
+ end
26
+
27
+
28
+ # Returns the feature title as a string suitable for use as a file name.
29
+ #
30
+ def title_as_filename
31
+ result = ''
32
+ t = @title.strip.gsub(/^feature:/i, ' ').strip
33
+ t.downcase.gsub(/\ /, '_').scan(/[_0-9a-z]/) {|c| result << c }
34
+ result + '.feature'
35
+ end
36
+
37
+
38
+ # Returns a string that is either the file name given explititly in the
39
+ # mind map or a file name constructed from the feature title.
40
+ #
41
+ def filename
42
+ if @feature_filename.empty?
43
+ title_as_filename
44
+ else
45
+ @feature_filename
46
+ end
47
+ end
48
+
49
+
50
+ # Returns the file name extracted from the given text where the format
51
+ # is '<i>[file: filename.feature]</i>' or '<i>[file: "filename.feature"]</i>'.
52
+ #
53
+ def filename_from(text)
54
+ a = text.index ':'
55
+ if a.nil? then return "" end
56
+ b = text.index ']'
57
+ if b.nil? then return "" end
58
+ a += 1
59
+ if a > 5 and b > a
60
+ text.slice(a, b-a).delete('"').strip
61
+ else
62
+ ""
63
+ end
64
+ end
65
+
66
+
67
+ # Returns a string containing the attributes of this feature as text
68
+ # formatted for output to a Cucumber feature file.
69
+ #
70
+ def to_text
71
+ text = @tags.to_text('') + @title + "\n"
72
+ pad = " "
73
+ @description.each {|line| text += pad + line + "\n"}
74
+ text += "\n"
75
+ @backgrounds.each {|background| text += background.to_text(pad) + "\n"}
76
+ @scenarios.each {|scenario| text += scenario.to_text(pad) + "\n"}
77
+ text
78
+ end
79
+
80
+
81
+ private
82
+
83
+
84
+ # ===Parameters
85
+ # <tt>node</tt> - REXML::Element
86
+ #
87
+ def from_mm_node(node)
88
+ if node.has_elements?
89
+ node.elements.each do |e|
90
+ text = e.attributes["TEXT"]
91
+ if !text.nil?
92
+ case text
93
+ when /^Background:*/i
94
+ @backgrounds << FeatureNodeChild.new(e)
95
+ when /^Scenario:*/i
96
+ @scenarios << FeatureNodeChild.new(e)
97
+ when /^\[file:.*/i
98
+ @feature_filename = filename_from text
99
+ when /^Tags:*/i
100
+ @tags.from_text text
101
+ else
102
+ @description << text
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,58 @@
1
+ require 'rexml/document'
2
+ require 'cukehead/feature_node_tags'
3
+
4
+ module Cukehead
5
+
6
+ # Responsible for extracting the contents of a mind map node representing
7
+ # a sub-section of a feature (such as a Background or Scenario) and
8
+ # providing it as text for a feature file.
9
+ #
10
+ class FeatureNodeChild
11
+
12
+ # Extracts the feature section described in the given node.
13
+ # ===Parameters
14
+ # <tt>node</tt> - REXML::Element
15
+ #
16
+ def initialize(node)
17
+ @description = []
18
+ @tags = FeatureNodeTags.new
19
+ @title = node.attributes["TEXT"]
20
+ from_mm_node node
21
+ end
22
+
23
+
24
+ # Returns the title, tags, and descriptive text extracted from the
25
+ # mind map as a string of text formatted for output to a feature file.
26
+ # ===Parameters
27
+ # <tt>pad</tt> - String of whitespace to use to indent the section.
28
+ #
29
+ def to_text(pad)
30
+ s = "\n"
31
+ @description.each {|d| s += pad + " " + d + "\n"}
32
+ pad + @tags.to_text(pad) + @title + s
33
+ end
34
+
35
+ private
36
+
37
+ # Extracts tags and descriptive text from child nodes of the given node.
38
+ # ===Parameters
39
+ # <tt>node</tt> - REXML::Element
40
+ #
41
+ def from_mm_node(node)
42
+ if node.has_elements?
43
+ node.elements.each do |e|
44
+ text = e.attributes["TEXT"]
45
+ unless text.nil?
46
+ if text =~ /^Tags:*/i
47
+ @tags.from_text text
48
+ else
49
+ @description << text
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,41 @@
1
+ module Cukehead
2
+
3
+ # Responsible for holding a string of tags extracted from a mind map
4
+ # node and providing it in the format used in a Cucumber feature file.
5
+ #
6
+ class FeatureNodeTags
7
+
8
+ def initialize
9
+ @tagstr = ''
10
+ end
11
+
12
+
13
+ # Extracts the substring containing the tags from a string in the format
14
+ # <i>Tags: tag1 tag2</i>
15
+ # ===Parameters
16
+ # <tt>text</tt> - String containing tags from mind map node text.
17
+ #
18
+ def from_text(text)
19
+ a = text.split(':')
20
+ if a.length == 2
21
+ @tagstr = a[1].strip
22
+ end
23
+ end
24
+
25
+
26
+ # Returns the string of tags extracted by from_text in the format
27
+ # needed for output to a feature file.
28
+ # ===Parameters
29
+ # <tt>pad</tt> - String of whitespace to use to indent the tags.
30
+ #
31
+ def to_text(pad)
32
+ if @tagstr.length == 0
33
+ ''
34
+ else
35
+ pad + @tagstr + "\n"
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,25 @@
1
+ module Cukehead
2
+
3
+ # Container for text from part of a feature file (feature description,
4
+ # background, scenario).
5
+ #
6
+ class FeaturePart
7
+ attr_accessor :title
8
+ attr_reader :lines
9
+ attr_accessor :tags
10
+
11
+
12
+ def initialize(title = '')
13
+ @title = title
14
+ @lines = []
15
+ @tags = []
16
+ end
17
+
18
+
19
+ def add_line(line)
20
+ @lines << line if line.strip.length > 0
21
+ end
22
+ end
23
+
24
+ end
25
+
@@ -0,0 +1,78 @@
1
+ require 'cukehead/freemind_builder'
2
+ require 'cukehead/feature_file_section'
3
+
4
+ module Cukehead
5
+
6
+ class FeatureReader
7
+
8
+ # ===Parameters
9
+ # <tt>mm_xml</tt> - Optional string containing XML representation of a
10
+ # FreeMind mind map into which a Cucumber Features node will be inserted.
11
+ #
12
+ def initialize(mm_xml = nil)
13
+ @builder = FreemindBuilder.new(mm_xml)
14
+ end
15
+
16
+
17
+ # ===Parameters
18
+ # <tt>filename</tt> - The name of the file from which text was read.
19
+ #
20
+ #<tt>text</tt> - String containing the text of a Cucumber feature file.
21
+ #
22
+ def extract_features(filename, text)
23
+ @section = nil
24
+ in_literal_text = false
25
+ literal_text = ''
26
+ tags = []
27
+ text.each do |line|
28
+ s = line.strip
29
+ #$stderr.puts "DEBUG: LINE='#{s}'"
30
+ if s == '"""'
31
+ if in_literal_text
32
+ @section.add(literal_text + "\n\"\"\"") unless @section == nil
33
+ in_literal_text = false
34
+ else
35
+ literal_text = "\"\"\"\n"
36
+ in_literal_text = true
37
+ end
38
+ else
39
+ if in_literal_text
40
+ literal_text << line
41
+ else
42
+ case s
43
+ when /^\@.*/i
44
+ tags = s.split(' ')
45
+ when /^Feature:.*/i
46
+ @section.finish unless @section == nil
47
+ @section = FeatureSection.new(@builder, line, filename)
48
+ @section.set_tags tags
49
+ tags.clear
50
+ when /^Background:.*/i
51
+ @section.finish unless @section == nil
52
+ @section = BackgroundSection.new(@builder, line)
53
+ @section.set_tags tags
54
+ tags.clear
55
+ when /^(Scenario|Scenario Outline):.*/i
56
+ @section.finish unless @section == nil
57
+ @section = ScenarioSection.new(@builder, line)
58
+ @section.set_tags tags
59
+ tags.clear
60
+ else
61
+ @section.add(line) unless @section == nil
62
+ end
63
+ end
64
+ end
65
+ end
66
+ @section.finish unless @section == nil
67
+ end
68
+
69
+
70
+ # Returns a string that is the FreeMind mind map XML.
71
+ #
72
+ def freemind_xml
73
+ @builder.xml
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,42 @@
1
+ module Cukehead
2
+
3
+ class FeatureWriter
4
+ attr_accessor :output_path
5
+ attr_accessor :overwrite
6
+ attr_reader :errors
7
+
8
+
9
+ def initialize
10
+ @output_path = File.join(Dir.getwd, 'features')
11
+ @overwrite = false
12
+ @errors = []
13
+ end
14
+
15
+
16
+ # Writes feature files to the location specified by :output_path.
17
+ # ===Parameters
18
+ # <tt>features</tt> - Hash of filename => featuretext.
19
+ #
20
+ def write_features(features)
21
+ FileUtils.mkdir_p(@output_path) unless File.directory? @output_path
22
+ ok = true
23
+ unless @overwrite
24
+ features.each {|fn, text|
25
+ filename = File.join(@output_path, fn)
26
+ if File.exists? filename
27
+ @errors << "Exists: #{filename}"
28
+ ok = false
29
+ end
30
+ }
31
+ end
32
+ if ok
33
+ features.each {|fn, text|
34
+ filename = File.join(@output_path, fn)
35
+ File.open(filename, 'w') {|f| f.write(text)}
36
+ }
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,184 @@
1
+ require 'rexml/document'
2
+
3
+ module Cukehead
4
+
5
+ class FreemindBuilder
6
+
7
+ DEFAULT_XML = "<map version='0.7.1'><node TEXT='New mind map'></node></map>"
8
+ COLOR_FEATURE = '#0000ff'
9
+ COLOR_BACKGROUND = '#ff0000'
10
+ COLOR_SCENARIO_1 = '#3d9140'
11
+ COLOR_SCENARIO_2 = '#cd8500'
12
+ COLOR_TAGS = '#B452CD'
13
+ COLOR_SYSTEM = '#a1a1a1'
14
+ FOLD_FEATURE = true
15
+ FOLD_BACKGROUND = true
16
+ FOLD_SCENARIO = true
17
+
18
+
19
+ # ===Parameters
20
+ # <tt>mm_xml</tt> - String, optional.
21
+ #
22
+ def initialize(mm_xml = nil)
23
+ if mm_xml.nil?
24
+ @mmdoc = REXML::Document.new(DEFAULT_XML)
25
+ else
26
+ @mmdoc = REXML::Document.new(mm_xml)
27
+ end
28
+ @features_path = nil
29
+ @features_node = cucumber_features_node
30
+ @scenario_count = 0
31
+ @current_feature = nil
32
+ end
33
+
34
+
35
+ # ===Parameters
36
+ # <tt>part</tt> - Cukehead::FeaturePart containing feature description.
37
+ #
38
+ # <tt>filename</tt> - String containing name of the source file for this feature.
39
+ #
40
+ def add_feature(part, filename)
41
+ add_features_path(filename) if @features_path.nil?
42
+ new_node = new_feature_node part.title
43
+ e = new_node_element feature_filename_text(filename), COLOR_SYSTEM
44
+ e.add_attribute 'LINK', filename
45
+ new_node.add_element e
46
+ unless part.tags.empty?
47
+ new_node.add_element(new_node_element('Tags: ' + part.tags.join(' '), COLOR_TAGS))
48
+ end
49
+ part.lines.each do |line|
50
+ el = new_node_element line.strip, COLOR_FEATURE
51
+ if line =~ /^\ *(As\ |In\ |I\ ).*$/
52
+ el.add_element new_bold_font_element
53
+ else
54
+ el.add_element new_italic_font_element
55
+ end
56
+ new_node.add_element el
57
+ end
58
+ @current_feature = new_node
59
+ end
60
+
61
+
62
+ # ===Parameters
63
+ # <tt>part</tt> - Cukehead::FeaturePart containing background description.
64
+ #
65
+ def add_background(part)
66
+ raise "No feature defined. Cannot add background section." if @current_feature.nil?
67
+ new_node = @current_feature.add_element(new_node_element(part.title.strip, COLOR_BACKGROUND, FOLD_BACKGROUND))
68
+ unless part.tags.empty?
69
+ new_node.add_element(new_node_element('Tags: ' + part.tags.join(' '), COLOR_TAGS))
70
+ end
71
+ part.lines.each do |line|
72
+ el = new_node_element line.strip, COLOR_BACKGROUND
73
+ if line =~ /^\ *(Given\ |When\ |Then\ |And\ |But\ ).*$/
74
+ el.add_element new_bold_font_element
75
+ else
76
+ el.add_element new_italic_font_element
77
+ end
78
+ new_node.add_element el
79
+ end
80
+ end
81
+
82
+
83
+ # ===Parameters
84
+ # <tt>part</tt> - Cukehead::FeaturePart containing scenario description.
85
+ #
86
+ def add_scenario(part)
87
+ raise "No feature defined. Cannot add scenario section." if @current_feature.nil?
88
+ @scenario_count += 1
89
+ @scenario_count.odd? ? color = COLOR_SCENARIO_1 : color = COLOR_SCENARIO_2
90
+ new_node = @current_feature.add_element(new_node_element(part.title.strip, color, FOLD_SCENARIO))
91
+ unless part.tags.empty?
92
+ new_node.add_element(new_node_element('Tags: ' + part.tags.join(' '), COLOR_TAGS))
93
+ end
94
+ part.lines.each do |line|
95
+ el = new_node_element line.strip, color
96
+ if line =~ /^\ *(Given\ |When\ |Then\ |And\ |But\ ).*$/
97
+ el.add_element new_bold_font_element
98
+ else
99
+ el.add_element new_italic_font_element
100
+ end
101
+ new_node.add_element el
102
+ end
103
+ end
104
+
105
+
106
+ def xml
107
+ @mmdoc.to_s
108
+ end
109
+
110
+ private
111
+
112
+ def new_node_element(text, color = "", folded = false)
113
+ e = REXML::Element.new "node"
114
+ e.add_attribute 'TEXT', text
115
+ e.add_attribute 'COLOR', color if color.length > 0
116
+ e.add_attribute 'FOLDED', 'true' if folded
117
+ e
118
+ end
119
+
120
+
121
+ def new_italic_font_element
122
+ e = REXML::Element.new "font"
123
+ e.add_attribute 'ITALIC', "true"
124
+ e.add_attribute 'NAME', "SansSerif"
125
+ e.add_attribute 'SIZE', "12"
126
+ e
127
+ end
128
+
129
+
130
+ def new_bold_font_element
131
+ e = REXML::Element.new "font"
132
+ e.add_attribute 'BOLD', "true"
133
+ e.add_attribute 'NAME', "SansSerif"
134
+ e.add_attribute 'SIZE', "12"
135
+ e
136
+ end
137
+
138
+
139
+ def add_cucumber_features_node
140
+ node = @mmdoc.root.elements[1]
141
+ e = new_node_element "Cucumber features:"
142
+ node.add_element e
143
+ return e
144
+ end
145
+
146
+
147
+ def cucumber_features_node
148
+ node = REXML::XPath.first(@mmdoc, '//node[attribute::TEXT="Cucumber features:"]')
149
+ if node == nil
150
+ add_cucumber_features_node
151
+ else
152
+ node
153
+ end
154
+ end
155
+
156
+
157
+ def features_path_text
158
+ '[path: ' + @features_path + ']'
159
+ end
160
+
161
+
162
+ def feature_filename_text(filename)
163
+ '[file: ' + File.basename(filename) + ']'
164
+ end
165
+
166
+
167
+ def add_features_path(filename)
168
+ @features_path = File.dirname(File.expand_path(filename))
169
+ e = new_node_element features_path_text, COLOR_SYSTEM
170
+ e.add_attribute 'LINK', @features_path
171
+ @features_node.add_element e
172
+ end
173
+
174
+
175
+ def new_feature_node(title)
176
+ e = new_node_element title.strip, COLOR_FEATURE, FOLD_FEATURE
177
+ e.add_element new_bold_font_element
178
+ @features_node.add_element e
179
+ end
180
+
181
+
182
+ end
183
+
184
+ end