beaker 2.18.3 → 2.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +8 -8
  2. data/HISTORY.md +439 -2
  3. data/acceptance/lib/beaker/acceptance/install_utils.rb +58 -0
  4. data/acceptance/pre_suite/puppet_git/install.rb +6 -65
  5. data/acceptance/tests/foss_utils/clone_git_repo_on.rb +49 -0
  6. data/beaker.gemspec +2 -0
  7. data/lib/beaker/dsl/helpers/web_helpers.rb +2 -1
  8. data/lib/beaker/dsl/install_utils/aio_defaults.rb +0 -2
  9. data/lib/beaker/dsl/install_utils/foss_utils.rb +97 -60
  10. data/lib/beaker/dsl/install_utils/pe_utils.rb +30 -53
  11. data/lib/beaker/dsl/install_utils/puppet_utils.rb +43 -0
  12. data/lib/beaker/dsl/install_utils/windows_utils.rb +144 -0
  13. data/lib/beaker/dsl/roles.rb +20 -3
  14. data/lib/beaker/dsl/structure.rb +14 -3
  15. data/lib/beaker/host.rb +24 -3
  16. data/lib/beaker/host/unix/pkg.rb +9 -0
  17. data/lib/beaker/host/windows/exec.rb +3 -0
  18. data/lib/beaker/host_prebuilt_steps.rb +5 -9
  19. data/lib/beaker/hypervisor/aws_sdk.rb +22 -18
  20. data/lib/beaker/hypervisor/docker.rb +7 -0
  21. data/lib/beaker/hypervisor/vmpooler.rb +4 -0
  22. data/lib/beaker/logger.rb +12 -1
  23. data/lib/beaker/options/command_line_parser.rb +9 -0
  24. data/lib/beaker/options/options_hash.rb +3 -296
  25. data/lib/beaker/options/parser.rb +12 -0
  26. data/lib/beaker/options/presets.rb +0 -1
  27. data/lib/beaker/ssh_connection.rb +48 -23
  28. data/lib/beaker/test_case.rb +1 -1
  29. data/lib/beaker/version.rb +1 -1
  30. data/spec/beaker/dsl/helpers/web_helpers_spec.rb +10 -1
  31. data/spec/beaker/dsl/install_utils/foss_utils_spec.rb +194 -49
  32. data/spec/beaker/dsl/install_utils/pe_utils_spec.rb +112 -22
  33. data/spec/beaker/dsl/install_utils/puppet_utils_spec.rb +57 -0
  34. data/spec/beaker/dsl/install_utils/windows_utils_spec.rb +132 -0
  35. data/spec/beaker/dsl/roles_spec.rb +36 -5
  36. data/spec/beaker/dsl/structure_spec.rb +9 -2
  37. data/spec/beaker/host/unix/pkg_spec.rb +26 -6
  38. data/spec/beaker/host_prebuilt_steps_spec.rb +3 -2
  39. data/spec/beaker/host_spec.rb +18 -0
  40. data/spec/beaker/hypervisor/aixer_spec.rb +1 -1
  41. data/spec/beaker/hypervisor/aws_sdk_spec.rb +595 -58
  42. data/spec/beaker/hypervisor/docker_spec.rb +2 -1
  43. data/spec/beaker/hypervisor/solaris_spec.rb +1 -0
  44. data/spec/beaker/hypervisor/vagrant_spec.rb +2 -1
  45. data/spec/beaker/logger_spec.rb +39 -0
  46. data/spec/beaker/options/command_line_parser_spec.rb +2 -2
  47. data/spec/beaker/options/options_hash_spec.rb +1 -102
  48. data/spec/beaker/options/parser_spec.rb +19 -0
  49. data/spec/beaker/options/pe_version_scaper_spec.rb +11 -1
  50. data/spec/beaker/options/presets_spec.rb +8 -0
  51. data/spec/beaker/ssh_connection_spec.rb +39 -21
  52. data/spec/helpers.rb +9 -3
  53. data/spec/mocks.rb +2 -0
  54. metadata +34 -11
  55. data/lib/beaker/answers.rb +0 -143
  56. data/lib/beaker/answers/version20.rb +0 -120
  57. data/lib/beaker/answers/version28.rb +0 -121
  58. data/lib/beaker/answers/version30.rb +0 -227
  59. data/lib/beaker/answers/version32.rb +0 -44
  60. data/lib/beaker/answers/version34.rb +0 -51
  61. data/lib/beaker/answers/version38.rb +0 -29
  62. data/lib/beaker/answers/version40.rb +0 -44
  63. data/spec/beaker/answers_spec.rb +0 -547
@@ -214,8 +214,9 @@ module Beaker
214
214
  end
215
215
 
216
216
  #Return the ip address of this host
217
+ #Always pull fresh, because this can sometimes change
217
218
  def ip
218
- self[:ip] ||= get_ip
219
+ self['ip'] = get_ip
219
220
  end
220
221
 
221
222
  #@return [Boolean] true if x86_64, false otherwise
@@ -224,13 +225,29 @@ module Beaker
224
225
  end
225
226
 
226
227
  def connection
227
- @connection ||= SshConnection.connect( reachable_name,
228
+ # create new connection object if necessary
229
+ @connection ||= SshConnection.connect( { :ip => self['ip'], :vmhostname => self['vmhostname'], :hostname => @name },
228
230
  self['user'],
229
231
  self['ssh'], { :logger => @logger } )
232
+ # update connection information
233
+ if self['ip'] && (@connection.ip != self['ip'])
234
+ @connection.ip = self['ip']
235
+ end
236
+ if self['vmhostname'] && (@connection.vmhostname != self['vmhostname'])
237
+ @connection.vmhostname = self['vmhostname']
238
+ end
239
+ if @name && (@connection.hostname != @name)
240
+ @connection.hostname = @name
241
+ end
242
+ @connection
230
243
  end
231
244
 
232
245
  def close
233
246
  @connection.close if @connection
247
+ # update connection information
248
+ @connection.ip = self['ip'] if self['ip']
249
+ @connection.vmhostname = self['vmhostname'] if self['vmhostname']
250
+ @connection.hostname = @name
234
251
  @connection = nil
235
252
  end
236
253
 
@@ -242,7 +259,11 @@ module Beaker
242
259
  output_callback = nil
243
260
  else
244
261
  @logger.debug "\n#{log_prefix} #{Time.new.strftime('%H:%M:%S')}$ #{cmdline}"
245
- output_callback = logger.method(:host_output)
262
+ if @options[:color_host_output]
263
+ output_callback = logger.method(:color_host_output)
264
+ else
265
+ output_callback = logger.method(:host_output)
266
+ end
246
267
  end
247
268
 
248
269
  unless $dry_run
@@ -65,6 +65,11 @@ module Unix::Pkg
65
65
  execute("zypper --non-interactive in #{name}", opts)
66
66
  when /el-4/
67
67
  @logger.debug("Package installation not supported on rhel4")
68
+ when /fedora-22/
69
+ if version
70
+ name = "#{name}-#{version}"
71
+ end
72
+ execute("dnf -y #{cmdline_args} install #{name}", opts)
68
73
  when /cisco|fedora|centos|eos|el-/
69
74
  if version
70
75
  name = "#{name}-#{version}"
@@ -121,6 +126,8 @@ module Unix::Pkg
121
126
  execute("zypper --non-interactive rm #{name}", opts)
122
127
  when /el-4/
123
128
  @logger.debug("Package uninstallation not supported on rhel4")
129
+ when /fedora-22/
130
+ execute("dnf -y #{cmdline_args} remove #{name}", opts)
124
131
  when /cisco|fedora|centos|eos|el-/
125
132
  execute("yum -y #{cmdline_args} remove #{name}", opts)
126
133
  when /ubuntu|debian|cumulus/
@@ -145,6 +152,8 @@ module Unix::Pkg
145
152
  execute("zypper --non-interactive --no-gpg-checks up #{name}", opts)
146
153
  when /el-4/
147
154
  @logger.debug("Package upgrade is not supported on rhel4")
155
+ when /fedora-22/
156
+ execute("dnf -y #{cmdline_args} update #{name}", opts)
148
157
  when /cisco|fedora|centos|eos|el-/
149
158
  execute("yum -y #{cmdline_args} update #{name}", opts)
150
159
  when /ubuntu|debian|cumulus/
@@ -3,6 +3,9 @@ module Windows::Exec
3
3
 
4
4
  def reboot
5
5
  exec(Beaker::Command.new('shutdown /r /t 0 /d p:4:1 /c "Beaker::Host reboot command issued"'), :expect_connection_failure => true)
6
+ # rebooting on windows is sloooooow
7
+ # give it some breathing room before attempting a reconnect
8
+ sleep(30)
6
9
  end
7
10
 
8
11
  ABS_CMD = 'c:\\\\windows\\\\system32\\\\cmd.exe'
@@ -341,7 +341,10 @@ module Beaker
341
341
  def hack_etc_hosts hosts, opts
342
342
  etc_hosts = "127.0.0.1\tlocalhost localhost.localdomain\n"
343
343
  hosts.each do |host|
344
- etc_hosts += "#{host['vm_ip'] || host['ip'].to_s}\t#{host[:vmhostname] || host.name}\n"
344
+ ip = host['vm_ip'] || host['ip'].to_s
345
+ hostname = host[:vmhostname] || host.name
346
+ domain = get_domain_name(host)
347
+ etc_hosts += "#{ip}\t#{hostname}.#{domain} #{hostname}\n"
345
348
  end
346
349
  hosts.each do |host|
347
350
  set_etc_hosts(host, etc_hosts)
@@ -553,14 +556,7 @@ module Beaker
553
556
  end
554
557
  # REMOVE POST BEAKER 3: backwards compatability, do some setup based upon the global type
555
558
  # this is the worst and i hate it
556
- if host[:type]
557
- case host[:type]
558
- when /git|foss|aio/
559
- Class.new.extend(Beaker::DSL).configure_foss_defaults_on(host)
560
- when /pe/
561
- Class.new.extend(Beaker::DSL).configure_pe_defaults_on(host)
562
- end
563
- end
559
+ Class.new.extend(Beaker::DSL).configure_type_defaults_on(host)
564
560
 
565
561
  #close the host to re-establish the connection with the new sshd settings
566
562
  host.close
@@ -127,7 +127,7 @@ module Beaker
127
127
 
128
128
  # Return all instances currently on ec2.
129
129
  # @see AwsSdk#instance_by_id
130
- # @return [Array<AWS::EC2::Instance>] An array of AWS::EC2 instance objects
130
+ # @return [AWS::EC2::InstanceCollection] An array of AWS::EC2 instance objects
131
131
  def instances
132
132
  @ec2.instances
133
133
  end
@@ -142,7 +142,7 @@ module Beaker
142
142
 
143
143
  # Return all VPCs currently on ec2.
144
144
  # @see AwsSdk#vpc_by_id
145
- # @return [Array<AWS::EC2::VPC>] An array of AWS::EC2 vpc objects
145
+ # @return [AWS::EC2::VPCCollection] An array of AWS::EC2 vpc objects
146
146
  def vpcs
147
147
  @ec2.vpcs
148
148
  end
@@ -157,7 +157,7 @@ module Beaker
157
157
 
158
158
  # Return all security groups currently on ec2.
159
159
  # @see AwsSdk#security_goup_by_id
160
- # @return [Array<AWS::EC2::SecurityGroup>] An array of AWS::EC2 security group objects
160
+ # @return [AWS::EC2::SecurityGroupCollection] An array of AWS::EC2 security group objects
161
161
  def security_groups
162
162
  @ec2.security_groups
163
163
  end
@@ -224,7 +224,7 @@ module Beaker
224
224
 
225
225
  # Create an EC2 instance for host, tag it, and return it.
226
226
  #
227
- # @return [AWS::EC2::Instance)]
227
+ # @return [void]
228
228
  # @api private
229
229
  def create_instance(host, ami_spec, subnet_id)
230
230
  amitype = host['vmname'] || host['platform']
@@ -489,28 +489,32 @@ module Beaker
489
489
  nil
490
490
  end
491
491
 
492
+ # Return a valid /etc/hosts line for a given host
493
+ #
494
+ # @param [Beaker::Host] host Beaker::Host object for generating /etc/hosts entry
495
+ # @param [Symbol] interface Symbol identifies which ip should be used for host
496
+ # @return [String] formatted hosts entry for host
497
+ # @api private
498
+ def etc_hosts_entry(host, interface = :ip)
499
+ name = host.name
500
+ domain = get_domain_name(host)
501
+ ip = host[interface.to_s]
502
+ "#{ip}\t#{name} #{name}.#{domain} #{host['dns_name']}\n"
503
+ end
504
+
492
505
  # Configure /etc/hosts for each node
493
506
  #
494
507
  # @return [void]
495
508
  # @api private
496
509
  def configure_hosts
497
510
  @hosts.each do |host|
498
- etc_hosts = "127.0.0.1\tlocalhost localhost.localdomain\n"
499
- name = host.name
500
- domain = get_domain_name(host)
501
- ip = host['private_ip']
502
- etc_hosts += "#{ip}\t#{name} #{name}.#{domain} #{host['dns_name']}\n"
503
- @hosts.each do |neighbor|
504
- if neighbor == host
505
- next
506
- end
507
- name = neighbor.name
508
- domain = get_domain_name(neighbor)
509
- ip = neighbor['ip']
510
- etc_hosts += "#{ip}\t#{name} #{name}.#{domain} #{neighbor['dns_name']}\n"
511
+ host_entries = @hosts.map do |h|
512
+ h == host ? etc_hosts_entry(h, :private_ip) : etc_hosts_entry(h)
511
513
  end
512
- set_etc_hosts(host, etc_hosts)
514
+ host_entries.unshift "127.0.0.1\tlocalhost localhost.localdomain\n"
515
+ set_etc_hosts(host, host_entries.join(''))
513
516
  end
517
+ nil
514
518
  end
515
519
 
516
520
  # Enables root for instances with custom username like ubuntu-amis
@@ -176,6 +176,13 @@ module Beaker
176
176
  RUN apt-get update
177
177
  RUN apt-get install -y openssh-server openssh-client #{Beaker::HostPrebuiltSteps::CUMULUS_PACKAGES.join(' ')}
178
178
  EOF
179
+ when /fedora-22/
180
+ dockerfile += <<-EOF
181
+ RUN dnf clean all
182
+ RUN dnf install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::UNIX_PACKAGES.join(' ')}
183
+ RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
184
+ RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
185
+ EOF
179
186
  when /^el-/, /centos/, /fedora/, /redhat/, /eos/
180
187
  dockerfile += <<-EOF
181
188
  RUN yum clean all
@@ -194,6 +194,10 @@ module Beaker
194
194
  http = Net::HTTP.new( uri.host, uri.port )
195
195
  request = Net::HTTP::Delete.new(uri.request_uri)
196
196
 
197
+ if @credentials[:vmpooler_token]
198
+ request['X-AUTH-TOKEN'] = @credentials[:vmpooler_token]
199
+ end
200
+
197
201
  begin
198
202
  response = http.request(request)
199
203
  rescue *SSH_EXCEPTIONS => e
@@ -26,6 +26,7 @@ module Beaker
26
26
  BRIGHT_MAGENTA = "\e[01;35m"
27
27
  BRIGHT_CYAN = "\e[01;36m"
28
28
  BRIGHT_WHITE = "\e[01;37m"
29
+ NONE = ""
29
30
 
30
31
  # The defined log levels. Each log level also reports messages at levels lower than itself
31
32
  LOG_LEVELS = {
@@ -186,6 +187,16 @@ module Beaker
186
187
  optionally_color GREY, string, false
187
188
  end
188
189
 
190
+ # Custom reporting for messages generated by host SUTs - to preserve output
191
+ # Will not print unless we are at {LOG_LEVELS} 'verbose' or higher.
192
+ # Preserves outout by not stripping out colour codes
193
+ # @param args[Array<String>] Strings to be reported
194
+ def color_host_output *args
195
+ return unless is_verbose?
196
+ string = args.join
197
+ optionally_color NONE, string, false
198
+ end
199
+
189
200
  # Custom reporting for performance/sysstat messages
190
201
  # Will not print unless we are at {LOG_LEVELS} 'debug' or higher.
191
202
  # @param args[Array<String>] Strings to be reported
@@ -270,7 +281,7 @@ module Beaker
270
281
  @destinations.each do |to|
271
282
  to.print color_code if @color
272
283
  to.send print_statement, convert( msg )
273
- to.print NORMAL if @color
284
+ to.print NORMAL if @color unless color_code == NONE
274
285
  end
275
286
  end
276
287
 
@@ -121,6 +121,15 @@ module Beaker
121
121
  @cmd_options[:color] = bool
122
122
  end
123
123
 
124
+ opts.on '--[no-]color-host-output',
125
+ 'Ensure SUT colored output is preserved',
126
+ '(default: false)' do |bool|
127
+ @cmd_options[:color_host_output] = bool
128
+ if bool
129
+ @cmd_options[:color_host_output] = true
130
+ end
131
+ end
132
+
124
133
  opts.on '--log-level LEVEL',
125
134
  'Log level',
126
135
  'Supported LEVEL keywords:',
@@ -1,44 +1,11 @@
1
+ require 'stringify-hash'
2
+
1
3
  module Beaker
2
4
  module Options
3
5
 
4
6
  # A hash that treats Symbol and String keys interchangeably
5
7
  # and recursively merges hashes
6
- class OptionsHash < Hash
7
-
8
- # The dividor between elements when OptionsHash is dumped
9
- DIV = ' '
10
-
11
- # The end of line when dumping
12
- EOL = "\n"
13
-
14
- # Get value for given key, search for both k as String and k as Symbol,
15
- # if not present return nil
16
- #
17
- # @param [Object] k The key to find, searches for both k as String
18
- # and k as Symbol
19
- #
20
- # @example Use this method to return the value for a given key
21
- # a['key'] = 'value'
22
- # a['key'] == a[:key] == 'value'
23
- #
24
- # @return [nil, Object] Return the Object found at given key,
25
- # or nil if no Object found
26
- def [] k
27
- super(k.to_s) || super(k.to_sym)
28
- end
29
-
30
- # Set Symbol key to Object value
31
- # @param [Object] k The key to associated with the value,
32
- # converted to Symbol key
33
- # @param [Object] v The value to store in the ObjectHash
34
- #
35
- # @example Use this method to set the value for a key
36
- # a['key'] = 'value'
37
- #
38
- # @return [Object] Return the Object value just stored
39
- def []=k,v
40
- super(k.to_sym, v)
41
- end
8
+ class OptionsHash < StringifyHash
42
9
 
43
10
  # Determine if type of ObjectHash is pe, defaults to true
44
11
  #
@@ -66,271 +33,11 @@ module Beaker
66
33
  :pe
67
34
  when /foss/
68
35
  :foss
69
- when /aio/
70
- :aio
71
36
  else
72
37
  :foss
73
38
  end
74
39
  end
75
40
 
76
- # Determine if key is stored in ObjectHash
77
- # @param [Object] k The key to find in ObjectHash, searches for
78
- # both k as String and k as Symbol
79
- #
80
- # @example Use this method to set the value for a key
81
- # a['key'] = 'value'
82
- # a.has_key[:key] == true
83
- #
84
- # @return [Boolean]
85
- def has_key? k
86
- super(k.to_s) || super(k.to_sym)
87
- end
88
-
89
- # Determine key=>value entry in OptionsHash, remove both value at
90
- # String key and value at Symbol key
91
- #
92
- # @param [Object] k The key to delete in ObjectHash,
93
- # deletes both k as String and k as Symbol
94
- #
95
- # @example Use this method to set the value for a key
96
- # a['key'] = 'value'
97
- # a.delete[:key] == 'value'
98
- #
99
- # @return [Object, nil] The Object deleted at value,
100
- # nil if no Object deleted
101
- def delete k
102
- super(k.to_s) || super(k.to_sym)
103
- end
104
-
105
- # Recursively merge and OptionsHash with an OptionsHash or Hash
106
- #
107
- # @param [OptionsHash] base The hash to merge into
108
- # @param [OptionsHash, Hash] hash The hash to merge from
109
- #
110
- # @example
111
- # base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
112
- # hash = { :key => { :subkey1 => 'newval'} }
113
- #
114
- # rmerge(base, hash)
115
- # #=> {:key =>
116
- # {:subkey1 => 'newval',
117
- # :subkey2 => 'subval'}}
118
- #
119
- # @return [OptionsHash] The combined bash and hash
120
- def rmerge base, hash
121
- return base unless hash.is_a?(Hash) || hash.is_a?(OptionsHash)
122
- hash.each do |key, v|
123
- if (base[key].is_a?(Hash) || base[key].is_a?(OptionsHash)) && (hash[key].is_a?(Hash) || hash[key].is_a?(OptionsHash))
124
- rmerge(base[key], hash[key])
125
- elsif hash[key].is_a?(Hash)
126
- base[key] = OptionsHash.new.merge(hash[key])
127
- else
128
- base[key]= hash[key]
129
- end
130
- end
131
- base
132
- end
133
-
134
- # Create new OptionsHash from recursively merged self with an OptionsHash or Hash
135
- #
136
- # @param [OptionsHash, Hash] hash The hash to merge from
137
- #
138
- # @example
139
- # base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
140
- # hash = { :key => { :subkey1 => 'newval'} }
141
- #
142
- # base.merge(hash)
143
- # #=> {:key =>
144
- # {:subkey1 => 'newval',
145
- # :subkey2 => 'subval' }
146
- #
147
- # @return [OptionsHash] The combined hash
148
- def merge hash
149
- #make a deep copy into an empty hash object
150
- merged_hash = rmerge(OptionsHash.new, self)
151
- rmerge(merged_hash, hash)
152
- end
153
-
154
- # Recursively merge self with an OptionsHash or Hash
155
- #
156
- # @param [OptionsHash, Hash] hash The hash to merge from
157
- #
158
- # @example
159
- # base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
160
- # hash = { :key => { :subkey1 => 'newval'} }
161
- #
162
- # base.merge!(hash)
163
- # #=> {:key =>
164
- # {:subkey1 => 'newval',
165
- # :subkey2 => 'subval' }
166
- #
167
- # @return [OptionsHash] The combined hash
168
- def merge! hash
169
- rmerge(self, hash)
170
- end
171
-
172
- # Helper for formatting collections
173
- # Computes the indentation level for elements of the collection
174
- # Yields indentation to block to so the caller can create
175
- # map of element strings
176
- # Places delimiters in the correct location
177
- # Joins everything with correct EOL
178
- #
179
- #
180
- # !@visibility private
181
- def as_coll( opening, closing, in_lvl, in_inc, &block )
182
- delim_indent = in_inc * in_lvl
183
- elem_indent = in_inc * (in_lvl + 1)
184
-
185
- open_brace = opening
186
- close_brace = delim_indent + closing
187
-
188
- fmtd_coll = block.call( elem_indent )
189
- str_coll = fmtd_coll.join( ',' + EOL )
190
-
191
- return open_brace + EOL + str_coll + EOL + close_brace
192
- end
193
-
194
- # Pretty prints a collection
195
- #
196
- # @param [Enumerable] collection The collection to be printed
197
- # @param [Integer] in_lvl The level of indentation
198
- # @param [String] in_inc The increment to indent
199
- #
200
- # @example
201
- # base = {:key => { :subkey1 => 'subval', :subkey2 => ['subval'] }}
202
- # self.fmt_collection( base )
203
- # #=> '{
204
- # "key": {
205
- # "subkey": "subval",
206
- # "subkey2": [
207
- # "subval"
208
- # ]
209
- # }
210
- # }'
211
- #
212
- # @return [String] The collection as a pretty JSON object
213
- def fmt_collection( collection, in_lvl = 0, in_inc = DIV )
214
- if collection.respond_to? :each_pair
215
- string = fmt_assoc( collection, in_lvl, in_inc )
216
- else
217
- string = fmt_list( collection, in_lvl, in_inc )
218
- end
219
-
220
- return string
221
- end
222
-
223
- # Pretty prints an associative collection
224
- #
225
- # @param [#each_pair] coll The collection to be printed
226
- # @param [Integer] in_lvl The level of indentation
227
- # @param [String] in_inc The increment to indent
228
- #
229
- # @example
230
- # base = { :key => 'value', :key2 => 'value' }
231
- # self.fmt_assoc( base )
232
- # #=> '{
233
- # "key": "value",
234
- # "key2": "value"
235
- # }'
236
- #
237
- # @return [String] The collection as a pretty JSON object
238
- def fmt_assoc( coll, in_lvl = 0, in_inc = DIV )
239
- if coll.empty?
240
- return '{}'
241
- else
242
- as_coll '{', '}', in_lvl, in_inc do |elem_indent|
243
- coll.map do |key, value|
244
- assoc_line = elem_indent + '"' + key.to_s + '"' + ': '
245
- assoc_line += fmt_value( value, in_lvl, in_inc )
246
- end
247
- end
248
- end
249
- end
250
-
251
- # Pretty prints a list collection
252
- #
253
- # @param [#each] coll The collection to be printed
254
- # @param [Integer] in_lvl The level of indentation
255
- # @param [String] in_inc The increment to indent
256
- #
257
- # @example
258
- # base = [ 'first', 'second' ]
259
- # self.fmt_list( base )
260
- # #=> '[
261
- # "first",
262
- # "second"
263
- # ]'
264
- #
265
- # @return [String] The collection as a pretty JSON object
266
- def fmt_list( coll, in_lvl = 0, in_inc = DIV )
267
- if coll.empty?
268
- return '[]'
269
- else
270
- as_coll '[', ']', in_lvl, in_inc do |indent|
271
- coll.map do |el|
272
- indent + fmt_value( el, in_lvl, in_inc )
273
- end
274
- end
275
- end
276
- end
277
-
278
- # Chooses between collection and primitive formatting
279
- #
280
- # !@visibility private
281
- def fmt_value( value, in_lvl = 0, in_inc = DIV )
282
- if value.kind_of? Enumerable and not value.is_a? String
283
- fmt_collection( value, in_lvl + 1, in_inc )
284
- else
285
- fmt_basic( value )
286
- end
287
- end
288
-
289
- # Pretty prints primitive JSON values
290
- #
291
- # @param [Object] value The collection to be printed
292
- #
293
- # @example
294
- # self.fmt_value( 4 )
295
- # #=> '4'
296
- #
297
- # @example
298
- # self.fmt_value( true )
299
- # #=> 'true'
300
- #
301
- # @example
302
- # self.fmt_value( nil )
303
- # #=> 'null'
304
- #
305
- # @example
306
- # self.fmt_value( 'string' )
307
- # #=> '"string"'
308
- #
309
- # @return [String] The value as a valid JSON primitive
310
- def fmt_basic( value )
311
- case value
312
- when Numeric, TrueClass, FalseClass then value.to_s
313
- when NilClass then "null"
314
- else "\"#{value}\""
315
- end
316
- end
317
-
318
- # Pretty print the options as JSON
319
- #
320
- # @example
321
- # base = { :key => { :subkey1 => 'subval', :subkey2 => 'subval' } }
322
- # base.dump
323
- # #=> '{
324
- # "key": {
325
- # "subkey1": "subval",
326
- # "subkey2": 2
327
- # }
328
- # }
329
- #
330
- # @return [String] The description of self
331
- def dump
332
- fmt_collection( self, 0, DIV )
333
- end
334
41
  end
335
42
  end
336
43
  end