cornucopia 0.1.12

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.
Files changed (107) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +51 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +16 -0
  6. data/LICENSE.TXT +22 -0
  7. data/README.md +341 -0
  8. data/Rakefile +18 -0
  9. data/cornucopia.gemspec +39 -0
  10. data/lib/cornucopia.rb +18 -0
  11. data/lib/cornucopia/capybara/finder_diagnostics.rb +536 -0
  12. data/lib/cornucopia/capybara/finder_extensions.rb +89 -0
  13. data/lib/cornucopia/capybara/install_finder_extensions.rb +105 -0
  14. data/lib/cornucopia/capybara/install_matcher_extensions.rb +39 -0
  15. data/lib/cornucopia/capybara/matcher_extensions.rb +83 -0
  16. data/lib/cornucopia/capybara/page_diagnostics.rb +228 -0
  17. data/lib/cornucopia/cucumber_hooks.rb +38 -0
  18. data/lib/cornucopia/factory_girl/dynamic_association.rb +14 -0
  19. data/lib/cornucopia/rspec_hooks.rb +37 -0
  20. data/lib/cornucopia/site_prism/element_extensions.rb +273 -0
  21. data/lib/cornucopia/site_prism/install_element_extensions.rb +23 -0
  22. data/lib/cornucopia/site_prism/page_application.rb +126 -0
  23. data/lib/cornucopia/source_files/collapse.gif +0 -0
  24. data/lib/cornucopia/source_files/cornucopia.css +162 -0
  25. data/lib/cornucopia/source_files/expand.gif +0 -0
  26. data/lib/cornucopia/source_files/index_base.html +10 -0
  27. data/lib/cornucopia/source_files/index_contents.html +2 -0
  28. data/lib/cornucopia/source_files/more_info.js +87 -0
  29. data/lib/cornucopia/source_files/report_base.html +10 -0
  30. data/lib/cornucopia/source_files/report_contents.html +3 -0
  31. data/lib/cornucopia/spinach_hooks.rb +51 -0
  32. data/lib/cornucopia/util/configuration.rb +493 -0
  33. data/lib/cornucopia/util/configured_report.rb +520 -0
  34. data/lib/cornucopia/util/file_asset.rb +46 -0
  35. data/lib/cornucopia/util/generic_settings.rb +37 -0
  36. data/lib/cornucopia/util/log_capture.rb +97 -0
  37. data/lib/cornucopia/util/pretty_formatter.rb +580 -0
  38. data/lib/cornucopia/util/report_builder.rb +474 -0
  39. data/lib/cornucopia/util/report_formatters.rb +11 -0
  40. data/lib/cornucopia/util/report_table.rb +195 -0
  41. data/lib/cornucopia/version.rb +3 -0
  42. data/lib/tasks/cornucopia_tasks.rake +4 -0
  43. data/spec/dummy/README.rdoc +28 -0
  44. data/spec/dummy/Rakefile +6 -0
  45. data/spec/dummy/app/assets/images/.keep +0 -0
  46. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  47. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  48. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  49. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  50. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  51. data/spec/dummy/app/mailers/.keep +0 -0
  52. data/spec/dummy/app/models/.keep +0 -0
  53. data/spec/dummy/app/models/concerns/.keep +0 -0
  54. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  55. data/spec/dummy/bin/bundle +3 -0
  56. data/spec/dummy/bin/rails +4 -0
  57. data/spec/dummy/bin/rake +4 -0
  58. data/spec/dummy/config.ru +4 -0
  59. data/spec/dummy/config/application.rb +27 -0
  60. data/spec/dummy/config/boot.rb +5 -0
  61. data/spec/dummy/config/cucumber.yml +8 -0
  62. data/spec/dummy/config/database.yml +37 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +29 -0
  65. data/spec/dummy/config/environments/production.rb +80 -0
  66. data/spec/dummy/config/environments/test.rb +36 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  69. data/spec/dummy/config/initializers/inflections.rb +16 -0
  70. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  71. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  72. data/spec/dummy/config/initializers/session_store.rb +3 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +23 -0
  75. data/spec/dummy/config/routes.rb +56 -0
  76. data/spec/dummy/db/schema.rb +16 -0
  77. data/spec/dummy/features/support/env.rb +66 -0
  78. data/spec/dummy/lib/assets/.keep +0 -0
  79. data/spec/dummy/lib/tasks/cucumber.rake +65 -0
  80. data/spec/dummy/public/404.html +58 -0
  81. data/spec/dummy/public/422.html +58 -0
  82. data/spec/dummy/public/500.html +57 -0
  83. data/spec/dummy/public/favicon.ico +0 -0
  84. data/spec/dummy/script/cucumber +10 -0
  85. data/spec/fixtures/sample_page.html +150 -0
  86. data/spec/lib/capybara/finder_diagnostics_spec.rb +517 -0
  87. data/spec/lib/capybara/finder_extensions_spec.rb +328 -0
  88. data/spec/lib/capybara/page_diagnostics_spec.rb +277 -0
  89. data/spec/lib/site_prism/element_extensions_spec.rb +290 -0
  90. data/spec/lib/site_prism/page_application_spec.rb +81 -0
  91. data/spec/lib/util/configuration_spec.rb +254 -0
  92. data/spec/lib/util/configured_report_spec.rb +1058 -0
  93. data/spec/lib/util/file_asset_spec.rb +86 -0
  94. data/spec/lib/util/generic_settings_spec.rb +48 -0
  95. data/spec/lib/util/log_capture_spec.rb +151 -0
  96. data/spec/lib/util/pretty_formatter_spec.rb +694 -0
  97. data/spec/lib/util/report_builder_spec.rb +983 -0
  98. data/spec/lib/util/report_formatters_spec.rb +13 -0
  99. data/spec/lib/util/report_table_exception_spec.rb +21 -0
  100. data/spec/lib/util/report_table_spec.rb +319 -0
  101. data/spec/pages/cornucopia_report_app.rb +10 -0
  102. data/spec/pages/google/email_page.rb +22 -0
  103. data/spec/pages/google/login_page.rb +25 -0
  104. data/spec/rails_helper.rb +43 -0
  105. data/spec/sample_report.rb +45 -0
  106. data/spec/spec_helper.rb +81 -0
  107. metadata +410 -0
@@ -0,0 +1,474 @@
1
+ require "timeout"
2
+ require ::File.expand_path('file_asset', File.dirname(__FILE__))
3
+ require ::File.expand_path('pretty_formatter', File.dirname(__FILE__))
4
+ require ::File.expand_path('report_table', File.dirname(__FILE__))
5
+ require ::File.expand_path('configuration', File.dirname(__FILE__))
6
+
7
+ module Cornucopia
8
+ module Util
9
+ class ReportBuilder
10
+ @@current_report = nil
11
+ @@on_close_blocks = []
12
+
13
+ MAX_OLD_FOLDERS = 5
14
+
15
+ class << self
16
+ def current_report(folder_name = nil, parent_folder = nil)
17
+ if (@@current_report &&
18
+ ((parent_folder && @@current_report.instance_variable_get(:@parent_folder_name) != parent_folder) ||
19
+ (folder_name && @@current_report.instance_variable_get(:@base_folder_name) != folder_name)))
20
+ @@current_report.close
21
+ @@current_report = nil
22
+ end
23
+
24
+ folder_name ||= Cornucopia::Util::Configuration.base_folder
25
+ parent_folder ||= Cornucopia::Util::Configuration.base_folder
26
+
27
+ @@current_report ||= Cornucopia::Util::ReportBuilder.new(folder_name, parent_folder)
28
+ end
29
+
30
+ def new_report(folder_name = nil, parent_folder = nil)
31
+ if (@@current_report)
32
+ @@current_report.close
33
+ @@current_report = nil
34
+ end
35
+
36
+ @@current_report = Cornucopia::Util::ReportBuilder.new(folder_name, parent_folder)
37
+ end
38
+
39
+ def escape_string(value)
40
+ value = value.to_s
41
+ value = value + "" if value.frozen?
42
+ "".html_safe + value.force_encoding("UTF-8")
43
+ end
44
+
45
+ def format_code_refs(value)
46
+ safe_text = Cornucopia::Util::ReportBuilder.escape_string(value)
47
+
48
+ safe_text = safe_text.gsub(/(#{Cornucopia::Util::ReportBuilder.root_folder}|\.\/|(?=(?:^features|^spec)\/))([^\:\n]*\:[^\:\n\>& ]*)/,
49
+ "\\1 <span class=\"cornucopia-app-file\">\\2\\3</span> ").html_safe
50
+
51
+ safe_text
52
+ end
53
+
54
+ def pretty_array(array, format_sub_items = true)
55
+ if (array.is_a?(Array))
56
+ array.map do |value|
57
+ # This is not expected to be called on an array of arrays,
58
+ # block this and just convert sub-arrays to strings.
59
+ format_value = value
60
+ format_value = value.to_s if value.is_a?(Array)
61
+
62
+ if format_sub_items
63
+ Cornucopia::Util::ReportBuilder.pretty_format(format_value).rstrip
64
+ else
65
+ format_value
66
+ end
67
+ end.join("\n").html_safe
68
+ else
69
+ Cornucopia::Util::ReportBuilder.pretty_format(array)
70
+ end
71
+ end
72
+
73
+ # I've seen some objects with pretty_inspect get stuck in an infinite loop (or close to it),
74
+ # so they take literally hours to print. This function uses Timeout to prevent this.
75
+ # either the class prints quickly, or we kill it and try something else.
76
+ #
77
+ # If the something else doesn't work, we just give up...
78
+ def pretty_object(value)
79
+ timed_out = false
80
+ return_value = nil
81
+
82
+ begin
83
+ begin
84
+ timeout_length = Cornucopia::Util::Configuration.print_timeout_min
85
+ if Object.const_defined?("Capybara")
86
+ timeout_length = [timeout_length, ::Capybara.default_wait_time].max
87
+ end
88
+ timeout_length = [timeout_length, 60 * 60].max if Rails.env.development?
89
+
90
+ Timeout::timeout(timeout_length) do
91
+ if value.is_a?(String)
92
+ return_value = value
93
+ elsif value.is_a?(Array)
94
+ return_value = Cornucopia::Util::ReportBuilder.pretty_array(value, false)
95
+ elsif value.respond_to?(:pretty_inspect)
96
+ return_value = value.pretty_inspect
97
+ else
98
+ return_value = value.to_s
99
+ end
100
+ end
101
+ rescue Timeout::Error
102
+ timed_out = true
103
+ end
104
+ rescue Exception => error
105
+ error.to_s
106
+ end
107
+
108
+ # If it timed out or threw an exception, try .to_s
109
+ # That may also timeout or throw an exception, and if it does, give up.
110
+ if timed_out || !return_value
111
+ begin
112
+ Timeout::timeout(timeout_length) do
113
+ return_value = value.to_s
114
+ end
115
+ rescue Timeout::Error
116
+ return_value = "Timed out rendering"
117
+ end
118
+ end
119
+
120
+ return_value
121
+ end
122
+
123
+ def pretty_format(value)
124
+ pretty_text = value
125
+
126
+ pretty_text = Cornucopia::Util::ReportBuilder.pretty_object(pretty_text)
127
+ pretty_text = Cornucopia::Util::ReportBuilder.escape_string(pretty_text)
128
+ pretty_text = Cornucopia::Util::PrettyFormatter.format_string(pretty_text)
129
+ pretty_text = Cornucopia::Util::ReportBuilder.format_code_refs(pretty_text)
130
+
131
+ pretty_text
132
+ end
133
+
134
+ def build_index_section_item(path_name)
135
+ item_path = "".html_safe
136
+
137
+ item_path << " <li>\n".html_safe
138
+ item_path << " <a href=\"#{path_name}\" target=\"_blank\">".html_safe
139
+ item_path << File.dirname(path_name)
140
+ item_path << "</a>\n".html_safe
141
+ item_path << " </li>\n".html_safe
142
+
143
+ item_path
144
+ end
145
+
146
+ def build_index_section(section_name, section_items)
147
+ section = "".html_safe
148
+
149
+ section << "<div class=\"report-block\">".html_safe
150
+ section << " <h4>".html_safe
151
+ section << section_name
152
+ section << "</h4>\n".html_safe
153
+ section << " <ul class=\"index-list\">\n".html_safe
154
+
155
+ section_items.each do |section_item|
156
+ section << Cornucopia::Util::ReportBuilder.build_index_section_item(section_item)
157
+ end
158
+
159
+ section << " </ul>\n".html_safe
160
+ section << "</div>\n".html_safe
161
+
162
+ section
163
+ end
164
+
165
+ def folder_name_to_section_name(folder_name)
166
+ case File.basename(folder_name)
167
+ when Cornucopia::Util::Configuration.base_folder
168
+ "Feature Tests"
169
+ when "diagnostics_rspec_report"
170
+ "RSPEC Tests"
171
+ else
172
+ File.basename(folder_name)
173
+ end
174
+ end
175
+
176
+ def root_folder
177
+ if Object.const_defined?("Rails")
178
+ Rails.root
179
+ else
180
+ FileUtils.pwd
181
+ end
182
+ end
183
+
184
+ def page_dump(page_html)
185
+ "<textarea class=\"cornucopia-page-dump\">#{Cornucopia::Util::ReportBuilder.escape_string(page_html)}</textarea>\n".html_safe
186
+ end
187
+
188
+ def on_close(&block)
189
+ @@on_close_blocks << block
190
+ end
191
+ end
192
+
193
+ def initialize(folder_name = nil, parent_folder = nil)
194
+ @parent_folder_name = parent_folder || Cornucopia::Util::Configuration.base_folder
195
+ @base_folder_name = folder_name || Cornucopia::Util::Configuration.base_folder
196
+ end
197
+
198
+ # This does nothing in a normal report because reports are built as you go.
199
+ # If nothing has been reported though, this will create a report indicating that
200
+ # nothing happened.
201
+ def close
202
+ exceptions = []
203
+
204
+ if @@on_close_blocks
205
+ @@on_close_blocks.each do |on_close_block|
206
+ begin
207
+ on_close_block.yield
208
+ rescue
209
+ exceptions << $!
210
+ end
211
+ end
212
+ end
213
+
214
+ if File.exists?(report_contents_page_name)
215
+ if Cornucopia::Util::Configuration.open_report_after_generation(@base_folder_name)
216
+ # `open #{report_base_page_name}` rescue nil
217
+ system("open #{report_base_page_name}") rescue nil
218
+ end
219
+ else
220
+ initialize_report_files
221
+ File.open(report_contents_page_name, "a:UTF-8") do |write_file|
222
+ write_file.write %Q[<p class=\"cornucopia-no-errors\">No Errors to report</p>]
223
+ write_file.write "\n"
224
+ end
225
+ end
226
+
227
+ if self == @@current_report
228
+ @@current_report = nil
229
+ end
230
+
231
+ exceptions.each do |exception|
232
+ raise exception
233
+ end
234
+ end
235
+
236
+ def report_folder_name
237
+ unless @report_folder_name
238
+ @report_folder_name = File.join(index_folder_name, "#{@base_folder_name}/")
239
+
240
+ backup_report_folder
241
+ delete_old_folders
242
+ end
243
+
244
+ @report_folder_name
245
+ end
246
+
247
+ def index_folder_name
248
+ unless @index_folder_name
249
+ @index_folder_name = File.join(Cornucopia::Util::ReportBuilder.root_folder, "#{@parent_folder_name}/")
250
+
251
+ FileUtils.mkdir_p @index_folder_name
252
+ end
253
+
254
+ @index_folder_name
255
+ end
256
+
257
+ def backup_report_folder
258
+ if Dir.exists?(@report_folder_name)
259
+ if File.exists?(File.join(@report_folder_name, "index.html"))
260
+ update_time = File.ctime(File.join(@report_folder_name, "index.html"))
261
+ else
262
+ update_time = File.ctime(@report_folder_name)
263
+ end
264
+
265
+ # ensure the name is unique...
266
+ new_sub_dir = File.join(index_folder_name, "#{@base_folder_name}_#{update_time.strftime("%Y_%m_%d_%H_%M_%S")}").to_s
267
+ index = 0
268
+ while Dir.exists?(new_sub_dir)
269
+ if new_sub_dir[-1 * "_alt_#{index}".length..-1] == "_alt_#{index}"
270
+ new_sub_dir = new_sub_dir[0..-1 * "_alt_#{index}".length - 1]
271
+ end
272
+
273
+ index += 1
274
+ new_sub_dir += "_alt_#{index}"
275
+ end
276
+
277
+ FileUtils.mv @report_folder_name,
278
+ new_sub_dir
279
+ end
280
+ end
281
+
282
+ def delete_old_folders
283
+ old_directories = Dir[File.join(index_folder_name, "#{@base_folder_name}_*")].
284
+ map { |dir| File.directory?(dir) ? dir : nil }.compact
285
+
286
+ if Array.wrap(old_directories).length > MAX_OLD_FOLDERS
287
+ old_directories.each_with_index do |dir, index|
288
+ break if index >= old_directories.length - MAX_OLD_FOLDERS
289
+
290
+ FileUtils.rm_rf dir
291
+ end
292
+ end
293
+ end
294
+
295
+ def rebuild_index_page
296
+ index_folder = index_folder_name
297
+
298
+ FileUtils.mkdir_p index_folder_name
299
+ FileUtils.rm_rf index_contents_page_name
300
+
301
+ FileAsset.asset("index_base.html").create_file(File.join(index_folder, "index.html"))
302
+ FileAsset.asset("index_contents.html").add_file(File.join(index_folder, "report_contents.html"))
303
+ FileAsset.asset("cornucopia.css").add_file(File.join(index_folder, "cornucopia.css"))
304
+
305
+ index_file = "".html_safe
306
+ if File.exists?(File.join(Cornucopia::Util::ReportBuilder.root_folder, "coverage/index.html"))
307
+ index_file << Cornucopia::Util::ReportBuilder.build_index_section("Coverage", ["../coverage/index.html"])
308
+ end
309
+
310
+ last_folder = nil
311
+ group_items = []
312
+ Dir[File.join(@index_folder_name, "*")].sort.each do |directory_item|
313
+ if File.directory?(directory_item) && File.exists?(File.join(directory_item, "index.html"))
314
+ directory_item = directory_item[@index_folder_name.to_s.length..-1]
315
+
316
+ if last_folder
317
+ if directory_item =~ /^#{last_folder}_/
318
+ group_items << File.join(directory_item, "index.html")
319
+ else
320
+ unless group_items.empty?
321
+ index_file << Cornucopia::Util::ReportBuilder.build_index_section(
322
+ Cornucopia::Util::ReportBuilder.folder_name_to_section_name(last_folder), group_items)
323
+ end
324
+
325
+ last_folder = directory_item
326
+ group_items = [File.join(last_folder, "index.html")]
327
+ end
328
+ else
329
+ last_folder = directory_item
330
+ group_items = [File.join(last_folder, "index.html")]
331
+ end
332
+ end
333
+ end
334
+
335
+ unless group_items.empty?
336
+ index_file << Cornucopia::Util::ReportBuilder.build_index_section(
337
+ Cornucopia::Util::ReportBuilder.folder_name_to_section_name(last_folder), group_items)
338
+ end
339
+
340
+ File.open(index_contents_page_name, "a:UTF-8") do |write_file|
341
+ write_file << index_file
342
+ end
343
+ end
344
+
345
+ def report_base_page_name
346
+ File.join(report_folder_name, "index.html")
347
+ end
348
+
349
+ def report_contents_page_name
350
+ File.join(report_folder_name, "report_contents.html")
351
+ end
352
+
353
+ def index_base_page_name
354
+ File.join(index_folder_name, "index.html")
355
+ end
356
+
357
+ def index_contents_page_name
358
+ File.join(index_folder_name, "report_contents.html")
359
+ end
360
+
361
+ def initialize_report_files()
362
+ support_folder_name = report_folder_name
363
+
364
+ FileUtils.mkdir_p @report_folder_name
365
+
366
+ unless File.exists?(report_base_page_name)
367
+ FileAsset.asset("report_base.html").add_file(File.join(support_folder_name, "index.html"))
368
+ rebuild_index_page
369
+ end
370
+
371
+ FileAsset.asset("report_contents.html").add_file(File.join(support_folder_name, "report_contents.html"))
372
+ FileAsset.asset("collapse.gif").add_file(File.join(support_folder_name, "collapse.gif"))
373
+ FileAsset.asset("expand.gif").add_file(File.join(support_folder_name, "expand.gif"))
374
+ FileAsset.asset("more_info.js").add_file(File.join(support_folder_name, "more_info.js"))
375
+ FileAsset.asset("cornucopia.css").add_file(File.join(support_folder_name, "cornucopia.css"))
376
+ end
377
+
378
+ def open_report_contents_file(&block)
379
+ initialize_report_files
380
+
381
+ File.open(report_contents_page_name, "a:UTF-8", &block)
382
+ end
383
+
384
+ def within_section(section_text, &block)
385
+ begin
386
+ open_report_contents_file do |write_file|
387
+ write_file.write "<div class=\"cornucopia-section\">\n"
388
+ write_file.write "<p class=\"cornucopia-section-label\">#{Cornucopia::Util::ReportBuilder.escape_string(section_text)}</p>\n".
389
+ force_encoding("UTF-8")
390
+ end
391
+ block.yield self
392
+ ensure
393
+ open_report_contents_file do |write_file|
394
+ write_file.write "</div>\n"
395
+ end
396
+ end
397
+ end
398
+
399
+ def within_table(options = {}, &block)
400
+ report_table = nil
401
+ options_report_table = options[:report_table]
402
+
403
+ begin
404
+ Cornucopia::Util::ReportTable.new(options) do |table|
405
+ report_table = table
406
+ block.yield(report_table)
407
+ end
408
+ ensure
409
+ if report_table && !options_report_table
410
+ open_report_contents_file do |write_file|
411
+ write_file.write report_table.full_table.force_encoding("UTF-8")
412
+ end
413
+ end
414
+ end
415
+ end
416
+
417
+ def image_link(image_file_name)
418
+ dest_file_name = unique_file_name(File.basename(image_file_name))
419
+
420
+ FileUtils.mv image_file_name, File.join(report_folder_name, dest_file_name)
421
+
422
+ "<img class=\"cornucopia-section-image\" src=\"./#{dest_file_name}\" />".html_safe
423
+ end
424
+
425
+ def page_text(page_html)
426
+ padded_frame("<textarea class=\"cornucopia-page-dump\">#{Cornucopia::Util::ReportBuilder.escape_string(page_html)}</textarea>\n".html_safe)
427
+ end
428
+
429
+ def page_frame(page_html)
430
+ dump_file_name = unique_file_name("page_dump.html")
431
+
432
+ File.open(File.join(report_folder_name, dump_file_name), "w:UTF-8") do |dump_file|
433
+ page_text = page_html.to_s
434
+ page_text = page_text + "" if page_text.frozen?
435
+ dump_file.write page_text.force_encoding("UTF-8")
436
+ end
437
+
438
+ padded_frame("<iframe src=\"#{dump_file_name}\" class=\"cornucopia-sample-frame\"></iframe>\n".html_safe)
439
+ end
440
+
441
+ def padded_frame(padded_text)
442
+ padded_val = "<div class=\"padded-frame\">".html_safe
443
+ padded_val << padded_text
444
+ padded_val << "</div>".html_safe
445
+ end
446
+
447
+ def unique_file_name(file_base_name)
448
+ file_parts = file_base_name.split(".")
449
+ base_name = file_parts[0..-2].join(".")
450
+ extension = file_parts[-1]
451
+
452
+ unique_num = 1
453
+ num_string = ""
454
+ while File.exists?(File.join(report_folder_name, "#{base_name}#{num_string}.#{extension}"))
455
+ num_string = "_#{unique_num}"
456
+ unique_num += 1
457
+ end
458
+
459
+ "#{base_name}#{num_string}.#{extension}"
460
+ end
461
+
462
+ def unique_folder_name(folder_base_name)
463
+ unique_num = 1
464
+ num_string = ""
465
+ while File.exists?(File.join(report_folder_name, "#{folder_base_name}#{num_string}"))
466
+ num_string = "_#{unique_num}"
467
+ unique_num += 1
468
+ end
469
+
470
+ "#{folder_base_name}#{num_string}"
471
+ end
472
+ end
473
+ end
474
+ end