startapp 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +7 -0
  2. data/COPYRIGHT +1 -0
  3. data/LICENSE +11 -0
  4. data/README.md +95 -0
  5. data/Rakefile +6 -0
  6. data/autocomplete/rhc_bash +1672 -0
  7. data/bin/app +37 -0
  8. data/conf/express.conf +8 -0
  9. data/features/assets/deploy.tar.gz +0 -0
  10. data/features/core_feature.rb +191 -0
  11. data/features/deployments_feature.rb +129 -0
  12. data/features/domains_feature.rb +58 -0
  13. data/features/keys_feature.rb +37 -0
  14. data/features/members_feature.rb +166 -0
  15. data/lib/rhc/auth/basic.rb +64 -0
  16. data/lib/rhc/auth/token.rb +102 -0
  17. data/lib/rhc/auth/token_store.rb +53 -0
  18. data/lib/rhc/auth.rb +5 -0
  19. data/lib/rhc/autocomplete.rb +66 -0
  20. data/lib/rhc/autocomplete_templates/bash.erb +39 -0
  21. data/lib/rhc/cartridge_helpers.rb +118 -0
  22. data/lib/rhc/cli.rb +40 -0
  23. data/lib/rhc/command_runner.rb +185 -0
  24. data/lib/rhc/commands/account.rb +25 -0
  25. data/lib/rhc/commands/alias.rb +124 -0
  26. data/lib/rhc/commands/app.rb +726 -0
  27. data/lib/rhc/commands/apps.rb +20 -0
  28. data/lib/rhc/commands/authorization.rb +115 -0
  29. data/lib/rhc/commands/base.rb +174 -0
  30. data/lib/rhc/commands/cartridge.rb +329 -0
  31. data/lib/rhc/commands/clone.rb +66 -0
  32. data/lib/rhc/commands/configure.rb +20 -0
  33. data/lib/rhc/commands/create.rb +100 -0
  34. data/lib/rhc/commands/delete.rb +19 -0
  35. data/lib/rhc/commands/deploy.rb +32 -0
  36. data/lib/rhc/commands/deployment.rb +82 -0
  37. data/lib/rhc/commands/domain.rb +172 -0
  38. data/lib/rhc/commands/env.rb +142 -0
  39. data/lib/rhc/commands/force_stop.rb +17 -0
  40. data/lib/rhc/commands/git_clone.rb +34 -0
  41. data/lib/rhc/commands/logout.rb +51 -0
  42. data/lib/rhc/commands/logs.rb +21 -0
  43. data/lib/rhc/commands/member.rb +148 -0
  44. data/lib/rhc/commands/port_forward.rb +197 -0
  45. data/lib/rhc/commands/reload.rb +17 -0
  46. data/lib/rhc/commands/restart.rb +17 -0
  47. data/lib/rhc/commands/scp.rb +54 -0
  48. data/lib/rhc/commands/server.rb +40 -0
  49. data/lib/rhc/commands/setup.rb +60 -0
  50. data/lib/rhc/commands/show.rb +43 -0
  51. data/lib/rhc/commands/snapshot.rb +137 -0
  52. data/lib/rhc/commands/ssh.rb +51 -0
  53. data/lib/rhc/commands/sshkey.rb +97 -0
  54. data/lib/rhc/commands/start.rb +17 -0
  55. data/lib/rhc/commands/stop.rb +17 -0
  56. data/lib/rhc/commands/tail.rb +47 -0
  57. data/lib/rhc/commands/threaddump.rb +14 -0
  58. data/lib/rhc/commands/tidy.rb +17 -0
  59. data/lib/rhc/commands.rb +396 -0
  60. data/lib/rhc/config.rb +321 -0
  61. data/lib/rhc/context_helper.rb +121 -0
  62. data/lib/rhc/core_ext.rb +202 -0
  63. data/lib/rhc/coverage_helper.rb +33 -0
  64. data/lib/rhc/deployment_helpers.rb +111 -0
  65. data/lib/rhc/exceptions.rb +256 -0
  66. data/lib/rhc/git_helpers.rb +106 -0
  67. data/lib/rhc/help_formatter.rb +55 -0
  68. data/lib/rhc/helpers.rb +481 -0
  69. data/lib/rhc/highline_extensions.rb +479 -0
  70. data/lib/rhc/json.rb +51 -0
  71. data/lib/rhc/output_helpers.rb +260 -0
  72. data/lib/rhc/rest/activation.rb +11 -0
  73. data/lib/rhc/rest/alias.rb +42 -0
  74. data/lib/rhc/rest/api.rb +87 -0
  75. data/lib/rhc/rest/application.rb +348 -0
  76. data/lib/rhc/rest/attributes.rb +36 -0
  77. data/lib/rhc/rest/authorization.rb +8 -0
  78. data/lib/rhc/rest/base.rb +79 -0
  79. data/lib/rhc/rest/cartridge.rb +162 -0
  80. data/lib/rhc/rest/client.rb +650 -0
  81. data/lib/rhc/rest/deployment.rb +18 -0
  82. data/lib/rhc/rest/domain.rb +98 -0
  83. data/lib/rhc/rest/environment_variable.rb +15 -0
  84. data/lib/rhc/rest/gear_group.rb +16 -0
  85. data/lib/rhc/rest/httpclient.rb +145 -0
  86. data/lib/rhc/rest/key.rb +44 -0
  87. data/lib/rhc/rest/membership.rb +105 -0
  88. data/lib/rhc/rest/mock.rb +1042 -0
  89. data/lib/rhc/rest/user.rb +32 -0
  90. data/lib/rhc/rest.rb +148 -0
  91. data/lib/rhc/scp_helpers.rb +27 -0
  92. data/lib/rhc/ssh_helpers.rb +380 -0
  93. data/lib/rhc/tar_gz.rb +51 -0
  94. data/lib/rhc/usage_templates/command_help.erb +51 -0
  95. data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
  96. data/lib/rhc/usage_templates/help.erb +61 -0
  97. data/lib/rhc/usage_templates/missing_help.erb +1 -0
  98. data/lib/rhc/usage_templates/options_help.erb +12 -0
  99. data/lib/rhc/vendor/okjson.rb +600 -0
  100. data/lib/rhc/vendor/parseconfig.rb +178 -0
  101. data/lib/rhc/vendor/sshkey.rb +253 -0
  102. data/lib/rhc/vendor/zliby.rb +628 -0
  103. data/lib/rhc/version.rb +5 -0
  104. data/lib/rhc/wizard.rb +637 -0
  105. data/lib/rhc.rb +34 -0
  106. data/spec/coverage_helper.rb +82 -0
  107. data/spec/direct_execution_helper.rb +339 -0
  108. data/spec/keys/example.pem +23 -0
  109. data/spec/keys/example_private.pem +27 -0
  110. data/spec/keys/server.pem +19 -0
  111. data/spec/rest_spec_helper.rb +31 -0
  112. data/spec/rhc/assets/cert.crt +22 -0
  113. data/spec/rhc/assets/cert_key_rsa +27 -0
  114. data/spec/rhc/assets/empty.txt +0 -0
  115. data/spec/rhc/assets/env_vars.txt +7 -0
  116. data/spec/rhc/assets/env_vars_2.txt +1 -0
  117. data/spec/rhc/assets/foo.txt +1 -0
  118. data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
  119. data/spec/rhc/assets/targz_sample.tar.gz +0 -0
  120. data/spec/rhc/auth_spec.rb +442 -0
  121. data/spec/rhc/cli_spec.rb +186 -0
  122. data/spec/rhc/command_spec.rb +435 -0
  123. data/spec/rhc/commands/account_spec.rb +42 -0
  124. data/spec/rhc/commands/alias_spec.rb +333 -0
  125. data/spec/rhc/commands/app_spec.rb +777 -0
  126. data/spec/rhc/commands/apps_spec.rb +39 -0
  127. data/spec/rhc/commands/authorization_spec.rb +157 -0
  128. data/spec/rhc/commands/cartridge_spec.rb +665 -0
  129. data/spec/rhc/commands/clone_spec.rb +41 -0
  130. data/spec/rhc/commands/deployment_spec.rb +327 -0
  131. data/spec/rhc/commands/domain_spec.rb +401 -0
  132. data/spec/rhc/commands/env_spec.rb +493 -0
  133. data/spec/rhc/commands/git_clone_spec.rb +102 -0
  134. data/spec/rhc/commands/logout_spec.rb +86 -0
  135. data/spec/rhc/commands/member_spec.rb +247 -0
  136. data/spec/rhc/commands/port_forward_spec.rb +217 -0
  137. data/spec/rhc/commands/scp_spec.rb +77 -0
  138. data/spec/rhc/commands/server_spec.rb +69 -0
  139. data/spec/rhc/commands/setup_spec.rb +118 -0
  140. data/spec/rhc/commands/snapshot_spec.rb +179 -0
  141. data/spec/rhc/commands/ssh_spec.rb +163 -0
  142. data/spec/rhc/commands/sshkey_spec.rb +188 -0
  143. data/spec/rhc/commands/tail_spec.rb +81 -0
  144. data/spec/rhc/commands/threaddump_spec.rb +84 -0
  145. data/spec/rhc/config_spec.rb +407 -0
  146. data/spec/rhc/helpers_spec.rb +531 -0
  147. data/spec/rhc/highline_extensions_spec.rb +314 -0
  148. data/spec/rhc/json_spec.rb +30 -0
  149. data/spec/rhc/rest_application_spec.rb +258 -0
  150. data/spec/rhc/rest_client_spec.rb +752 -0
  151. data/spec/rhc/rest_spec.rb +740 -0
  152. data/spec/rhc/targz_spec.rb +55 -0
  153. data/spec/rhc/wizard_spec.rb +756 -0
  154. data/spec/spec_helper.rb +575 -0
  155. data/spec/wizard_spec_helper.rb +330 -0
  156. metadata +469 -0
@@ -0,0 +1,479 @@
1
+ require 'delegate'
2
+
3
+ #
4
+ # Add specific improved functionality
5
+ #
6
+ class HighLineExtension < HighLine
7
+ attr_writer :debug
8
+
9
+ [:ask, :agree].each do |sym|
10
+ define_method(sym) do |*args, &block|
11
+ separate_blocks
12
+ r = super(*args, &block)
13
+ @last_line_open = false
14
+ r
15
+ end
16
+ end
17
+
18
+ if HighLine::CHARACTER_MODE == 'stty'
19
+ def raw_no_echo_mode
20
+ @state = `stty -g 2>/dev/null`
21
+ `stty raw -echo -icanon isig 2>&1`
22
+ end
23
+
24
+ def restore_mode
25
+ `stty #{@state} 2>&1`
26
+ end
27
+ end
28
+
29
+ def debug(msg)
30
+ $stderr.puts "DEBUG: #{msg}" if debug?
31
+ end
32
+ def debug_error(e)
33
+ debug "#{e.message} (#{e.class})\n #{e.backtrace.join("\n ")}"
34
+ end
35
+ def debug?
36
+ @debug
37
+ end
38
+
39
+ # OVERRIDE
40
+ def say(msg)
41
+ if msg.respond_to? :to_str
42
+ separate_blocks
43
+
44
+ statement = msg.to_str
45
+ return statement unless statement.present?
46
+
47
+ template = ERB.new(statement, nil, "%")
48
+ statement = template.result(binding)
49
+
50
+ if @wrap_at
51
+ statement = statement.chomp.textwrap_ansi(@wrap_at, false)
52
+ if @last_line_open && statement.length > 1
53
+ @last_line_open = false
54
+ @output.puts
55
+ end
56
+ statement = statement.join("#{indentation}\n")
57
+ end
58
+ statement = send(:page_print, statement) unless @page_at.nil?
59
+
60
+ @output.print(indentation) unless @last_line_open
61
+
62
+ @last_line_open =
63
+ if statement[-1, 1] == " " or statement[-1, 1] == "\t"
64
+ @output.print(statement)
65
+ @output.flush
66
+ #statement.strip_ansi.length + (@last_line_open || 0)
67
+ true
68
+ else
69
+ @output.puts(statement)
70
+ false
71
+ end
72
+
73
+ elsif msg.respond_to? :each
74
+ separate_blocks
75
+
76
+ @output.print if @last_line_open
77
+ @last_line_open = false
78
+
79
+ color = msg.color if msg.respond_to? :color
80
+ @output.print HighLine::Style(color).code if color
81
+
82
+ msg.each do |s|
83
+ @output.print indentation
84
+ @output.puts s
85
+ end
86
+
87
+ @output.print HighLine::CLEAR if color
88
+ @output.flush
89
+ end
90
+
91
+ msg
92
+ end
93
+
94
+ # given an array of arrays "items", construct an array of strings that can
95
+ # be used to print in tabular form.
96
+ def table(items, opts={}, &block)
97
+ items = items.map(&block) if block_given?
98
+ opts[:width] ||= default_max_width
99
+ Table.new(items, opts)
100
+ end
101
+
102
+ def table_args(indent=nil, *args)
103
+ opts = {}
104
+ opts[:indent] = indent
105
+ opts[:width] = [default_max_width, *args]
106
+ opts
107
+ end
108
+
109
+ def default_max_width
110
+ @wrap_at ? @wrap_at - indentation.length : nil
111
+ end
112
+
113
+ def header(str,opts = {}, &block)
114
+ say Header.new(str, default_max_width, ' ')
115
+ if block_given?
116
+ indent &block
117
+ end
118
+ end
119
+
120
+ #:nocov:
121
+ # Backport from Highline 1.6.16
122
+ unless HighLine.method_defined? :indent
123
+ #
124
+ # Outputs indentation with current settings
125
+ #
126
+ def indentation
127
+ @indent_size ||= 2
128
+ @indent_level ||= 0
129
+ return ' '*@indent_size*@indent_level
130
+ end
131
+
132
+ #
133
+ # Executes block or outputs statement with indentation
134
+ #
135
+ def indent(increase=1, statement=nil, multiline=nil)
136
+ @indent_size ||= 2
137
+ @indent_level ||= 0
138
+ @indent_level += increase
139
+ multi = @multi_indent
140
+ @multi_indent = multiline unless multiline.nil?
141
+ if block_given?
142
+ yield self
143
+ else
144
+ say(statement)
145
+ end
146
+ ensure
147
+ @multi_indent = multi
148
+ @indent_level -= increase
149
+ end
150
+ end
151
+ #:nocov:
152
+
153
+ ##
154
+ # section
155
+ #
156
+ # highline helper mixin which correctly formats block of say and ask
157
+ # output to have correct margins. section remembers the last margin
158
+ # used and calculates the relitive margin from the previous section.
159
+ # For example:
160
+ #
161
+ # section(bottom=1) do
162
+ # say "Hello"
163
+ # end
164
+ #
165
+ # section(top=1) do
166
+ # say "World"
167
+ # end
168
+ #
169
+ # Will output:
170
+ #
171
+ # > Hello
172
+ # >
173
+ # > World
174
+ #
175
+ # with only one newline between the two. Biggest margin wins.
176
+ #
177
+ # params:
178
+ # top - top margin specified in lines
179
+ # bottom - bottom margin specified in line
180
+ #
181
+ def section(params={}, &block)
182
+ top = params[:top] || 0
183
+ bottom = params[:bottom] || 0
184
+
185
+ # the first section cannot take a newline
186
+ top = 0 unless @margin
187
+ @margin = [top, @margin || 0].max
188
+
189
+ separate_blocks if params[:now]
190
+ value = block.call
191
+
192
+ say "\n" if @last_line_open
193
+ @margin = [bottom, @margin].max
194
+
195
+ value
196
+ end
197
+
198
+ ##
199
+ # paragraph
200
+ #
201
+ # highline helper which creates a section with margins of 1, 1
202
+ #
203
+ def paragraph(&block)
204
+ section(:top => 1, :bottom => 1, &block)
205
+ end
206
+
207
+ def pager
208
+ #:nocov:
209
+ return if RHC::Helpers.windows?
210
+ return unless @output.tty?
211
+
212
+ read, write = IO.pipe
213
+
214
+ unless Kernel.fork
215
+ # Child process
216
+ STDOUT.reopen(write)
217
+ STDERR.reopen(write) if STDERR.tty?
218
+ read.close
219
+ write.close
220
+ return
221
+ end
222
+
223
+ # Parent process, become pager
224
+ STDIN.reopen(read)
225
+ read.close
226
+ write.close
227
+
228
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
229
+
230
+ Kernel.select [STDIN] # Wait until we have input before we start the pager
231
+ pager = ENV['PAGER'] || 'less'
232
+ exec pager rescue exec "/bin/sh", "-c", pager
233
+ #:nocov:
234
+ end
235
+
236
+ private
237
+ def separate_blocks
238
+ if (@margin ||= 0) > 0 && !@last_line_open
239
+ @output.print "\n" * @margin
240
+ @margin = 0
241
+ end
242
+ end
243
+ end
244
+
245
+ #
246
+ # An element capable of being split into multiple logical rows
247
+ #
248
+ module RowBased
249
+ extend Forwardable
250
+ def_delegators :rows, :each, :to_a, :join
251
+ alias_method :each_line, :each
252
+ end
253
+
254
+ class HighLine::Header < Struct.new(:text, :width, :indent, :color)
255
+ include RowBased
256
+
257
+ protected
258
+ def rows
259
+ @rows ||= begin
260
+ if !width || width == 0
261
+ [text.is_a?(Array) ? text.compact.join(' ') : text]
262
+
263
+ elsif text.is_a? Array
264
+ text.compact!
265
+ widths = text.map{ |s| s.strip_ansi.length }
266
+ chars, join, indented = 0, 1, (indent || '').length
267
+ narrow = width - indented
268
+ text.zip(widths).inject([]) do |rows, (section, w)|
269
+ if rows.empty?
270
+ if w > width
271
+ rows.concat(section.textwrap_ansi(width))
272
+ else
273
+ rows << section.dup
274
+ chars += w
275
+ end
276
+ else
277
+ if w + chars + join > narrow
278
+ rows.concat(section.textwrap_ansi(narrow).map{ |s| "#{indent}#{s}" })
279
+ chars = 0
280
+ elsif chars == 0
281
+ rows << "#{indent}#{section}"
282
+ chars += w + indented
283
+ else
284
+ rows[-1] << " #{section}"
285
+ chars += w + join
286
+ end
287
+ end
288
+ rows
289
+ end
290
+ else
291
+ text.textwrap_ansi(width)
292
+ end
293
+ end.tap do |rows|
294
+ rows << '-' * (rows.map{ |s| s.strip_ansi.length }.max || 0)
295
+ end
296
+ end
297
+ end
298
+
299
+ #
300
+ # Represent a columnar layout of items with wrapping and flexible layout.
301
+ #
302
+ class HighLine::Table
303
+ include RowBased
304
+
305
+ def initialize(items=nil,options={},&mapper)
306
+ @items, @options, @mapper = items, options, mapper
307
+ end
308
+
309
+ def color
310
+ opts[:color]
311
+ end
312
+
313
+ protected
314
+ attr_reader :items
315
+
316
+ def opts
317
+ @options
318
+ end
319
+
320
+ def align
321
+ opts[:align] || []
322
+ end
323
+ def joiner
324
+ opts[:join] || ' '
325
+ end
326
+ def indent
327
+ opts[:indent] || ''
328
+ end
329
+ def heading
330
+ @heading ||= opts[:heading] ? HighLine::Header.new(opts[:heading], max_width, indent) : nil
331
+ end
332
+
333
+ def source_rows
334
+ @source_rows ||= begin
335
+ (@mapper ? (items.map &@mapper) : items).each do |row|
336
+ row.map! do |col|
337
+ case col
338
+ when Array then col.join("\n")
339
+ when String then col
340
+ else col.to_s
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
346
+
347
+ def headers
348
+ @headers ||= opts[:header] ? [Array(opts[:header])] : []
349
+ end
350
+
351
+ def columns
352
+ @columns ||= source_rows.map(&:length).max || 0
353
+ end
354
+
355
+ def column_widths
356
+ @column_widths ||= begin
357
+ widths = Array.new(columns){ Width.new(0,0,0) }
358
+ (source_rows + headers).each do |row|
359
+ row.each_with_index do |col, i|
360
+ w = widths[i]
361
+ s = col.strip_ansi
362
+ word_length = s.scan(/\b\S+/).inject(0){ |l, word| l = word.length if l <= word.length; l }
363
+ w.min = word_length unless w.min > word_length
364
+ w.max = s.length unless w.max > s.length
365
+ end
366
+ end
367
+ widths
368
+ end
369
+ end
370
+
371
+ Width = Struct.new(:min, :max, :set)
372
+
373
+ def allocate_widths_for(available)
374
+ available -= (columns-1) * joiner.length + indent.length
375
+ return column_widths.map{ |w| w.max } if available >= column_widths.inject(0){ |sum, w| sum + w.max } || column_widths.inject(0){ |sum, w| sum + w.min } > available
376
+
377
+ fair = available / columns
378
+ column_widths.each do |w|
379
+ if w.set > 0
380
+ available -= w.set
381
+ next
382
+ end
383
+ w.set = if w.max <= fair
384
+ available -= w.max
385
+ w.max
386
+ else
387
+ 0
388
+ end
389
+ end
390
+
391
+ remaining = column_widths.inject(0) do |sum, w|
392
+ if w.set == 0
393
+ sum += w.max
394
+ available -= w.min
395
+ end
396
+ sum
397
+ end
398
+ fair = available.to_f / remaining.to_f
399
+
400
+ column_widths.
401
+ each do |w|
402
+ if w.set == 0
403
+ alloc = (w.max * fair).to_i
404
+ overflow = alloc + w.min - w.max
405
+ if overflow > 0
406
+ alloc -= overflow
407
+ end
408
+ available -= alloc
409
+ w.set = alloc + w.min
410
+ end
411
+ end.
412
+ each do |w|
413
+ next unless available > 0
414
+ gap = w.max - w.set
415
+ if gap > 0
416
+ left = [gap,available].min
417
+ available -= left
418
+ w.set += left
419
+ end
420
+ end.map(&:set)
421
+ end
422
+
423
+ def widths
424
+ @widths ||= begin
425
+ case w = opts[:width]
426
+ when Array
427
+ column_widths.zip(w[1..-1]).each do |width, col|
428
+ width.set = col || 0
429
+ width.max = width.set if width.set > width.max
430
+ end
431
+ allocate_widths_for(w.first || 0)
432
+ when Integer
433
+ allocate_widths_for(w)
434
+ else
435
+ column_widths.map{ |w| w.max }
436
+ end
437
+ end
438
+ end
439
+
440
+ def max_width
441
+ @max_width ||= opts[:width].is_a?(Array) ? opts[:width].first : (opts[:width] ? opts[:width] : 0)
442
+ end
443
+
444
+ def header_rows
445
+ @header_rows ||= begin
446
+ headers << widths.map{ |w| '-' * w } if headers.present?
447
+ headers
448
+ end
449
+ end
450
+
451
+ def rows
452
+ @rows ||= begin
453
+ body = (header_rows + source_rows).inject([]) do |a,row|
454
+ row = row.zip(widths).map{ |column,w| w && w > 0 ? column.textwrap_ansi(w, false) : [column] }
455
+ (row.map(&:length).max || 0).times do |i|
456
+ s = []
457
+ row.each_with_index do |lines, j|
458
+ cell = lines[i]
459
+ l = cell ? cell.strip_ansi.length : 0
460
+ s <<
461
+ if align[j] == :right
462
+ "#{' '*(widths[j]-l) if l < widths[j]}#{cell}"
463
+ else
464
+ "#{cell}#{' '*(widths[j]-l) if l < widths[j]}"
465
+ end
466
+ end
467
+ a << "#{indent}#{s.join(joiner).rstrip}"
468
+ end
469
+ a
470
+ end
471
+
472
+ body = heading.to_a.concat(body) if heading
473
+ body
474
+ end
475
+ end
476
+ end
477
+
478
+ $terminal = HighLineExtension.new
479
+ $terminal.indent_size = 2 if $terminal.respond_to? :indent_size
data/lib/rhc/json.rb ADDED
@@ -0,0 +1,51 @@
1
+ require 'rhc/vendor/okjson'
2
+
3
+ module RHC
4
+
5
+ module Json
6
+
7
+ def self.decode(string, options={})
8
+ string = string.read if string.respond_to?(:read)
9
+ result = RHC::Vendor::OkJson.decode(string)
10
+ options[:symbolize_keys] ? symbolize_keys(result) : result
11
+ end
12
+
13
+ def self.encode(object, options={})
14
+ RHC::Vendor::OkJson.valenc(stringify_keys(object))
15
+ end
16
+
17
+ def self.symbolize_keys(object)
18
+ modify_keys(object) do |key|
19
+ key.is_a?(String) ? key.to_sym : key
20
+ end
21
+ end
22
+
23
+ def self.stringify_keys(object)
24
+ modify_keys(object) do |key|
25
+ key.is_a?(Symbol) ? key.to_s : key
26
+ end
27
+ end
28
+
29
+ def self.modify_keys(object, &modifier)
30
+ case object
31
+ when Array
32
+ object.map do |value|
33
+ modify_keys(value, &modifier)
34
+ end
35
+ when Hash
36
+ object.inject({}) do |result, (key, value)|
37
+ new_key = modifier.call(key)
38
+ new_value = modify_keys(value, &modifier)
39
+ result.merge! new_key => new_value
40
+ end
41
+ else
42
+ object
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ class JsonError < ::StandardError; end
49
+
50
+ end
51
+