beaker 2.18.3 → 2.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/HISTORY.md +439 -2
- data/acceptance/lib/beaker/acceptance/install_utils.rb +58 -0
- data/acceptance/pre_suite/puppet_git/install.rb +6 -65
- data/acceptance/tests/foss_utils/clone_git_repo_on.rb +49 -0
- data/beaker.gemspec +2 -0
- data/lib/beaker/dsl/helpers/web_helpers.rb +2 -1
- data/lib/beaker/dsl/install_utils/aio_defaults.rb +0 -2
- data/lib/beaker/dsl/install_utils/foss_utils.rb +97 -60
- data/lib/beaker/dsl/install_utils/pe_utils.rb +30 -53
- data/lib/beaker/dsl/install_utils/puppet_utils.rb +43 -0
- data/lib/beaker/dsl/install_utils/windows_utils.rb +144 -0
- data/lib/beaker/dsl/roles.rb +20 -3
- data/lib/beaker/dsl/structure.rb +14 -3
- data/lib/beaker/host.rb +24 -3
- data/lib/beaker/host/unix/pkg.rb +9 -0
- data/lib/beaker/host/windows/exec.rb +3 -0
- data/lib/beaker/host_prebuilt_steps.rb +5 -9
- data/lib/beaker/hypervisor/aws_sdk.rb +22 -18
- data/lib/beaker/hypervisor/docker.rb +7 -0
- data/lib/beaker/hypervisor/vmpooler.rb +4 -0
- data/lib/beaker/logger.rb +12 -1
- data/lib/beaker/options/command_line_parser.rb +9 -0
- data/lib/beaker/options/options_hash.rb +3 -296
- data/lib/beaker/options/parser.rb +12 -0
- data/lib/beaker/options/presets.rb +0 -1
- data/lib/beaker/ssh_connection.rb +48 -23
- data/lib/beaker/test_case.rb +1 -1
- data/lib/beaker/version.rb +1 -1
- data/spec/beaker/dsl/helpers/web_helpers_spec.rb +10 -1
- data/spec/beaker/dsl/install_utils/foss_utils_spec.rb +194 -49
- data/spec/beaker/dsl/install_utils/pe_utils_spec.rb +112 -22
- data/spec/beaker/dsl/install_utils/puppet_utils_spec.rb +57 -0
- data/spec/beaker/dsl/install_utils/windows_utils_spec.rb +132 -0
- data/spec/beaker/dsl/roles_spec.rb +36 -5
- data/spec/beaker/dsl/structure_spec.rb +9 -2
- data/spec/beaker/host/unix/pkg_spec.rb +26 -6
- data/spec/beaker/host_prebuilt_steps_spec.rb +3 -2
- data/spec/beaker/host_spec.rb +18 -0
- data/spec/beaker/hypervisor/aixer_spec.rb +1 -1
- data/spec/beaker/hypervisor/aws_sdk_spec.rb +595 -58
- data/spec/beaker/hypervisor/docker_spec.rb +2 -1
- data/spec/beaker/hypervisor/solaris_spec.rb +1 -0
- data/spec/beaker/hypervisor/vagrant_spec.rb +2 -1
- data/spec/beaker/logger_spec.rb +39 -0
- data/spec/beaker/options/command_line_parser_spec.rb +2 -2
- data/spec/beaker/options/options_hash_spec.rb +1 -102
- data/spec/beaker/options/parser_spec.rb +19 -0
- data/spec/beaker/options/pe_version_scaper_spec.rb +11 -1
- data/spec/beaker/options/presets_spec.rb +8 -0
- data/spec/beaker/ssh_connection_spec.rb +39 -21
- data/spec/helpers.rb +9 -3
- data/spec/mocks.rb +2 -0
- metadata +34 -11
- data/lib/beaker/answers.rb +0 -143
- data/lib/beaker/answers/version20.rb +0 -120
- data/lib/beaker/answers/version28.rb +0 -121
- data/lib/beaker/answers/version30.rb +0 -227
- data/lib/beaker/answers/version32.rb +0 -44
- data/lib/beaker/answers/version34.rb +0 -51
- data/lib/beaker/answers/version38.rb +0 -29
- data/lib/beaker/answers/version40.rb +0 -44
- data/spec/beaker/answers_spec.rb +0 -547
data/lib/beaker/host.rb
CHANGED
@@ -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[
|
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
|
-
|
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
|
-
|
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
|
data/lib/beaker/host/unix/pkg.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 [
|
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 [
|
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 [
|
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 [
|
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
|
-
|
499
|
-
|
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
|
-
|
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
|
data/lib/beaker/logger.rb
CHANGED
@@ -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 <
|
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
|