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
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Bill Melvin
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
== cukehead
|
2
|
+
|
3
|
+
=== Introduction
|
4
|
+
|
5
|
+
Cukehead provides hours (well, maybe a couple minutes) of fun
|
6
|
+
exploring Cucumber feature files in the form of FreeMind mind
|
7
|
+
maps. If you want to create feature files based on a mind map
|
8
|
+
it can do that too but I'm not sure anyone will want to do
|
9
|
+
that.
|
10
|
+
|
11
|
+
Cukehead is the result of the author wanting to learn how Cucumber works
|
12
|
+
in preparation for a Rails project during the same period of time he was
|
13
|
+
exploring mind mapping software in a questionable attempt to become more
|
14
|
+
organized. Somehow the two came together as an exercise to learn how to
|
15
|
+
build a Ruby application.
|
16
|
+
|
17
|
+
|
18
|
+
=== Usage
|
19
|
+
|
20
|
+
cukehead command [options]
|
21
|
+
|
22
|
+
command:
|
23
|
+
map
|
24
|
+
Read Cucumber feature files and create a FreeMind mind map file.
|
25
|
+
|
26
|
+
cuke
|
27
|
+
Read a FreeMind mind map file and create Cucumber feature files.
|
28
|
+
|
29
|
+
options:
|
30
|
+
-h or --help
|
31
|
+
Show the help text.
|
32
|
+
|
33
|
+
-o or --overwrite
|
34
|
+
Overwrite existing output file(s).
|
35
|
+
|
36
|
+
-m FILENAME or --mm-filename FILENAME
|
37
|
+
map: Name of output file (default is mm/cukehead-output.mm).
|
38
|
+
|
39
|
+
-f PATH or --features-path PATH
|
40
|
+
map: Directory containing feature files to read (default is directory
|
41
|
+
named 'features' in current directory).
|
42
|
+
|
43
|
+
cuke: Directory feature files will be written to.
|
44
|
+
|
45
|
+
-s FILENAME or --source-mm FILENAME
|
46
|
+
map: FreeMind mind map file to use as a template for creating
|
47
|
+
the output file. If the template contains a node with the text
|
48
|
+
'Cucumber features:' then the feature nodes will be inserted there.
|
49
|
+
|
50
|
+
=== Resources
|
51
|
+
|
52
|
+
Cucumber project: http://cukes.info/
|
53
|
+
|
54
|
+
FreeMind project: http://freemind.sourceforge.net/wiki/index.php/Main_Page
|
55
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rubygems/specification'
|
4
|
+
require 'date'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
require 'rake/testtask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'cucumber/rake/task'
|
9
|
+
|
10
|
+
GEM = 'cukehead'
|
11
|
+
|
12
|
+
spec = Gem::Specification.new do |s|
|
13
|
+
s.name = "cukehead"
|
14
|
+
s.version = "0.1.1"
|
15
|
+
s.author = "Bill Melvin"
|
16
|
+
s.email = "bill@bogusoft.com"
|
17
|
+
s.homepage = "http://www.bogusoft.com/cukehead/"
|
18
|
+
s.description = s.summary = "A gem that creates a FreeMind mind map from Cucumber feature files and vice versa."
|
19
|
+
|
20
|
+
s.platform = Gem::Platform::RUBY
|
21
|
+
s.has_rdoc = true
|
22
|
+
#s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
|
23
|
+
s.extra_rdoc_files = ["README"]
|
24
|
+
#s.summary = SUMMARY
|
25
|
+
|
26
|
+
# Uncomment this to add a dependency
|
27
|
+
# s.add_dependency "foo"
|
28
|
+
|
29
|
+
s.require_path = 'lib'
|
30
|
+
#s.autorequire = GEM
|
31
|
+
|
32
|
+
#s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
|
33
|
+
|
34
|
+
s.executables = ["cukehead"]
|
35
|
+
|
36
|
+
files = []
|
37
|
+
File.open('Manifest.txt', 'r') {|f| f.each {|line| files << line.strip}}
|
38
|
+
s.files = files
|
39
|
+
end
|
40
|
+
|
41
|
+
#task :default => :spec
|
42
|
+
|
43
|
+
desc "Run specs"
|
44
|
+
Spec::Rake::SpecTask.new do |t|
|
45
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
46
|
+
#t.spec_opts = %w(-fs --color)
|
47
|
+
t.spec_opts = %w(-fs --color --debug)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
52
|
+
pkg.gem_spec = spec
|
53
|
+
end
|
54
|
+
|
55
|
+
#desc "install the gem locally"
|
56
|
+
#task :install => [:package] do
|
57
|
+
# sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
|
58
|
+
#end
|
59
|
+
|
60
|
+
desc "create a gemspec file"
|
61
|
+
task :make_spec do
|
62
|
+
File.open("#{GEM}.gemspec", "w") do |file|
|
63
|
+
file.puts spec.to_ruby
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
desc "run unit tests"
|
69
|
+
Rake::TestTask.new("unit_tests") {|t|
|
70
|
+
t.pattern = 'test/*_test.rb'
|
71
|
+
t.verbose = true
|
72
|
+
t.warning = true
|
73
|
+
}
|
74
|
+
|
75
|
+
|
76
|
+
desc "Run RDoc to generate documentation"
|
77
|
+
Rake::RDocTask.new do |rd|
|
78
|
+
rd.rdoc_dir = 'doc/rdocs'
|
79
|
+
rd.main = 'README'
|
80
|
+
rd.rdoc_files.include 'README', "lib/**/*\.rb"
|
81
|
+
rd.options << '--all'
|
82
|
+
rd.options << '--inline-source'
|
83
|
+
#rd.options << '--line-numbers'
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
desc "Cucumber features"
|
88
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
89
|
+
t.cucumber_opts = "features --format pretty"
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
desc "Make feature files from CukeHead mind map"
|
94
|
+
task 'cukehead' do
|
95
|
+
sh "bin/cukehead cuke -m mm/CukeHead.mm -o"
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
task :cuke => [:cukehead, :features]
|
100
|
+
|
101
|
+
task :default => [:unit_tests, :spec, :cuke]
|
data/bin/cukehead
ADDED
data/lib/cukehead.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'cukehead/app'
|
data/lib/cukehead/app.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'getoptlong'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'cukehead/feature_reader'
|
4
|
+
require 'cukehead/freemind_writer'
|
5
|
+
require 'cukehead/freemind_reader'
|
6
|
+
require 'cukehead/feature_writer'
|
7
|
+
|
8
|
+
module Cukehead
|
9
|
+
|
10
|
+
# The Cukehead::App class is responsible for responding to
|
11
|
+
# command line arguments and providing the main functionality
|
12
|
+
# of the cukehead application.
|
13
|
+
#
|
14
|
+
class App
|
15
|
+
attr_accessor :features_path
|
16
|
+
attr_accessor :mindmap_filename
|
17
|
+
attr_accessor :mindmap_template_filename
|
18
|
+
attr_accessor :do_overwrite
|
19
|
+
attr_reader :errors
|
20
|
+
attr_reader :feature_reader
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@command = ''
|
24
|
+
@features_path = File.join(Dir.getwd, 'features')
|
25
|
+
@mindmap_filename = File.join(Dir.getwd, 'mm', 'cukehead-output.mm')
|
26
|
+
@mindmap_template_filename = ''
|
27
|
+
@feature_reader = nil
|
28
|
+
@mindmap_reader = nil
|
29
|
+
@do_overwrite = false
|
30
|
+
@errors = []
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Main entry point for executing the cukehead application.
|
35
|
+
# Responsible for responding to the command line arguments.
|
36
|
+
#
|
37
|
+
def run
|
38
|
+
get_options
|
39
|
+
if @errors.empty?
|
40
|
+
if @command == 'map'
|
41
|
+
read_features
|
42
|
+
write_mindmap
|
43
|
+
show_errors
|
44
|
+
elsif @command == 'cuke'
|
45
|
+
read_mindmap
|
46
|
+
write_features
|
47
|
+
show_errors
|
48
|
+
else
|
49
|
+
show_help
|
50
|
+
end
|
51
|
+
else
|
52
|
+
show_errors
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def read_features
|
59
|
+
@feature_reader = FeatureReader.new get_source_xml
|
60
|
+
search_path = File.join @features_path, '*.feature'
|
61
|
+
puts "Reading #{search_path}"
|
62
|
+
Dir[search_path].sort.each {|filename|
|
63
|
+
File.open(filename, 'r') {|f|
|
64
|
+
text = f.readlines
|
65
|
+
@feature_reader.extract_features filename, text #unless text.nil?
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def write_mindmap
|
72
|
+
if @feature_reader.nil?
|
73
|
+
@errors << "No features to write (perhaps read_features has not been called)"
|
74
|
+
return false
|
75
|
+
end
|
76
|
+
if @do_overwrite != true and File.exists? @mindmap_filename
|
77
|
+
@errors << "File already exists: #{@mindmap_filename}"
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
dir = File.dirname(@mindmap_filename)
|
81
|
+
FileUtils.mkdir_p(dir) unless File.directory? dir
|
82
|
+
writer = FreemindWriter.new
|
83
|
+
puts "Writing " + @mindmap_filename
|
84
|
+
writer.write_mm @mindmap_filename, @feature_reader.freemind_xml
|
85
|
+
return true
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def default_mm_search_path
|
90
|
+
File.join(Dir.getwd, 'mm', '*.mm')
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def read_mindmap
|
95
|
+
puts "Reading #{@mindmap_filename}"
|
96
|
+
begin
|
97
|
+
@mindmap_reader = FreemindReader.new @mindmap_filename
|
98
|
+
rescue => ex
|
99
|
+
@errors << 'Error in read_mindmap: ' + ex.message
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def write_features
|
105
|
+
if @mindmap_reader.nil?
|
106
|
+
@errors << "No mind map data."
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
begin
|
110
|
+
writer = FeatureWriter.new
|
111
|
+
writer.output_path = @features_path
|
112
|
+
writer.overwrite = @do_overwrite
|
113
|
+
features = @mindmap_reader.get_features
|
114
|
+
if features.empty?
|
115
|
+
@errors << 'No Cucumber features found in the mind map file.'
|
116
|
+
@errors << 'Mind map may be missing a "Cucumber features:" node.'
|
117
|
+
else
|
118
|
+
features.each_key {|filename| puts "Writing #{File.join(@features_path, filename)}"}
|
119
|
+
writer.write_features features
|
120
|
+
@errors << writer.errors unless writer.errors.empty?
|
121
|
+
end
|
122
|
+
rescue => ex
|
123
|
+
@errors << 'Error in write_features: ' + ex.message
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def get_source_xml
|
129
|
+
if @mindmap_template_filename.empty?
|
130
|
+
nil
|
131
|
+
else
|
132
|
+
text = nil
|
133
|
+
File.open(@mindmap_template_filename, 'r') {|f|
|
134
|
+
text = f.readlines
|
135
|
+
}
|
136
|
+
text.join
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def default_mm_file
|
142
|
+
Dir[default_mm_search_path].first
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def get_options
|
147
|
+
mm = ''
|
148
|
+
fp = ''
|
149
|
+
begin
|
150
|
+
opts = GetoptLong.new(
|
151
|
+
['--help', '-h', GetoptLong::NO_ARGUMENT],
|
152
|
+
['--overwrite', '-o', GetoptLong::NO_ARGUMENT],
|
153
|
+
['--mm-filename', '-m', GetoptLong::REQUIRED_ARGUMENT],
|
154
|
+
['--features-path', '-f', GetoptLong::REQUIRED_ARGUMENT],
|
155
|
+
['--source-mm', '-s', GetoptLong::REQUIRED_ARGUMENT]
|
156
|
+
)
|
157
|
+
opts.each do |opt, arg|
|
158
|
+
case
|
159
|
+
when opt == '--help' then @command = 'help'
|
160
|
+
when opt == '--overwrite' then @do_overwrite = true
|
161
|
+
when opt == '--mm-filename' then mm = arg
|
162
|
+
when opt == '--features-path' then fp = arg
|
163
|
+
when opt == '--source-mm' then @mindmap_template_filename = arg
|
164
|
+
end
|
165
|
+
end
|
166
|
+
rescue => ex
|
167
|
+
@errors << ex.message
|
168
|
+
end
|
169
|
+
|
170
|
+
# If features path or mindmap file name was not specified
|
171
|
+
# in option arguments then infer them from any remaining
|
172
|
+
# command line arguments. Specific option takes precidence.
|
173
|
+
ARGV.each do |a|
|
174
|
+
if a == 'map'
|
175
|
+
@command = 'map' if @command == ''
|
176
|
+
elsif a == 'cuke'
|
177
|
+
@command = 'cuke' if @command == ''
|
178
|
+
elsif a.slice(-9, 9) == '/features'
|
179
|
+
fp = a if fp.empty?
|
180
|
+
elsif a.slice(-3, 3) == '.mm'
|
181
|
+
mm = a if mm.empty?
|
182
|
+
else
|
183
|
+
@errors << "Unknown command or option: #{a}"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
@features_path = fp unless fp.empty?
|
188
|
+
@mindmap_filename = mm unless mm.empty?
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def show_errors
|
193
|
+
unless @errors.empty?
|
194
|
+
puts "Errors:"
|
195
|
+
@errors.each {|err| puts err}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
|
200
|
+
def show_help
|
201
|
+
puts <<xxx
|
202
|
+
|
203
|
+
Usage: cukehead command [options]
|
204
|
+
|
205
|
+
command:
|
206
|
+
map
|
207
|
+
Read Cucumber feature files and create a FreeMind mind map file.
|
208
|
+
|
209
|
+
cuke
|
210
|
+
Read a FreeMind mind map file and create Cucumber feature files.
|
211
|
+
|
212
|
+
options:
|
213
|
+
-h or --help
|
214
|
+
Show this help message.
|
215
|
+
|
216
|
+
-o or --overwrite
|
217
|
+
Overwrite existing output file(s).
|
218
|
+
|
219
|
+
-m FILENAME or --mm-filename FILENAME
|
220
|
+
map: Name of output file (default is mm/cukehead-output.mm).
|
221
|
+
|
222
|
+
cuke: Name of mind map file containing Cucumber features.
|
223
|
+
|
224
|
+
-f PATH or --features-path PATH
|
225
|
+
map: Directory containing feature files to read (default is directory
|
226
|
+
named 'features' in current directory).
|
227
|
+
|
228
|
+
cuke: Directory feature files will be written to.
|
229
|
+
|
230
|
+
-s FILENAME or --source-mm FILENAME
|
231
|
+
map: FreeMind mind map file to use as a template for creating
|
232
|
+
the output file. If the template contains a node with the text
|
233
|
+
'Cucumber features:' then the feature nodes will be inserted there.
|
234
|
+
|
235
|
+
xxx
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'cukehead/feature_part'
|
2
|
+
|
3
|
+
module Cukehead
|
4
|
+
|
5
|
+
# Base class for a container that holds a part of a Cucumber
|
6
|
+
# feature file that will be passed to a FreemindBuilder object
|
7
|
+
# once the part has been read.
|
8
|
+
#
|
9
|
+
class FeatureFileSection
|
10
|
+
|
11
|
+
# ===Parameters
|
12
|
+
# <tt>builder</tt> - Instance of FreemindBuilder that will receive this section of the file.
|
13
|
+
#
|
14
|
+
# <tt>title</tt> - String containing the title of the section.
|
15
|
+
#
|
16
|
+
def initialize(builder, title)
|
17
|
+
@builder = builder
|
18
|
+
@part = FeaturePart.new title
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def add(line)
|
23
|
+
@part.add_line(line)
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def set_tags(tags)
|
28
|
+
@part.tags = tags.clone
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def finish
|
33
|
+
raise "Not implemented"
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Serves as a container for the feature description part of a Cucumber
|
40
|
+
# feature file while parsing the file.
|
41
|
+
#
|
42
|
+
class FeatureSection < FeatureFileSection
|
43
|
+
|
44
|
+
def initialize (builder, text, filename)
|
45
|
+
@feature_filename = filename
|
46
|
+
super builder, text
|
47
|
+
end
|
48
|
+
|
49
|
+
def finish
|
50
|
+
@builder.add_feature(@part, @feature_filename)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
class BackgroundSection < FeatureFileSection
|
57
|
+
|
58
|
+
def finish
|
59
|
+
@builder.add_background(@part)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
class ScenarioSection < FeatureFileSection
|
66
|
+
|
67
|
+
def finish
|
68
|
+
@builder.add_scenario(@part)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|