cukable 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/.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
|
+
|