cukehead 0.1.1
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 +20 -0
- data/README +55 -0
- data/Rakefile +101 -0
- data/bin/cukehead +7 -0
- data/lib/cukehead.rb +1 -0
- data/lib/cukehead/app.rb +241 -0
- data/lib/cukehead/feature_file_section.rb +73 -0
- data/lib/cukehead/feature_node.rb +111 -0
- data/lib/cukehead/feature_node_child.rb +58 -0
- data/lib/cukehead/feature_node_tags.rb +41 -0
- data/lib/cukehead/feature_part.rb +25 -0
- data/lib/cukehead/feature_reader.rb +78 -0
- data/lib/cukehead/feature_writer.rb +42 -0
- data/lib/cukehead/freemind_builder.rb +184 -0
- data/lib/cukehead/freemind_reader.rb +69 -0
- data/lib/cukehead/freemind_writer.rb +21 -0
- data/spec/app_spec.rb +275 -0
- data/spec/cukehead_spec.rb +104 -0
- data/spec/feature_file_section_spec.rb +93 -0
- data/spec/feature_node_spec.rb +115 -0
- data/spec/feature_part_spec.rb +37 -0
- data/spec/feature_reader_spec.rb +33 -0
- data/spec/feature_writer_spec.rb +73 -0
- data/spec/freemind_builder_spec.rb +91 -0
- data/spec/freemind_reader_spec.rb +62 -0
- data/spec/freemind_writer_spec.rb +25 -0
- data/spec/spec_helper.rb +88 -0
- metadata +93 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'cukehead/feature_node'
|
3
|
+
|
4
|
+
module Cukehead
|
5
|
+
|
6
|
+
class FreemindReader
|
7
|
+
|
8
|
+
def initialize(filename = nil)
|
9
|
+
@mmdoc = nil
|
10
|
+
read_file filename unless filename.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Loads the text from the specified file into a new REXML::Document
|
14
|
+
#
|
15
|
+
def read_file(filename)
|
16
|
+
File.open(filename, "r") {|f| load_xml(f.read)}
|
17
|
+
end
|
18
|
+
|
19
|
+
# Loads the given XML string into a new REXML::Document.
|
20
|
+
#
|
21
|
+
def load_xml(xml)
|
22
|
+
@mmdoc = REXML::Document.new(xml)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the first <i>REXML::Element</i> containing a TEXT attribute
|
26
|
+
# that matches 'Cucumber features:'.
|
27
|
+
# Returns nil if no match is found.
|
28
|
+
#
|
29
|
+
def cucumber_features_node
|
30
|
+
# XPath is case-sensitive so I'm using the translate function as
|
31
|
+
# described in a blog post titled "Performing a Case In-sensitive
|
32
|
+
# search in an XML Document" by Harish Ranganathan
|
33
|
+
# http://geekswithblogs.net/ranganh/archive/2005/09/12/53520.aspx
|
34
|
+
#
|
35
|
+
# There may be functions in XPath version 2 that provide a better way
|
36
|
+
# to do case-insensitive search but as of this writing REXML only
|
37
|
+
# implements XPath 1.0.
|
38
|
+
#
|
39
|
+
# Because the search is for a specific string, only those characters
|
40
|
+
# need translated to lower case.
|
41
|
+
REXML::XPath.first(@mmdoc, '//node[translate(attribute::TEXT, "CUMBERFATS", "cumberfats")="cucumber features:"]')
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def get_feature_nodes
|
46
|
+
node = cucumber_features_node
|
47
|
+
feature_nodes = []
|
48
|
+
node.each {|e|
|
49
|
+
if e.is_a? REXML::Element
|
50
|
+
text = e.attributes["TEXT"]
|
51
|
+
feature_nodes << FeatureNode.new(e) if text =~ /^feature:.*/i
|
52
|
+
end
|
53
|
+
} unless node.nil?
|
54
|
+
feature_nodes
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def get_features
|
59
|
+
result = Hash.new
|
60
|
+
feature_nodes = get_feature_nodes
|
61
|
+
feature_nodes.each {|feature|
|
62
|
+
result[feature.filename] = feature.to_text
|
63
|
+
}
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cukehead
|
2
|
+
|
3
|
+
class FreemindWriter
|
4
|
+
|
5
|
+
def add_newline_after_tags(xml)
|
6
|
+
t = ""
|
7
|
+
xml.each_char {|c| c == ">" ? t << c + "\n" : t << c}
|
8
|
+
return t
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def write_mm(filename, mmxml)
|
13
|
+
s = add_newline_after_tags mmxml
|
14
|
+
File.open(filename, 'w') do |f|
|
15
|
+
f.write(s)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
data/spec/app_spec.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
require 'cukehead/app'
|
3
|
+
|
4
|
+
describe "Cukehead application" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@testdata_dir = File.dirname(__FILE__) + '/../testdata'
|
8
|
+
@features_dir = @testdata_dir + '/project1/features'
|
9
|
+
File.directory?(@features_dir).should be_true
|
10
|
+
File.directory?($testing_tmp).should be_true
|
11
|
+
@test_mm_filename = File.join $testing_tmp, 'app_spec_test.mm'
|
12
|
+
File.delete @test_mm_filename if File.exists? @test_mm_filename
|
13
|
+
File.exists?(@test_mm_filename).should be_false
|
14
|
+
@app = Cukehead::App.new
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
it "reads a set of Cucumber features and create a FreeMind mind map" do
|
19
|
+
@app.features_path = @features_dir
|
20
|
+
target = @test_mm_filename
|
21
|
+
@app.mindmap_filename = target
|
22
|
+
@app.send :read_features
|
23
|
+
result = @app.send :write_mindmap
|
24
|
+
result.should be_true
|
25
|
+
File.exists?(target).should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
it "looks for feature files in a 'features' sub-directory of the current directory by default" do
|
30
|
+
@app.features_path.should eql Dir.getwd + '/features'
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
it "accepts a features path to specify where to look for feature files" do
|
35
|
+
dir = File.join $testing_tmp, 'features'
|
36
|
+
@app.features_path = dir
|
37
|
+
@app.features_path.should eql dir
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
it "creates 'cukehead-output.mm' in a 'mm' sub-directory of the current directory by default" do
|
42
|
+
@app.mindmap_filename.should eql Dir.getwd + '/mm/cukehead-output.mm'
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
it "accepts a mind map file name to override the default" do
|
47
|
+
@app.mindmap_filename = @test_mm_filename
|
48
|
+
@app.mindmap_filename.should eql @test_mm_filename
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
it "creates the output directory if it does not exist" do
|
53
|
+
outdir = File.join $testing_tmp, 'app_test_dir_create'
|
54
|
+
FileUtils.remove_dir(outdir) if File.directory? outdir
|
55
|
+
File.directory?(outdir).should be_false
|
56
|
+
fn = File.join outdir, 'test.mm'
|
57
|
+
@app.features_path = @features_dir
|
58
|
+
@app.mindmap_filename = fn
|
59
|
+
@app.send :read_features
|
60
|
+
@app.send :write_mindmap
|
61
|
+
File.directory?(outdir).should be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
it "does not overwrite an existing mind map file" do
|
66
|
+
@app.features_path = @features_dir
|
67
|
+
target = @test_mm_filename
|
68
|
+
@app.mindmap_filename = target
|
69
|
+
File.open(target, 'w') {|f| f.write("###")}
|
70
|
+
File.exists?(target).should be_true
|
71
|
+
|
72
|
+
@app.send :read_features
|
73
|
+
@app.send :write_mindmap
|
74
|
+
|
75
|
+
File.open(target, 'r') {|f|
|
76
|
+
s = f.readline
|
77
|
+
s.should eql "###"
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
it "accepts an overwrite option that allows it to replace an exiting mind map file" do
|
83
|
+
@app.features_path = @features_dir
|
84
|
+
target = @test_mm_filename
|
85
|
+
@app.mindmap_filename = target
|
86
|
+
File.open(target, 'w') {|f| f.write("###")}
|
87
|
+
File.exists?(target).should be_true
|
88
|
+
|
89
|
+
@app.send :read_features
|
90
|
+
|
91
|
+
@app.do_overwrite = true
|
92
|
+
@app.send :write_mindmap
|
93
|
+
File.open(target, 'r') {|f|
|
94
|
+
s = f.readline
|
95
|
+
s.should_not eql "###"
|
96
|
+
}
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
it "accepts an optional file name of an existing mind map" do
|
101
|
+
@app.mindmap_template_filename = 'test.mm'
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
it "adds feature nodes under an existing 'Cucumber features:' node" do
|
106
|
+
@app.features_path = @features_dir
|
107
|
+
target = File.join $testing_tmp, 'app_spec_insert_test.mm'
|
108
|
+
@app.mindmap_filename = target
|
109
|
+
source_mm = File.join(@testdata_dir, 'insert_test.mm')
|
110
|
+
@app.mindmap_template_filename = source_mm
|
111
|
+
|
112
|
+
@app.send :read_features
|
113
|
+
|
114
|
+
@app.do_overwrite = true
|
115
|
+
@app.send :write_mindmap
|
116
|
+
mm = ''
|
117
|
+
File.open(target, 'r') {|f|
|
118
|
+
mm = f.readlines
|
119
|
+
}
|
120
|
+
# *!* Wow. This spec is ugly. There must be a better way.
|
121
|
+
doc = REXML::Document.new(mm.join)
|
122
|
+
doc.should_not be_nil
|
123
|
+
node = REXML::XPath.first(doc, '//node[attribute::TEXT="Here"]')
|
124
|
+
node.should_not be_nil
|
125
|
+
child = node.elements.first
|
126
|
+
child.to_s.should match /.*Cucumber features:.*/
|
127
|
+
grandchild = child.elements[2]
|
128
|
+
grandchild.to_s.should match /.*Test feature.*/
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
it "reads files in the features directory in order by name" do
|
133
|
+
fdir = File.join $testing_tmp, 'features'
|
134
|
+
FileUtils.remove_dir(fdir) if File.directory? fdir
|
135
|
+
File.directory?(fdir).should be_false
|
136
|
+
FileUtils.mkdir(fdir)
|
137
|
+
# This may not be a good test because it is possible Dir[] will return the
|
138
|
+
# files in sorted order even though they are written out of order here.
|
139
|
+
File.open(File.join(fdir, 'carrots.feature'), 'w') {|f|f.write("Feature: Carrots\n")}
|
140
|
+
File.open(File.join(fdir, 'apples.feature'), 'w') {|f|f.write("Feature: Apples\n")}
|
141
|
+
File.open(File.join(fdir, 'bananas.feature'), 'w') {|f|f.write("Feature: Bananas\n")}
|
142
|
+
@app.features_path = fdir
|
143
|
+
@app.send :read_features
|
144
|
+
xml = @app.feature_reader.freemind_xml
|
145
|
+
xml.should match /.*Apples.*Bananas.*Carrots.*/m
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
describe "Cukehead application (generating features from mind map)" do
|
152
|
+
|
153
|
+
before do
|
154
|
+
# Define temporary mind map file and make sure the file does not exist.
|
155
|
+
@test_mm_filename = File.join $testing_tmp, 'app_spec_test.mm'
|
156
|
+
File.delete @test_mm_filename if File.exists? @test_mm_filename
|
157
|
+
File.exists?(@test_mm_filename).should be_false
|
158
|
+
|
159
|
+
# Define temporary input directory and create test mind map files.
|
160
|
+
@in_dir = File.join $testing_tmp, 'app_cuke_test_input'
|
161
|
+
FileUtils.mkdir(@in_dir) unless File.directory? @in_dir
|
162
|
+
@in_filename_1 = File.join $testing_tmp, 'test1.mm'
|
163
|
+
File.open(@in_filename_1, 'w') {|f| f.write($testing_freemind_data)}
|
164
|
+
File.exists?(@in_filename_1).should be_true
|
165
|
+
@in_filename_2 = File.join $testing_tmp, 'test2.mm'
|
166
|
+
File.open(@in_filename_2, 'w') {|f| f.write($testing_freemind_data_2)}
|
167
|
+
File.exists?(@in_filename_2).should be_true
|
168
|
+
|
169
|
+
# Define temporary output directory and make sure it does not exist.
|
170
|
+
@out_dir = File.join $testing_tmp, 'app_cuke_test_output'
|
171
|
+
FileUtils.remove_dir(@out_dir) if File.directory? @out_dir
|
172
|
+
File.directory?(@out_dir).should be_false
|
173
|
+
|
174
|
+
# Create an app instance to test.
|
175
|
+
@app = Cukehead::App.new
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
it "looks for a single file matching *.mm in a 'mm' subdirectory of the current directory by default" do
|
180
|
+
result = @app.send :default_mm_search_path
|
181
|
+
result.should eql Dir.getwd + '/mm/*.mm'
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
it "accepts the name of the mind map file to read overriding the default" do
|
186
|
+
# Uses mindmap_filename attribute as source for optional mind map template
|
187
|
+
# file in 'map' mode and as destination in 'cuke' mode.
|
188
|
+
@app.mindmap_filename = @test_mm_filename
|
189
|
+
@app.mindmap_filename.should eql @test_mm_filename
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
it "creates features in a 'features' sub-directory of the current directory by default" do
|
194
|
+
@app.features_path.should eql Dir.getwd + '/features'
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
it "accepts a features path to specify where to write feature files" do
|
199
|
+
# Uses features_path attribute as source directory in 'map' mode and as
|
200
|
+
# destination in 'cuke' mode.
|
201
|
+
dir = File.join $testing_tmp, 'output', 'features'
|
202
|
+
@app.features_path = dir
|
203
|
+
@app.features_path.should eql dir
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
it "creates the output directory if it does not exist" do
|
208
|
+
@app.mindmap_filename = @in_filename_1
|
209
|
+
@app.features_path = @out_dir
|
210
|
+
@app.send :read_mindmap
|
211
|
+
@app.send :write_features
|
212
|
+
|
213
|
+
File.directory?(@out_dir).should be_true
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
it "writes a file for each feature in the mind map" do
|
218
|
+
@app.mindmap_filename = @in_filename_2
|
219
|
+
@app.features_path = @out_dir
|
220
|
+
@app.send :read_mindmap
|
221
|
+
@app.send :write_features
|
222
|
+
a = []
|
223
|
+
Dir[File.join(@out_dir, '*')].each {|fn| a << File.basename(fn)}
|
224
|
+
a.should have(2).files
|
225
|
+
a.should include('first_feature.feature', 'second_feature.feature')
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
it "does not write any files if any one of the files to be written already exists" do
|
230
|
+
FileUtils.mkdir(@out_dir)
|
231
|
+
fn = File.join @out_dir, 'second_feature.feature'
|
232
|
+
File.open(fn, 'w') {|f| f.write('###')}
|
233
|
+
File.exists?(fn).should be_true
|
234
|
+
@app.mindmap_filename = @in_filename_2
|
235
|
+
@app.features_path = @out_dir
|
236
|
+
@app.send :read_mindmap
|
237
|
+
@app.send :write_features
|
238
|
+
@app.errors.should have(1).error
|
239
|
+
@app.errors.first.to_s.should match /exists/i
|
240
|
+
a = Dir[File.join(@out_dir, '*')]
|
241
|
+
a.should have(1).file
|
242
|
+
File.open(fn, 'r') {|t| t.readline.should eql '###' }
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
it "accepts an overwrite option that allows it to replace exiting files" do
|
247
|
+
FileUtils.mkdir(@out_dir)
|
248
|
+
fn = File.join @out_dir, 'second_feature.feature'
|
249
|
+
File.open(fn, 'w') {|f| f.write('###')}
|
250
|
+
File.exists?(fn).should be_true
|
251
|
+
@app.mindmap_filename = @in_filename_2
|
252
|
+
@app.features_path = @out_dir
|
253
|
+
@app.do_overwrite = true
|
254
|
+
@app.send :read_mindmap
|
255
|
+
@app.send :write_features
|
256
|
+
@app.errors.should have(0).errors
|
257
|
+
a = Dir[File.join(@out_dir, '*')]
|
258
|
+
a.should have(2).files
|
259
|
+
File.open(fn, 'r') {|t| t.readline.should_not eql '###' }
|
260
|
+
end
|
261
|
+
|
262
|
+
it "returns an error message if no features were found in the mind map" do
|
263
|
+
File.open(@test_mm_filename, 'w') {|f| f.write($testing_freemind_data_nocukes)}
|
264
|
+
File.exists?(@test_mm_filename).should be_true
|
265
|
+
@app.mindmap_filename = @test_mm_filename
|
266
|
+
@app.features_path = @out_dir
|
267
|
+
@app.do_overwrite = true
|
268
|
+
@app.send :read_mindmap
|
269
|
+
@app.send :write_features
|
270
|
+
@app.errors.should have(2).errors
|
271
|
+
@app.errors[0].to_s.should match /No Cucumber features found/
|
272
|
+
@app.errors[1].to_s.should match /may be missing a "Cucumber features:" node/
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
def run_cukehead(params = '')
|
4
|
+
cmdline = params.empty? ? $cukehead_bin : $cukehead_bin + ' ' + params
|
5
|
+
puts "DEBUG: run$ #{cmdline}"
|
6
|
+
result = `#{cmdline}`
|
7
|
+
puts "DEBUG: result='#{result}'"
|
8
|
+
result
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
describe "cukehead" do
|
13
|
+
|
14
|
+
before do
|
15
|
+
@testdata_dir = File.dirname(__FILE__) + '/../testdata'
|
16
|
+
@features_dir = @testdata_dir + '/project1/features'
|
17
|
+
@source_mm = @testdata_dir + '/project2/mm/testdata.mm'
|
18
|
+
File.directory?(@features_dir).should be_true
|
19
|
+
File.directory?($testing_tmp).should be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
it "should run" do
|
24
|
+
result = system("#{$cukehead_bin}")
|
25
|
+
result.should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
it "should display a help message if no command line arguments are given" do
|
30
|
+
result = run_cukehead
|
31
|
+
result.should match /.*Usage:.*/
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
it "should include available commands and options in the help message" do
|
36
|
+
result = run_cukehead
|
37
|
+
result.should match /\bmap\b/
|
38
|
+
result.should match /\bcuke\b/
|
39
|
+
result.should match /.*--help\b/
|
40
|
+
result.should include ' -h '
|
41
|
+
result.should match /.*--overwrite\b/
|
42
|
+
result.should include ' -o '
|
43
|
+
result.should include ' --mm-filename '
|
44
|
+
result.should include ' -m '
|
45
|
+
result.should include ' --features-path '
|
46
|
+
result.should include ' -f '
|
47
|
+
result.should include ' --source-mm '
|
48
|
+
result.should include ' -s '
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
it "should display a help message if '--help' command line argument is given" do
|
53
|
+
result = run_cukehead '--help'
|
54
|
+
result.should match /.*Usage:.*/
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
it "should read features and create a mind map if 'map' command line argument is given" do
|
59
|
+
params = 'map -o'
|
60
|
+
params << ' --features-path ' + @features_dir
|
61
|
+
params << ' --mm-filename ' + File.join($testing_tmp, 'test-output.mm')
|
62
|
+
result = run_cukehead params
|
63
|
+
result.should match /^Reading.*Writing.*/m
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
it "shows an error message and does not overwrite an existing mind map file" do
|
68
|
+
output_filename = File.join $testing_tmp, 'cukehead_spec_test.mm'
|
69
|
+
File.open(output_filename, 'w') {|f| f.write("###")}
|
70
|
+
File.exists?(output_filename).should be_true
|
71
|
+
|
72
|
+
params = 'map -f ' + @features_dir + ' -m ' + output_filename
|
73
|
+
result = run_cukehead params
|
74
|
+
result.should match /.*already exists.*/
|
75
|
+
|
76
|
+
File.open(output_filename, 'r') {|f|
|
77
|
+
s = f.readline
|
78
|
+
s.should eql "###"
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
it "complains about unknown command line options." do
|
84
|
+
result = run_cukehead '--unknown-option'
|
85
|
+
result.should match /.*error.*unrecognized.*/im
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
it "complains about unknown command line arguments." do
|
90
|
+
result = run_cukehead 'UnknownArgument'
|
91
|
+
result.should match /.*error.*unknown.*/im
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
it "should read a mind map and write features if 'cuke' command is given" do
|
96
|
+
params = 'cuke -o'
|
97
|
+
params << ' --mm-filename ' + @source_mm
|
98
|
+
params << ' --features-path ' + File.join($testing_tmp, 'features')
|
99
|
+
result = run_cukehead params
|
100
|
+
result.should match /^Reading.*Writing.*/m
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
end
|