cornucopia 0.1.12

Sign up to get free protection for your applications and to get access to all the features.
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