cukable 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +7 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +47 -0
- data/README.md +368 -0
- data/Rakefile +41 -0
- data/bin/cuke2fit +25 -0
- data/cukable.gemspec +26 -0
- data/features/conversion.feature +54 -0
- data/features/cuke_fixture.feature +147 -0
- data/features/slim_json_formatter.feature +153 -0
- data/features/step_definitions/cukable_steps.rb +115 -0
- data/features/support/env.rb +183 -0
- data/lib/cukable.rb +2 -0
- data/lib/cukable/conversion.rb +314 -0
- data/lib/cukable/cuke.rb +305 -0
- data/lib/cukable/helper.rb +221 -0
- data/lib/cukable/slim_json_formatter.rb +366 -0
- data/spec/conversion_spec.rb +275 -0
- data/spec/cuke_spec.rb +134 -0
- data/spec/helper_spec.rb +203 -0
- data/spec/spec_helper.rb +10 -0
- data/vendor/cache/rubyslim-0.1.1.gem +0 -0
- metadata +188 -0
data/lib/cukable/cuke.rb
ADDED
@@ -0,0 +1,305 @@
|
|
1
|
+
# cuke.rb
|
2
|
+
|
3
|
+
# FIXME: This is a hack to support running cucumber features.
|
4
|
+
# May have unwanted side-effects.
|
5
|
+
$:.unshift File.join(File.dirname(__FILE__), '..')
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'diff/lcs/array'
|
10
|
+
|
11
|
+
require 'cukable/helper'
|
12
|
+
require 'cukable/conversion'
|
13
|
+
|
14
|
+
module Cukable
|
15
|
+
|
16
|
+
# Exception raised when a table is not in the expected format
|
17
|
+
class FormatError < Exception
|
18
|
+
end
|
19
|
+
|
20
|
+
# This fixture allows running Cucumber from FitNesse via rubyslim.
|
21
|
+
class Cuke
|
22
|
+
|
23
|
+
include Cukable::Helper
|
24
|
+
include Cukable::Conversion
|
25
|
+
|
26
|
+
# Hash mapping the MD5 digest of a feature to the .json output for that
|
27
|
+
# feature. This tells `do_table` whether a feature has already been
|
28
|
+
# run by the accelerator, and prevents it from being run again.
|
29
|
+
@@output_files = Hash.new
|
30
|
+
|
31
|
+
# Last-run FitNesse suite. Indicates whether a higher-level accelerator
|
32
|
+
# has already run. For example, if `HelloWorld.AaaAccelerator` has already
|
33
|
+
# run, then there's no need to run `HelloWorld.SuiteOne.AaaAccelerator` or
|
34
|
+
# `HelloWorld.SuiteTwo.AaaAccelerator`.
|
35
|
+
@@last_suite_name = nil
|
36
|
+
|
37
|
+
|
38
|
+
# Create the fixture, with optional Cucumber command-line arguments.
|
39
|
+
#
|
40
|
+
# @param [String] cucumber_args
|
41
|
+
# If this constructor is called from a `Cuke` table, then
|
42
|
+
# these arguments will be passed to Cucumber for that single
|
43
|
+
# test. Otherwise, the `cucumber_args` passed to `accelerate`
|
44
|
+
# take precedence.
|
45
|
+
#
|
46
|
+
def initialize(cucumber_args='')
|
47
|
+
# Directory where temporary .feature files will be written
|
48
|
+
@features_dir = File.join('features', 'fitnesse')
|
49
|
+
# Directory where JSON output files will be written by Cucumber
|
50
|
+
@output_dir = 'slim_results'
|
51
|
+
# Cucumber command-line arguments
|
52
|
+
@cucumber_args = cucumber_args
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
# Perform a batch-run of all features found in siblings of the given test
|
57
|
+
# in the FitNesse wiki tree. This only takes effect if `test_name` ends
|
58
|
+
# with `AaaAccelerator`; otherwise, nothing happens. The test results for
|
59
|
+
# each feature are stored in `@@output_files`, indexed by a hash of the
|
60
|
+
# feature contents; the individual tests in the suite will then invoke
|
61
|
+
# `do_table`, which looks up the test results in `@@output_files`.
|
62
|
+
#
|
63
|
+
# @param [String] test_name
|
64
|
+
# Dotted name of the test page in FitNesse. For example,
|
65
|
+
# `'HelloWorld.AaaAccelerator'`
|
66
|
+
# @param [String] cucumber_args
|
67
|
+
# Command-line arguments to pass to Cucumber for this run.
|
68
|
+
# Affects all tests in the suite.
|
69
|
+
#
|
70
|
+
def accelerate(test_name, cucumber_args='')
|
71
|
+
# Remove wiki cruft from the test_path
|
72
|
+
test_name = remove_cruft(test_name)
|
73
|
+
@cucumber_args = cucumber_args
|
74
|
+
|
75
|
+
# Don't run the accelerator unless we're on a page called AaaAccelerator
|
76
|
+
if !(test_name =~ /^.*AaaAccelerator$/)
|
77
|
+
return true
|
78
|
+
else
|
79
|
+
# Get the suite path (everything up to the last '.')
|
80
|
+
parts = test_name.split('.')
|
81
|
+
suite_path = parts[0..-2].join('/')
|
82
|
+
end
|
83
|
+
|
84
|
+
# If a higher-level accelerator has already run, skip this run
|
85
|
+
if @@last_suite_name != nil && suite_path =~ /^#{@@last_suite_name}/
|
86
|
+
return true
|
87
|
+
# Otherwise, this is the top-level accelerator in the suite
|
88
|
+
else
|
89
|
+
@@last_suite_name = suite_path
|
90
|
+
end
|
91
|
+
|
92
|
+
# Find the suite in the FitNesseRoot
|
93
|
+
suite = "FitNesseRoot/" + suite_path
|
94
|
+
|
95
|
+
# Delete and recreate @features_dir and @output_dir
|
96
|
+
# FIXME: May need to be smarter about this--what happens if
|
97
|
+
# two people are running different suites at the same time?
|
98
|
+
# The same suite at the same time?
|
99
|
+
[@features_dir, @output_dir].each do |dir|
|
100
|
+
FileUtils.rm_rf(dir)
|
101
|
+
FileUtils.mkdir(dir)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Reset the digest-to-json map, then fill it in with the
|
105
|
+
# digest and .json output file of each feature that ran
|
106
|
+
@@output_files = Hash.new
|
107
|
+
|
108
|
+
# Write all .feature files and run Cucumber on them
|
109
|
+
feature_filenames = write_suite_features(suite)
|
110
|
+
run_cucumber(feature_filenames, @output_dir)
|
111
|
+
|
112
|
+
# Parse the results out over their sources.
|
113
|
+
return true # Wait for someone to test one of the same tables.
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
# Write `.feature` files for all scenarios found in `suite`,
|
118
|
+
# and return an array of all `.feature` filenames.
|
119
|
+
def write_suite_features(suite)
|
120
|
+
# For all FitNesse content files in the suite
|
121
|
+
feature_filenames = []
|
122
|
+
|
123
|
+
Dir.glob(File.join(suite, '**', 'content.txt')).each do |fitnesse_filename|
|
124
|
+
feature = clean_filename(fitnesse_filename, suite, 'content.txt')
|
125
|
+
number = 0
|
126
|
+
# For all feature tables in the content file
|
127
|
+
fitnesse_to_features(File.open(fitnesse_filename)).each do |table|
|
128
|
+
# Write the table to a .feature file with a unique name
|
129
|
+
feature_filename = File.join(
|
130
|
+
@features_dir, "#{feature}_#{number}.feature")
|
131
|
+
feature_filenames << feature_filename
|
132
|
+
begin
|
133
|
+
write_feature(table, feature_filename)
|
134
|
+
rescue FormatError => err
|
135
|
+
puts "!!!! Error writing #{feature_filename}:"
|
136
|
+
puts err.message
|
137
|
+
puts err.backtrace[0..5].join("\n")
|
138
|
+
puts ".... Continuing anyway."
|
139
|
+
end
|
140
|
+
|
141
|
+
# Store the JSON filename in the digest hash
|
142
|
+
digest = table_digest(table)
|
143
|
+
json_filename = File.join(@output_dir, "#{feature_filename}.json")
|
144
|
+
@@output_files[digest] = json_filename
|
145
|
+
end
|
146
|
+
end
|
147
|
+
return feature_filenames
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
# Execute a single test in a FitNesse TableTable.
|
152
|
+
#
|
153
|
+
# @param [Array] table
|
154
|
+
# Rows in the TableTable, where each row is an Array of strings
|
155
|
+
# containing cell contents.
|
156
|
+
#
|
157
|
+
# @return [Array]
|
158
|
+
# Same structure and number of rows as the input table, but with standard
|
159
|
+
# SliM status indicators and additional HTML formatting for displaying
|
160
|
+
# the test results in place of the original table.
|
161
|
+
#
|
162
|
+
def do_table(table)
|
163
|
+
# If the digest of this table already exists in @output files,
|
164
|
+
# simply return the results that were already generated.
|
165
|
+
existing = @@output_files[table_digest(table)]
|
166
|
+
if existing
|
167
|
+
results = existing
|
168
|
+
# Otherwise, run Cucumber from scratch on this table,
|
169
|
+
# and return the results
|
170
|
+
else
|
171
|
+
# FIXME: Move this to a separate method?
|
172
|
+
# Create @features_dir if it doesn't exist
|
173
|
+
FileUtils.mkdir(@features_dir) unless File.directory?(@features_dir)
|
174
|
+
feature_filename = File.join(@features_dir, 'fitnesse_test.feature')
|
175
|
+
# Create the feature file, run cucumber, return results
|
176
|
+
write_feature(table, feature_filename)
|
177
|
+
run_cucumber([feature_filename], @output_dir)
|
178
|
+
results = File.join(@output_dir, "#{feature_filename}.json")
|
179
|
+
end
|
180
|
+
|
181
|
+
# If the results file exists, parse it, merge with the original table,
|
182
|
+
# and return the results
|
183
|
+
if File.exist?(results)
|
184
|
+
json = JSON.load(File.open(results))
|
185
|
+
merged = merge_table_with_results(table, json)
|
186
|
+
return merged
|
187
|
+
# Otherwise, return an 'ignore' for all rows/cells in the table
|
188
|
+
else
|
189
|
+
return table.collect { |row| row.collect { |cell| 'ignore' } }
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
# Merge the original input table with the actual results, and
|
195
|
+
# return a new table that puts the results section in the correct place,
|
196
|
+
# with all other original rows marked as ignored.
|
197
|
+
#
|
198
|
+
# @param [Array] input_table
|
199
|
+
# The original TableTable content as it came from FitNesse
|
200
|
+
# @param [Array] json_results
|
201
|
+
# JSON results returned from Cucumber, via the SlimJSON formatter
|
202
|
+
#
|
203
|
+
# @return [Array]
|
204
|
+
# Original table with JSON results merged into the corresponding
|
205
|
+
# rows, and all other rows (if any) marked as ignored.
|
206
|
+
#
|
207
|
+
def merge_table_with_results(input_table, json_results)
|
208
|
+
final_results = []
|
209
|
+
# Strip extra stuff from the results to get the original line
|
210
|
+
clean_results = json_results.collect do |row|
|
211
|
+
row.collect do |cell|
|
212
|
+
clean_cell(cell)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
# Perform a context-diff
|
216
|
+
input_table.sdiff(clean_results).each do |diff|
|
217
|
+
# If this row was in the input table, but not in the results,
|
218
|
+
# output it as an ignored row
|
219
|
+
if diff.action == '-'
|
220
|
+
# Ignore all cells in the row
|
221
|
+
final_results << input_table[diff.old_position].collect do |cell|
|
222
|
+
"ignore:#{cell}"
|
223
|
+
end
|
224
|
+
# In all other cases, output the row from json_results
|
225
|
+
else # '=', '+', '!'
|
226
|
+
final_results << json_results[diff.new_position]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
return final_results
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# Write a Cucumber `.feature` file containing the lines of Gherkin text
|
234
|
+
# found in `table`.
|
235
|
+
#
|
236
|
+
# @param [Array] table
|
237
|
+
# TableTable content as it came from FitNesse
|
238
|
+
# @param [String] feature_filename
|
239
|
+
# Name of `.feature` file to write the converted Cucumber feature to
|
240
|
+
#
|
241
|
+
def write_feature(table, feature_filename)
|
242
|
+
# Have 'Feature:' or 'Scenario:' been found in the input?
|
243
|
+
got_feature = false
|
244
|
+
got_scenario = false
|
245
|
+
|
246
|
+
FileUtils.mkdir(@features_dir) unless File.directory?(@features_dir)
|
247
|
+
file = File.open(feature_filename, 'w')
|
248
|
+
|
249
|
+
# Error if there is not exactly one "Feature" row
|
250
|
+
features = table.select { |row| row.first =~ /^\s*Feature:/ }
|
251
|
+
if features.count != 1
|
252
|
+
raise FormatError, "Table needs exactly one 'Feature:' row."
|
253
|
+
end
|
254
|
+
|
255
|
+
# Error if there are no "Scenario" or "Scenario Outline" rows
|
256
|
+
scenarios = table.select { |row| row.first =~ /^\s*Scenario( Outline)?:/ }
|
257
|
+
if scenarios.count < 1:
|
258
|
+
raise FormatError, "Table needs at least one 'Scenario:' or 'Scenario Outline:' row."
|
259
|
+
end
|
260
|
+
|
261
|
+
# Write all other lines from the table
|
262
|
+
table.each do |row|
|
263
|
+
# If this row starts with an empty cell, output remaining cells
|
264
|
+
# as a |-delimited table
|
265
|
+
if row.first.strip == ""
|
266
|
+
file.puts " | " + unescape(row[1..-1].join(" | ")) + " |"
|
267
|
+
# For all other rows, output all cells joined by spaces
|
268
|
+
else
|
269
|
+
# Replace < and > so scenario outlines will work
|
270
|
+
line = unescape(row.join(" "))
|
271
|
+
file.puts line
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
file.close
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
# Run cucumber on `feature_filenames`, and output
|
280
|
+
# results in FitNesse table format to `output_dir`.
|
281
|
+
#
|
282
|
+
# @param [Array] feature_filenames
|
283
|
+
# All `.feature` files to execute
|
284
|
+
# @param [String] output_dir
|
285
|
+
# Where to save the SlimJSON-formatted results
|
286
|
+
#
|
287
|
+
def run_cucumber(feature_filenames, output_dir)
|
288
|
+
req = "--require /home/eric/git/cukable/lib/"
|
289
|
+
format = "--format Cucumber::Formatter::SlimJSON"
|
290
|
+
output = "--out #{output_dir}"
|
291
|
+
args = @cucumber_args
|
292
|
+
features = feature_filenames.join(" ")
|
293
|
+
|
294
|
+
#puts "cucumber #{req} #{format} #{output} #{args} #{features}"
|
295
|
+
system "cucumber #{req} #{format} #{output} #{args} #{features}"
|
296
|
+
|
297
|
+
# TODO: Ensure that the correct number of output files were written
|
298
|
+
#if !File.exist?(@results_filename)
|
299
|
+
#raise "Cucumber failed to write '#{@results_filename}'"
|
300
|
+
#end
|
301
|
+
end
|
302
|
+
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
@@ -0,0 +1,221 @@
|
|
1
|
+
# Helper functions for Cukable
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'digest/md5'
|
5
|
+
|
6
|
+
module Cukable
|
7
|
+
|
8
|
+
# Common/shared methods supporting the Cukable library
|
9
|
+
module Helper
|
10
|
+
|
11
|
+
# Return `filename` with `prefix` and `suffix` removed, and any
|
12
|
+
# path-separators converted to underscores.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# clean_filename('abc/some/path/xyz', 'abc', 'xyz')
|
16
|
+
# #=> 'some_path'
|
17
|
+
#
|
18
|
+
# @param [String] filename
|
19
|
+
# Filename to clean
|
20
|
+
# @param [String] prefix
|
21
|
+
# Leading text to remove
|
22
|
+
# @param [String] suffix
|
23
|
+
# Trailing text to remove
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
# Cleaned filename with prefix and suffix removed
|
27
|
+
#
|
28
|
+
def clean_filename(filename, prefix, suffix)
|
29
|
+
middle = filename.gsub(/^#{prefix}\/(.+)\/#{suffix}$/, '\1')
|
30
|
+
return middle.gsub('/', '_')
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# Remove FitNesse-generated link cruft from a string. Strips `<a ...></a>`
|
35
|
+
# tags, keeping the inner content unless that content is '[?]'.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# remove_cruft('Go to <a href="SomePage">this page</a>')
|
39
|
+
# #=> 'Go to this page'
|
40
|
+
# remove_cruft('See SomePage<a href="SomePage">[?]</a>')
|
41
|
+
# #=> 'See SomePage'
|
42
|
+
#
|
43
|
+
# @param [String] string
|
44
|
+
# The string to remove link-cruft from
|
45
|
+
#
|
46
|
+
# @return [String]
|
47
|
+
# Same string with `<a ...></a>` and `[?]` removed.
|
48
|
+
#
|
49
|
+
def remove_cruft(string)
|
50
|
+
string.gsub(/<a [^>]*>([^<]*)<\/a>/, '\1').gsub('[?]', '')
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# Wikify (CamelCase) the given string, removing spaces, underscores, dashes
|
55
|
+
# and periods, and CamelCasing the remaining words. If this does not result
|
56
|
+
# in a CamelCase word with at least two words in it (that is, if the input was
|
57
|
+
# only a single word), then the last letter in the word is capitalized so
|
58
|
+
# as to make FitNesse happy.
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# wikify('file.extension') #=> 'FileExtension'
|
62
|
+
# wikify('with_underscore') #=> 'WithUnderscore'
|
63
|
+
# wikify('having spaces') #=> 'HavingSpaces'
|
64
|
+
# wikify('foo') #=> 'FoO'
|
65
|
+
#
|
66
|
+
# @param [String] string
|
67
|
+
# String to wikify
|
68
|
+
#
|
69
|
+
# @return [String]
|
70
|
+
# Wikified string
|
71
|
+
#
|
72
|
+
# FIXME: This will not generate valid FitNesse wiki page names for
|
73
|
+
# pathological cases, such as any input that would result in consecutive
|
74
|
+
# capital letters, including words having only two letters in them.
|
75
|
+
#
|
76
|
+
def wikify(string)
|
77
|
+
string.gsub!(/^[a-z]|[_.\s\-]+[a-z]/) { |a| a.upcase }
|
78
|
+
string.gsub!(/[_.\s\-]/, '')
|
79
|
+
if string =~ /(([A-Z][a-z]*){2})/
|
80
|
+
return string
|
81
|
+
else
|
82
|
+
return string.gsub(/.\b/) { |c| c.upcase }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
# Return the given string with any CamelCase words, email addresses, and
|
88
|
+
# URLs escaped with FitNesse's `!-...-!` string-literal markup.
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# literalize('With a CamelCase word')
|
92
|
+
# #=> 'With a !-CamelCase-! word'
|
93
|
+
#
|
94
|
+
# @param [String] string
|
95
|
+
# String to escape CamelCase words in
|
96
|
+
#
|
97
|
+
# @return [String]
|
98
|
+
# Same string with CamelCase words escaped
|
99
|
+
#
|
100
|
+
# FIXME: Literals inside other literals will cause too much escaping!
|
101
|
+
def literalize(string)
|
102
|
+
result = string.strip
|
103
|
+
|
104
|
+
# Literalize email addresses
|
105
|
+
# FitNesse pattern for email addresses, per TextMaker.java:
|
106
|
+
# [\w\-_.]+@[\w\-_.]+\.[\w\-_.]+
|
107
|
+
result.gsub!(/([\w\-_.]+@[\w\-_.]+\.[\w\-_.]+)/, '!-\1-!')
|
108
|
+
|
109
|
+
|
110
|
+
# Literalize CamelCase words
|
111
|
+
# Regex for matching wiki words, according to FitNesse.UserGuide.WikiWord
|
112
|
+
# \b[A-Z](?:[a-z0-9]+[A-Z][a-z0-9]*)+
|
113
|
+
result.gsub!(/(\b[A-Z](?:[a-z0-9]+[A-Z][a-z0-9]*)+)/, '!-\1-!')
|
114
|
+
|
115
|
+
# Literalize URLs
|
116
|
+
# Brain-dead URL matcher, should do the trick in most cases though
|
117
|
+
# (Better to literalize too much than not enough)
|
118
|
+
result.gsub!(/(http[^ ]+)/, '!-\1-!')
|
119
|
+
|
120
|
+
return result
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
# Wikify the given path name, and return a path that's suitable
|
125
|
+
# for use as a FitNesse wiki page path. Any path component having only
|
126
|
+
# a single word in it will have the last letter in that word capitalized.
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# wikify_path('features/account/create.feature')
|
130
|
+
# #=> 'FeatureS/AccounT/CreateFeature'
|
131
|
+
#
|
132
|
+
# @param [String] path
|
133
|
+
# Arbitrary path name to convert
|
134
|
+
#
|
135
|
+
# @return [String]
|
136
|
+
# New path with each component being a WikiWord
|
137
|
+
#
|
138
|
+
def wikify_path(path)
|
139
|
+
wiki_parts = path.split(File::SEPARATOR).collect do |part|
|
140
|
+
wikify(part)
|
141
|
+
end
|
142
|
+
return File.join(wiki_parts)
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
# Return an MD5 digest string for `table`. Any HTML entities and FitNesse
|
147
|
+
# markup in the table is unescaped before the digest is calculated.
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# table_digest(['foo'], ['bar'])
|
151
|
+
# #=> '3858f62230ac3c915f300c664312c63f'
|
152
|
+
# table_digest(['foo', 'baz'])
|
153
|
+
# #=> '80338e79d2ca9b9c090ebaaa2ef293c7'
|
154
|
+
#
|
155
|
+
# @param [Array] table
|
156
|
+
# Array of strings, or nested Array of strings
|
157
|
+
#
|
158
|
+
# @return [String]
|
159
|
+
# Accumulated MD5 digest of all strings in `table`
|
160
|
+
#
|
161
|
+
def table_digest(table)
|
162
|
+
digest = Digest::MD5.new
|
163
|
+
table.flatten.each do |cell|
|
164
|
+
digest.update(unescape(cell))
|
165
|
+
end
|
166
|
+
return digest.to_s
|
167
|
+
end
|
168
|
+
|
169
|
+
|
170
|
+
# Unescape any HTML entities and FitNesse markup in the given string.
|
171
|
+
#
|
172
|
+
# @example
|
173
|
+
# unescape('Some <stuff> to !-unescape-!')
|
174
|
+
# #=> 'Some <stuff> to unescape'
|
175
|
+
#
|
176
|
+
# @param [String] string
|
177
|
+
# The string to unescape HTML entities in
|
178
|
+
#
|
179
|
+
# @return [String]
|
180
|
+
# Original string with HTML entities unescaped, and FitNesse `!-...-!`
|
181
|
+
# markup removed.
|
182
|
+
#
|
183
|
+
def unescape(string)
|
184
|
+
result = CGI.unescapeHTML(string)
|
185
|
+
result.gsub!(/!-(.*?)-!/, '\1')
|
186
|
+
return result
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Return the given string, cleaned of any HTML tags and status indicators
|
191
|
+
# added by the JSON output formatter. The intent here is to take a table
|
192
|
+
# cell from JSON output, and make it match the original FitNesse table
|
193
|
+
# cell.
|
194
|
+
#
|
195
|
+
# @example
|
196
|
+
# clean_cell('pass:Given some <b>bold text</b>')
|
197
|
+
# #=> 'Given some bold text'
|
198
|
+
# clean_cell('pass:Given a step <br>with line break')
|
199
|
+
# #=> 'Given a step'
|
200
|
+
#
|
201
|
+
# @param [String] string
|
202
|
+
# String to clean
|
203
|
+
#
|
204
|
+
# @return [String]
|
205
|
+
# String with any SlimJSON-added stuff removed.
|
206
|
+
#
|
207
|
+
def clean_cell(string)
|
208
|
+
# FIXME: This may not be terribly efficient...
|
209
|
+
# strip first to get a copy of the string
|
210
|
+
result = string.strip
|
211
|
+
# Remove extra stuff added by JSON formatter
|
212
|
+
result.gsub!(/^[^:]*:(.*)$/, '\1') # status indicator
|
213
|
+
result.gsub!(/<b>|<\/b>/, '') # all bold tags
|
214
|
+
result.gsub!(/<br\/?>.*/, '') # <br> and anything that follows
|
215
|
+
result.gsub!(/<span[^>]*>.*<\/span>/, '') # spans and their content
|
216
|
+
result.gsub!(/\(Undefined Step\)/, '') # (Undefined Step)
|
217
|
+
return result.strip
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|