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,46 @@
1
+ module Cornucopia
2
+ module Util
3
+ class FileAsset
4
+ class << self
5
+ def asset(asset_name)
6
+ @@asset_list ||= {}
7
+ @@asset_list[asset_name.to_sym] = FileAsset.new(asset_name) unless @@asset_list[asset_name.to_sym]
8
+ @@asset_list[asset_name.to_sym]
9
+ end
10
+ end
11
+
12
+ def initialize(asset_name)
13
+ @asset_name = asset_name
14
+ end
15
+
16
+ def body=(asset_body)
17
+ @asset_body = asset_body
18
+ end
19
+
20
+ def source_file=(source_file_name)
21
+ # We read the file into memory in case the file moves or is temporary.
22
+ @asset_body = File.read(source_file_name)
23
+ end
24
+
25
+ def add_file(output_location)
26
+ unless (File.exists?(output_location))
27
+ create_file(output_location)
28
+ end
29
+ end
30
+
31
+ def create_file(output_location)
32
+ if @asset_body
33
+ File.open(output_location, "w+") do |write_file|
34
+ write_file << @asset_body
35
+ end
36
+ else
37
+ FileUtils.cp path, output_location
38
+ end
39
+ end
40
+
41
+ def path
42
+ File.absolute_path(File.join(File.dirname(__FILE__), "../source_files/", @asset_name))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,37 @@
1
+ module Cornucopia
2
+ module Util
3
+ # This is a stupid little settings class
4
+ # Basicaly, anything you send to it is put into a hash or returned from a hash.
5
+ class GenericSettings
6
+ def initialize
7
+ @settings_hash = {}
8
+ end
9
+
10
+ def method_missing(method_sym, *arguments, &block)
11
+ if self.respond_to?("super__#{method_sym}".to_sym)
12
+ super
13
+ else
14
+ if method_sym.to_s[-1] == "="
15
+ raise "wrong number of arguments (#{arguments.length} for 1)" if !arguments || arguments.length != 1
16
+ raise "block not accepted" if block
17
+
18
+ @settings_hash[method_sym.to_s[0..-2].to_sym] = arguments[0]
19
+ else
20
+ raise "too many arguments (#{arguments.length} for 0)" if arguments && arguments.length > 0
21
+ raise "block not accepted" if block
22
+ @settings_hash[method_sym]
23
+ end
24
+ end
25
+ end
26
+
27
+ def respond_to?(method_sym, include_private = false)
28
+ method_name = method_sym.to_s
29
+ if method_name[0.."super__".length - 1] == "super__"
30
+ super(method_sym["super__".length..-1].to_sym, include_private)
31
+ else
32
+ true
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,97 @@
1
+ require ::File.expand_path('configuration', File.dirname(__FILE__))
2
+ require ::File.expand_path('report_builder', File.dirname(__FILE__))
3
+
4
+ module Cornucopia
5
+ module Util
6
+ class LogCapture
7
+ class << self
8
+ TAIL_BUF_LENGTH = 1 << 16
9
+
10
+ # This function will capture the logs and output them to the report
11
+ def capture_logs(report_table)
12
+ if report_table
13
+ if Object.const_defined?("Rails")
14
+ log_folder = Rails.root.to_s
15
+ if (log_folder =~ /\/features\/?$/ || log_folder =~ /\/spec\/?$/)
16
+ log_folder = File.join(log_folder, "../")
17
+ end
18
+
19
+ default_log_file = "log/#{Rails.env.to_s}.log"
20
+
21
+ output_log_file(report_table, File.join(log_folder, default_log_file))
22
+ else
23
+ log_folder = FileUtils.pwd
24
+ end
25
+
26
+ Cornucopia::Util::Configuration.user_log_files.each do |relative_log_file, options|
27
+ output_log_file(report_table, File.join(log_folder, relative_log_file), options)
28
+ end
29
+ else
30
+ Cornucopia::Util::ReportBuilder.current_report.within_section("Log Dump:") do |report|
31
+ report.within_table do |new_report_table|
32
+ Cornucopia::Util::LogCapture.capture_logs new_report_table
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def highlight_log_output(log_text)
39
+ output_text = Cornucopia::Util::ReportBuilder.format_code_refs(log_text)
40
+ output_text = output_text.gsub(/^(Completed [^23].*)$/, "<span class=\"completed-error\">\\1<\/span>")
41
+ output_text = output_text.gsub(/^(Completed [23].*)$/, "<span class=\"completed-other\">\\1<\/span>")
42
+
43
+ output_text.html_safe
44
+ end
45
+
46
+ # A cheap and sleazy tail function, but it should work...
47
+ def output_log_file(report_table, log_file_name, options = {})
48
+ if File.exists?(log_file_name)
49
+ output_file = false
50
+
51
+ options.reverse_merge!({ num_lines: Cornucopia::Util::Configuration.num_lines })
52
+
53
+ num_lines = options[:num_lines] || Cornucopia::Util::Configuration.num_lines
54
+ num_lines = Cornucopia::Util::Configuration.num_lines if num_lines <= 0
55
+ log_buffer = ""
56
+ file_size = File.size(log_file_name)
57
+
58
+ File.open(log_file_name) do |log_file|
59
+ seek_len = [file_size, TAIL_BUF_LENGTH].min
60
+ log_file.seek -seek_len, IO::SEEK_END
61
+
62
+ while (log_buffer.count("\n") <= num_lines)
63
+ log_buffer = log_file.read(seek_len) + log_buffer
64
+
65
+ file_size -= seek_len
66
+ seek_len = [file_size, TAIL_BUF_LENGTH].min
67
+
68
+ break if seek_len <= 0
69
+
70
+ log_file.seek -seek_len - TAIL_BUF_LENGTH, IO::SEEK_CUR
71
+ end
72
+ end
73
+
74
+ if log_buffer
75
+ log_buffer = log_buffer.split("\n")
76
+ if (log_buffer.length > num_lines)
77
+ log_buffer = log_buffer[-num_lines..-1]
78
+ end
79
+
80
+ report_table.write_stats File.basename(log_file_name),
81
+ Cornucopia::Util::PrettyFormatter.format_string(
82
+ Cornucopia::Util::LogCapture.highlight_log_output(
83
+ "log_file - #{log_file_name}:#{file_size}\n#{log_buffer.join("\n")}"
84
+ )
85
+ ),
86
+ do_not_pretty_print: true
87
+
88
+ output_file = true
89
+ end
90
+
91
+ output_file
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,580 @@
1
+ module Cornucopia
2
+ module Util
3
+ class PrettyFormatter
4
+ class << self
5
+ def format_string(unknown_string)
6
+ PrettyFormatter.new(unknown_string).pretty_print
7
+ end
8
+ end
9
+
10
+ def initialize(unknown_string)
11
+ @unknown_string = unknown_string
12
+ @print_html_safe = @unknown_string.html_safe?
13
+ end
14
+
15
+ def pretty_print
16
+ search_pos = 0
17
+
18
+ do_pretty_print = @unknown_string =~ /Parameters:[ \t]*\{.*?\}/
19
+ formatted_class_pos = class_pos = -1
20
+ while !do_pretty_print && class_pos && class_pos == formatted_class_pos
21
+ search_pos += formatted_class_pos + 1
22
+
23
+ if @print_html_safe
24
+ class_pos = @unknown_string[search_pos..-1] =~ /\#\&lt;[^ \t\n]+/
25
+ formatted_class_pos = (@unknown_string[search_pos..-1] =~ /\#\&lt;[^ \t\n]+[ \t]*\n/)
26
+ else
27
+ class_pos = @unknown_string[search_pos..-1] =~ /\#\<[^ \t\n]+/
28
+ formatted_class_pos = (@unknown_string[search_pos..-1] =~ /\#\<[^ \t\n]+[ \t]*\n/)
29
+ end
30
+
31
+ do_pretty_print ||= (class_pos && class_pos != formatted_class_pos)
32
+ end
33
+
34
+ if do_pretty_print
35
+ @indent_level = 0
36
+ @current_state = :unknown
37
+ @state_stack = [:unknown]
38
+ @current_pos = 0
39
+ @start_pos = 0
40
+ @unknown_string_len = @unknown_string.length
41
+ @formatted_string = ""
42
+
43
+ while @current_pos < @unknown_string_len
44
+ case @current_state
45
+ when :unknown
46
+ search_pos = @current_pos
47
+ params_pos = @unknown_string[search_pos..-1] =~ /Parameters:[ \t]*\{.*?\}/
48
+ params_pos += search_pos if params_pos
49
+ formatted_class_pos = class_pos = -1
50
+ while (class_pos && class_pos == formatted_class_pos)
51
+ search_pos += formatted_class_pos + 1
52
+
53
+ if @print_html_safe
54
+ class_pos = @unknown_string[search_pos..-1] =~ /\#\&lt;[^ \t\n]+/
55
+ formatted_class_pos = (@unknown_string[search_pos..-1] =~ /\#\&lt;[^ \t\n]+[ \t]*\n/)
56
+ else
57
+ class_pos = @unknown_string[search_pos..-1] =~ /\#\<[^ \t\n]+/
58
+ formatted_class_pos = (@unknown_string[search_pos..-1] =~ /\#\<[^ \t\n]+[ \t]*\n/)
59
+ end
60
+ end
61
+ class_pos += search_pos if class_pos
62
+ formatted_class_pos += search_pos if formatted_class_pos
63
+
64
+ if class_pos &&
65
+ class_pos != formatted_class_pos &&
66
+ (!params_pos || class_pos < params_pos)
67
+ @current_pos = class_pos
68
+
69
+ if @start_pos < @current_pos || @start_pos > 0
70
+ output_line(@unknown_string[@start_pos..@current_pos - 1])
71
+ else
72
+ @indent_level -= 1
73
+ @state_stack << :bottom_level
74
+ end
75
+
76
+ start_class
77
+ else
78
+ if params_pos
79
+ @current_pos = params_pos
80
+ @state_stack << :parameters
81
+
82
+ if @start_pos < @current_pos || @start_pos > 0
83
+ output_line(@unknown_string[@start_pos..@current_pos - 1])
84
+ else
85
+ @state_stack << :bottom_level
86
+ @indent_level -= 1
87
+ end
88
+
89
+ @formatted_string.rstrip!
90
+ @state_stack << :parameters_name
91
+ @current_state = :value
92
+ @start_pos = @current_pos
93
+ @indent_level += 1
94
+ else
95
+ output_end
96
+ end
97
+ end
98
+
99
+ when :value_end
100
+ case @state_stack[-1]
101
+ when :unknown
102
+ @current_state = :unknown
103
+
104
+ when :parameters
105
+ if @current_pos + 1 < @unknown_string_len
106
+ @formatted_string << "\n"
107
+ end
108
+ @indent_level -= 1
109
+ @state_stack.pop
110
+ @current_state = :unknown
111
+
112
+ when :parameters_name
113
+ group_value_end :parameters_start, "}"
114
+
115
+ when :class_name
116
+ group_value_end :variable,
117
+ ">",
118
+ end_append_text: @indent_level == 0 ? "\n" : "",
119
+ append_text: " ",
120
+ group_end_same_line: true,
121
+ add_value: true
122
+ @indent_level += 1
123
+
124
+ when :variable
125
+ if (position_matches_string(@current_pos - 1, ":") &&
126
+ ((!@print_html_safe &&
127
+ 0 == (@unknown_string[@current_pos..-1] =~ /^[ \t\r\n]*({.*?}|\#<.*?>|\[.*?\]|\".*?\"|[^\n,\"]+)[ \t\r\n]*[,\>]/)) ||
128
+ (@print_html_safe &&
129
+ 0 == (@unknown_string[@current_pos..-1] =~ /^[ \t\r\n]*({.*?}|\#&lt\;.*?&gt\;|\[.*?\]|&quot\;.*?&quot\;|[^\n,\"]+)[ \t\r\n]*(,|&gt\;)/))
130
+ ))
131
+ @state_stack.pop
132
+ @state_stack << :group_implied
133
+ @state_stack << :hash_key_symbol_post
134
+ elsif (!position_matches_string(@current_pos - 1, ":") &&
135
+ ((!@print_html_safe &&
136
+ 0 == (@unknown_string[@current_pos..-1] =~ /^[ \t\r\n]*=>[ \t\r\n]*({.*?}|\#<.*?>|\[.*?\]|\".*?\"|[^\n,\"]+)[ \t\r\n]*[,\>]/)) ||
137
+ (@print_html_safe &&
138
+ 0 == (@unknown_string[@current_pos..-1] =~ /^[ \t\r\n]*=&gt\;[ \t\r\n]*({.*?}|\#&lt\;.*?&gt\;|\[.*?\]|&quot\;.*?&quot\;|[^\n,\"&]+)[ \t\r\n]*(,|&gt\;)/))
139
+ ))
140
+ @state_stack.pop
141
+ @state_stack << :group_implied
142
+ @state_stack << :hash_key_string
143
+ else
144
+ group_value_end :variable_equal,
145
+ ">",
146
+ append_text: " ",
147
+ group_end_same_line: !@variable_set,
148
+ end_append_text: @indent_level == 0 ? "\n" : ""
149
+ end
150
+
151
+ when :variable_value
152
+ group_value_end :variable_comma, ">",
153
+ same_line: true,
154
+ end_append_text: @indent_level == 0 ? "\n" : ""
155
+
156
+ when :hash_key_symbol_post
157
+ group_value_end :hash_value,
158
+ @state_stack[-2] == :group_implied ? ">" : "}",
159
+ append_text: " ",
160
+ add_value: true
161
+
162
+ when :hash_key, :hash_key_symbol, :hash_key_string
163
+ group_value_end :hash_rocket,
164
+ @state_stack[-2] == :group_implied ? ">" : "}",
165
+ append_text: " "
166
+
167
+ when :hash_value
168
+ group_value_end :hash_comma,
169
+ @state_stack[-1] == :group_implied ? ">" : "}",
170
+ same_line: true
171
+
172
+ when :array
173
+ group_value_end :array_comma, "]"
174
+ end
175
+
176
+ @current_pos -= 1
177
+
178
+ when :hash_rocket
179
+ if @unknown_string[@current_pos] == ","
180
+ group_separator ",",
181
+ :hash_value,
182
+ @state_stack[-2] == :group_implied ? ">" : "}",
183
+ append_text: " "
184
+ else
185
+ group_separator "=>",
186
+ :hash_value,
187
+ @state_stack[-2] == :group_implied ? ">" : "}",
188
+ append_text: " "
189
+ end
190
+
191
+ when :variable_equal
192
+ @variable_set = true
193
+ if @unknown_string[@current_pos] == ","
194
+ group_separator ",",
195
+ :variable_value, ">",
196
+ end_append_text: @indent_level == 0 ? "\n" : ""
197
+ else
198
+ group_separator "=",
199
+ :variable_value, ">",
200
+ end_append_text: @indent_level == 0 ? "\n" : "",
201
+ append_text: " "
202
+ end
203
+
204
+ when :variable_comma
205
+ group_separator ",",
206
+ :variable, ">",
207
+ end_append_text: @indent_level == 0 ? "\n" : ""
208
+
209
+ when :array_comma
210
+ group_separator ",", :array, "]"
211
+
212
+ when :hash_comma
213
+ group_separator ",",
214
+ :hash_key,
215
+ @state_stack[-1] == :group_implied ? ">" : "}"
216
+ if @state_stack[-1] == :group_implied
217
+ @state_stack.pop
218
+ end
219
+
220
+ when :parameters_start
221
+ start_grouping :hash_key
222
+
223
+ when :whitespace
224
+ case @unknown_string[@current_pos]
225
+ when " ", "\r", "\n", "\t"
226
+ @start_pos = @current_pos + 1
227
+
228
+ else
229
+ @start_pos = @current_pos # probably redundant, but better safe than sorry...
230
+ @current_pos -= 1
231
+ @current_state = @state_stack.pop
232
+ end
233
+
234
+ when :string
235
+ if position_matches_string(@current_pos, "\\")
236
+ @current_pos += 1
237
+ elsif position_matches_string(@current_pos, "\"")
238
+ @current_pos += translate_value("\"").length
239
+ value_end
240
+ end
241
+
242
+ when :value
243
+ case @unknown_string[@current_pos]
244
+ when "{"
245
+ if @start_pos == @current_pos
246
+ start_grouping :hash_key
247
+ else
248
+ output_end
249
+ end
250
+
251
+ when "["
252
+ if @start_pos == @current_pos
253
+ start_grouping :array
254
+ else
255
+ output_end
256
+ end
257
+
258
+ when "\""
259
+ if position_matches_string(@current_pos, "\"")
260
+ start_quote
261
+ end
262
+
263
+ when "#"
264
+ if @start_pos == @current_pos &&
265
+ @current_pos < @unknown_string_len - 2 &&
266
+ position_matches_string(@current_pos + 1, "<")
267
+ start_class
268
+ end
269
+
270
+ when "="
271
+ case @state_stack[-1]
272
+ when :variable,
273
+ :hash_key,
274
+ :hash_key_string,
275
+ :hash_key_symbol,
276
+ :hash_key_symbol_post
277
+ value_end
278
+ end
279
+
280
+ when ":"
281
+ case @state_stack[-1]
282
+ when :hash_key
283
+ if @current_pos == @start_pos
284
+ @state_stack.pop
285
+ @state_stack << :hash_key_symbol
286
+ else
287
+ @state_stack.pop
288
+ @state_stack << :hash_key_symbol_post
289
+
290
+ @current_pos += 1
291
+ value_end
292
+ end
293
+
294
+ when :parameters_name
295
+ @current_pos += 1
296
+ value_end
297
+
298
+ when :variable
299
+ unless @current_pos == @start_pos
300
+ @current_pos += 1
301
+ value_end
302
+ end
303
+ end
304
+
305
+ when "&", ">"
306
+ if position_matches_string(@current_pos, ">")
307
+ case @state_stack[-1]
308
+ when :class_name,
309
+ :variable,
310
+ :variable_equal,
311
+ :variable_value,
312
+ :variable_comma
313
+ value_end
314
+ when :hash_key,
315
+ :hash_key_symbol_post,
316
+ :hash_key_symbol,
317
+ :hash_key_string,
318
+ :hash_rocket,
319
+ :hash_value,
320
+ :hash_comma
321
+ if @state_stack[-2] == :group_implied
322
+ value_end
323
+ end
324
+ end
325
+ elsif position_matches_string(@current_pos, "\"")
326
+ start_quote
327
+ end
328
+
329
+ when "}"
330
+ case @state_stack[-1]
331
+ when :hash_key,
332
+ :hash_key_symbol_post,
333
+ :hash_key_symbol,
334
+ :hash_key_string,
335
+ :hash_rocket,
336
+ :hash_value,
337
+ :hash_comma
338
+ value_end
339
+ end
340
+
341
+ when "]"
342
+ case @state_stack[-1]
343
+ when :array,
344
+ :array_comma
345
+ value_end
346
+ end
347
+
348
+ when ","
349
+ case @state_stack[-1]
350
+ when :hash_value,
351
+ :array,
352
+ :variable_value,
353
+ #unexpected, probably error states...
354
+ :variable,
355
+ :variable_equal,
356
+ :hash_key,
357
+ :hash_key_string,
358
+ :hash_key_symbol,
359
+ :hash_key_symbol_post,
360
+ :hash_rocket,
361
+ :hash_comma,
362
+ :variable_comma,
363
+ :array_comma
364
+ value_end
365
+ end
366
+
367
+ when " ", "\r", "\n", "\t"
368
+ case @state_stack[-1]
369
+ when :variable_value,
370
+ :array,
371
+ :hash_value
372
+ @current_pos += 1
373
+ @current_pos -= 1
374
+
375
+ else
376
+ value_end
377
+ end
378
+ end
379
+ end
380
+
381
+ @current_pos += 1
382
+ end
383
+
384
+ if @start_pos < @current_pos
385
+ output_end
386
+ end
387
+
388
+ @formatted_string.rstrip!
389
+ @formatted_string = @formatted_string.html_safe if @print_html_safe
390
+ @formatted_string
391
+ else
392
+ @unknown_string
393
+ end
394
+ end
395
+
396
+ def start_quote
397
+ if @start_pos == @current_pos
398
+ if @state_stack[-1] == :hash_key && @start_pos == @current_pos
399
+ @state_stack.pop
400
+ @state_stack << :hash_key_string
401
+ end
402
+
403
+ @current_state = :string
404
+ @current_pos += translate_value("\"").length - 1
405
+ else
406
+ # This is almost certainly an error. End it now...
407
+ output_end
408
+ end
409
+ end
410
+
411
+ def group_value_end(next_state, group_end_char, options = {})
412
+ @state_stack.pop
413
+
414
+ if position_matches_string @current_pos, group_end_char
415
+ output_line(@unknown_string[@start_pos..@current_pos - 1], options.merge(strip_output: true))
416
+ @start_pos = @current_pos
417
+
418
+ safe_end = translate_value(group_end_char)
419
+ end_grouping safe_end, options
420
+ @current_pos += 1
421
+ else
422
+ output_line(@unknown_string[@start_pos..@current_pos - 1], options.merge(strip_output: true))
423
+
424
+ if options[:append_text]
425
+ @formatted_string << options[:append_text]
426
+ end
427
+
428
+ @state_stack << next_state
429
+ @state_stack << :value if options[:add_value]
430
+ @start_pos = @current_pos
431
+ @current_state = :whitespace
432
+ end
433
+ end
434
+
435
+ def value_end
436
+ @current_state = :value_end
437
+ @current_pos -= 1
438
+ end
439
+
440
+ def group_separator(separator_value, next_state, group_end_char, options = {})
441
+ if position_matches_string(@current_pos, separator_value)
442
+ safe_separator = translate_value(separator_value)
443
+
444
+ @formatted_string << @unknown_string[@start_pos..(@current_pos + safe_separator.length - 1)]
445
+ @formatted_string.rstrip!
446
+
447
+ if options[:append_text]
448
+ @formatted_string << options[:append_text]
449
+ end
450
+
451
+ @start_pos = @current_pos + safe_separator.length
452
+ @current_pos += safe_separator.length - 1
453
+ @state_stack << next_state
454
+ @state_stack << :value
455
+ @current_state = :whitespace
456
+
457
+ elsif position_matches_string(@current_pos, group_end_char)
458
+ end_grouping translate_value(group_end_char), options
459
+
460
+ else
461
+ # Some kind of error, exit out
462
+ output_end
463
+ end
464
+ end
465
+
466
+ def end_grouping(safe_end, options = {})
467
+ @indent_level -= 1
468
+
469
+ if options[:group_end_same_line]
470
+ @formatted_string.strip!
471
+ @formatted_string << @unknown_string[@start_pos..@current_pos + safe_end.length - 1]
472
+ else
473
+ output_line(@unknown_string[@start_pos..@current_pos + safe_end.length - 1])
474
+
475
+ if options[:end_append_text]
476
+ @formatted_string << options[:end_append_text]
477
+ end
478
+ end
479
+
480
+ @indent_level -= 1
481
+ if @state_stack[-1] == :bottom_level ||
482
+ (@state_stack[-1] == :group_implied && @state_stack[-2] == :bottom_level)
483
+ if @state_stack[-1] == :bottom_level
484
+ @state_stack.pop
485
+ else
486
+ @state_stack.pop
487
+ @state_stack.pop
488
+ @state_stack << :group_implied
489
+ end
490
+
491
+ @indent_level += 1
492
+ end
493
+
494
+ @current_pos += safe_end.length - 1
495
+ @start_pos = @current_pos + 1
496
+ @current_state = :value_end
497
+ end
498
+
499
+ def start_grouping group_type
500
+ if @state_stack[-1] == :array
501
+ @state_stack << :bottom_level
502
+ else
503
+ @indent_level += 1
504
+ end
505
+
506
+ output_line(@unknown_string[@start_pos..@current_pos])
507
+ @indent_level += 1
508
+ @start_pos = @current_pos + 1
509
+ @state_stack << group_type
510
+ @state_stack << :value
511
+ @current_state = :whitespace
512
+ end
513
+
514
+ def start_class
515
+ if @state_stack[-1] == :array
516
+ @state_stack << :bottom_level
517
+ @indent_level -= 1
518
+ end
519
+ @indent_level += 1
520
+
521
+ @start_pos = @current_pos
522
+
523
+ @variable_set = false
524
+ @state_stack << :class_name
525
+
526
+ @current_pos += 1
527
+ @current_state = :value
528
+
529
+ @formatted_string.rstrip!
530
+ end
531
+
532
+ def output_end
533
+ unless @start_pos >= @unknown_string_len
534
+ @formatted_string << "\n" unless @formatted_string[-1] == "\n"
535
+ @formatted_string << @unknown_string[@start_pos..-1]
536
+ end
537
+
538
+ @current_pos = @unknown_string_len
539
+ @start_pos = @unknown_string_len
540
+ end
541
+
542
+ def translate_value(value)
543
+ safe_string = value
544
+
545
+ if @print_html_safe
546
+ safe_string = "".html_safe
547
+ safe_string << value
548
+ end
549
+
550
+ safe_string
551
+ end
552
+
553
+ def position_matches_string(test_position, test_string)
554
+ safe_string = translate_value(test_string)
555
+
556
+ test_position <= @unknown_string_len - safe_string.length &&
557
+ @unknown_string[test_position..(test_position + safe_string.length - 1)] == safe_string
558
+ end
559
+
560
+ def output_line(text, options = {})
561
+ unless text.blank?
562
+ unless options[:same_line]
563
+ if (@formatted_string[-1] == " ")
564
+ @formatted_string.rstrip!
565
+ end
566
+
567
+ @formatted_string << "\n" unless @formatted_string.length == 0 || @formatted_string[-1] == "\n"
568
+ @formatted_string << " " * (2 * @indent_level)
569
+ end
570
+
571
+ @formatted_string << text
572
+
573
+ if options[:strip_output]
574
+ @formatted_string.rstrip!
575
+ end
576
+ end
577
+ end
578
+ end
579
+ end
580
+ end