chef_backup 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/chef_backup.rb +8 -8
- data/lib/chef_backup/config.rb +17 -17
- data/lib/chef_backup/data_map.rb +18 -12
- data/lib/chef_backup/deep_merge.rb +145 -0
- data/lib/chef_backup/helpers.rb +46 -10
- data/lib/chef_backup/logger.rb +2 -2
- data/lib/chef_backup/mash.rb +226 -0
- data/lib/chef_backup/runner.rb +11 -13
- data/lib/chef_backup/strategy.rb +10 -10
- data/lib/chef_backup/strategy/backup/custom.rb +1 -2
- data/lib/chef_backup/strategy/backup/ebs.rb +3 -6
- data/lib/chef_backup/strategy/backup/lvm.rb +2 -4
- data/lib/chef_backup/strategy/backup/object.rb +2 -4
- data/lib/chef_backup/strategy/backup/tar.rb +23 -7
- data/lib/chef_backup/strategy/restore/tar.rb +69 -43
- data/lib/chef_backup/version.rb +1 -1
- metadata +20 -168
- data/.gitignore +0 -23
- data/.kitchen.yml +0 -30
- data/.rubocop.yml +0 -21
- data/.travis.yml +0 -6
- data/Gemfile +0 -4
- data/Guardfile +0 -22
- data/README.md +0 -21
- data/Rakefile +0 -44
- data/chef_backup.gemspec +0 -33
- data/spec/fixtures/chef-server-running.json +0 -589
- data/spec/spec_helper.rb +0 -98
- data/spec/unit/data_map_spec.rb +0 -59
- data/spec/unit/helpers_spec.rb +0 -88
- data/spec/unit/runner_spec.rb +0 -185
- data/spec/unit/shared_examples/helpers.rb +0 -20
- data/spec/unit/strategy/backup/lvm_spec.rb +0 -0
- data/spec/unit/strategy/backup/shared_examples/backup.rb +0 -92
- data/spec/unit/strategy/backup/tar_spec.rb +0 -327
- data/spec/unit/strategy/restore/lvm_spec.rb +0 -0
- data/spec/unit/strategy/restore/shared_examples/restore.rb +0 -84
- data/spec/unit/strategy/restore/tar_spec.rb +0 -255
- data/spec/unit/strategy_spec.rb +0 -36
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b4e824870ac3e10eb6ffaa5bedd55483721a1af6b15cb61c7cb58cc2ccbcf7d9
|
4
|
+
data.tar.gz: 73be0c4d4911cd7f2f238aa0c9bd93b67a5fe7fdbfeb90294a8bfbffdcc42d7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 718ef11067a9e995f10bef3ccee17ac176b42a9a08fd9c3eebad64856dd5fda41f63c3e75e82d5e22fbb37dff521c0dd8e231436a580ace946366fb7dc9f1bc3
|
7
|
+
data.tar.gz: 03d05f2b1fd933cbd1842fc3563392b63f91127697ab17ae066eaf7c19f094c9c14aaeb083b9bbcbd580db0eb6ed0a71a67b20371821466308a2e1103a8fcbfb
|
data/lib/chef_backup.rb
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
#
|
3
3
|
# All Rights Reserved
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
5
|
+
require "chef_backup/version"
|
6
|
+
require "chef_backup/exceptions"
|
7
|
+
require "chef_backup/config"
|
8
|
+
require "chef_backup/logger"
|
9
|
+
require "chef_backup/data_map"
|
10
|
+
require "chef_backup/helpers"
|
11
|
+
require "chef_backup/runner"
|
12
|
+
require "chef_backup/strategy"
|
data/lib/chef_backup/config.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "fileutils"
|
2
|
+
require "json"
|
3
|
+
require "forwardable"
|
4
4
|
|
5
5
|
module ChefBackup
|
6
6
|
# ChefBackup Global Config
|
7
7
|
class Config
|
8
8
|
extend Forwardable
|
9
9
|
|
10
|
-
DEFAULT_BASE =
|
10
|
+
DEFAULT_BASE = "private_chef".freeze
|
11
11
|
DEFAULT_CONFIG = {
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
}
|
12
|
+
"backup" => {
|
13
|
+
"always_dump_db" => true,
|
14
|
+
"strategy" => "none",
|
15
|
+
"export_dir" => "/var/opt/chef-backup",
|
16
|
+
"project_name" => "opscode",
|
17
|
+
"ctl-command" => "chef-server-ctl",
|
18
|
+
"running_filepath" => "/etc/opscode/chef-server-running.json",
|
19
|
+
"database_name" => "opscode_chef",
|
20
|
+
},
|
21
21
|
}.freeze
|
22
22
|
|
23
23
|
class << self
|
@@ -50,11 +50,11 @@ module ChefBackup
|
|
50
50
|
# @param config [Hash] a Hash of the private-chef-running.json
|
51
51
|
#
|
52
52
|
def initialize(config = {})
|
53
|
-
config[
|
54
|
-
base = config[
|
53
|
+
config["config_base"] ||= DEFAULT_BASE
|
54
|
+
base = config["config_base"]
|
55
55
|
config[base] ||= {}
|
56
|
-
config[base][
|
57
|
-
config[base][
|
56
|
+
config[base]["backup"] ||= {}
|
57
|
+
config[base]["backup"] = DEFAULT_CONFIG["backup"].merge(config[base]["backup"])
|
58
58
|
@config = config
|
59
59
|
end
|
60
60
|
|
data/lib/chef_backup/data_map.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "time"
|
2
2
|
|
3
3
|
module ChefBackup
|
4
4
|
# DataMap class to store data about the data we're backing up
|
@@ -11,27 +11,32 @@ module ChefBackup
|
|
11
11
|
attr_writer :data_map
|
12
12
|
end
|
13
13
|
|
14
|
-
attr_accessor :strategy, :backup_time, :topology, :configs, :services, :ha
|
14
|
+
attr_accessor :strategy, :backup_time, :topology, :configs, :services, :ha, :versions
|
15
15
|
|
16
16
|
def initialize
|
17
17
|
@services = {}
|
18
18
|
@configs = {}
|
19
|
+
@versions = {}
|
19
20
|
@ha = {}
|
20
21
|
yield self if block_given?
|
21
22
|
|
22
23
|
@backup_time ||= Time.now.iso8601
|
23
|
-
@strategy ||=
|
24
|
-
@toplogy ||=
|
24
|
+
@strategy ||= "none"
|
25
|
+
@toplogy ||= "idontknow"
|
25
26
|
end
|
26
27
|
|
27
28
|
def add_service(service, data_dir)
|
28
29
|
@services[service] ||= {}
|
29
|
-
@services[service][
|
30
|
+
@services[service]["data_dir"] = data_dir
|
30
31
|
end
|
31
32
|
|
32
33
|
def add_config(config, path)
|
33
34
|
@configs[config] ||= {}
|
34
|
-
@configs[config][
|
35
|
+
@configs[config]["data_dir"] = path
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_version(project_name, data)
|
39
|
+
@versions[project_name] = data
|
35
40
|
end
|
36
41
|
|
37
42
|
def add_ha_info(k, v)
|
@@ -40,12 +45,13 @@ module ChefBackup
|
|
40
45
|
|
41
46
|
def manifest
|
42
47
|
{
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
"strategy" => strategy,
|
49
|
+
"backup_time" => backup_time,
|
50
|
+
"topology" => topology,
|
51
|
+
"ha" => ha,
|
52
|
+
"services" => services,
|
53
|
+
"configs" => configs,
|
54
|
+
"versions" => versions,
|
49
55
|
}
|
50
56
|
end
|
51
57
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@chef.io>)
|
3
|
+
# Author:: Steve Midgley (http://www.misuse.org/science)
|
4
|
+
# Copyright:: Copyright 2009-2016, Chef Software Inc.
|
5
|
+
# Copyright:: Copyright 2008-2016, Steve Midgley
|
6
|
+
# License:: Apache License, Version 2.0
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
require 'chef_backup/mash'
|
20
|
+
|
21
|
+
#
|
22
|
+
# DANGER! THIS FILE WAS VENDORDED FROM CHEF. IF YOU ARE
|
23
|
+
# MAKING A CHANGE HERE, CONSIDER WHETHER IT IS REQUIRED IN CHEF ALSO?
|
24
|
+
#
|
25
|
+
module ChefBackup
|
26
|
+
module Mixin
|
27
|
+
# == Chef::Mixin::DeepMerge
|
28
|
+
# Implements a deep merging algorithm for nested data structures.
|
29
|
+
# ==== Notice:
|
30
|
+
# This code was originally imported from deep_merge by Steve Midgley.
|
31
|
+
# deep_merge is available under the MIT license from
|
32
|
+
# http://trac.misuse.org/science/wiki/DeepMerge
|
33
|
+
module DeepMerge
|
34
|
+
|
35
|
+
extend self
|
36
|
+
|
37
|
+
def merge(first, second)
|
38
|
+
first = Mash.new(first) unless first.kind_of?(Mash)
|
39
|
+
second = Mash.new(second) unless second.kind_of?(Mash)
|
40
|
+
|
41
|
+
DeepMerge.deep_merge(second, first)
|
42
|
+
end
|
43
|
+
|
44
|
+
class InvalidParameter < StandardError; end
|
45
|
+
|
46
|
+
# Deep Merge core documentation.
|
47
|
+
# deep_merge! method permits merging of arbitrary child elements. The two top level
|
48
|
+
# elements must be hashes. These hashes can contain unlimited (to stack limit) levels
|
49
|
+
# of child elements. These child elements to not have to be of the same types.
|
50
|
+
# Where child elements are of the same type, deep_merge will attempt to merge them together.
|
51
|
+
# Where child elements are not of the same type, deep_merge will skip or optionally overwrite
|
52
|
+
# the destination element with the contents of the source element at that level.
|
53
|
+
# So if you have two hashes like this:
|
54
|
+
# source = {:x => [1,2,3], :y => 2}
|
55
|
+
# dest = {:x => [4,5,'6'], :y => [7,8,9]}
|
56
|
+
# dest.deep_merge!(source)
|
57
|
+
# Results: {:x => [1,2,3,4,5,'6'], :y => 2}
|
58
|
+
# By default, "deep_merge!" will overwrite any unmergeables and merge everything else.
|
59
|
+
# To avoid this, use "deep_merge" (no bang/exclamation mark)
|
60
|
+
def deep_merge!(source, dest)
|
61
|
+
# if dest doesn't exist, then simply copy source to it
|
62
|
+
if dest.nil?
|
63
|
+
dest = source; return dest
|
64
|
+
end
|
65
|
+
|
66
|
+
case source
|
67
|
+
when nil
|
68
|
+
dest
|
69
|
+
when Hash
|
70
|
+
if dest.kind_of?(Hash)
|
71
|
+
source.each do |src_key, src_value|
|
72
|
+
if dest[src_key]
|
73
|
+
dest[src_key] = deep_merge!(src_value, dest[src_key])
|
74
|
+
else # dest[src_key] doesn't exist so we take whatever source has
|
75
|
+
dest[src_key] = src_value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
else # dest isn't a hash, so we overwrite it completely
|
79
|
+
dest = source
|
80
|
+
end
|
81
|
+
when Array
|
82
|
+
if dest.kind_of?(Array)
|
83
|
+
dest = dest | source
|
84
|
+
else
|
85
|
+
dest = source
|
86
|
+
end
|
87
|
+
when String
|
88
|
+
dest = source
|
89
|
+
else # src_hash is not an array or hash, so we'll have to overwrite dest
|
90
|
+
dest = source
|
91
|
+
end
|
92
|
+
dest
|
93
|
+
end # deep_merge!
|
94
|
+
|
95
|
+
def hash_only_merge(merge_onto, merge_with)
|
96
|
+
hash_only_merge!(safe_dup(merge_onto), safe_dup(merge_with))
|
97
|
+
end
|
98
|
+
|
99
|
+
def safe_dup(thing)
|
100
|
+
thing.dup
|
101
|
+
rescue TypeError
|
102
|
+
thing
|
103
|
+
end
|
104
|
+
|
105
|
+
# Deep merge without Array merge.
|
106
|
+
# `merge_onto` is the object that will "lose" in case of conflict.
|
107
|
+
# `merge_with` is the object whose values will replace `merge_onto`s
|
108
|
+
# values when there is a conflict.
|
109
|
+
def hash_only_merge!(merge_onto, merge_with)
|
110
|
+
# If there are two Hashes, recursively merge.
|
111
|
+
if merge_onto.kind_of?(Hash) && merge_with.kind_of?(Hash)
|
112
|
+
merge_with.each do |key, merge_with_value|
|
113
|
+
value =
|
114
|
+
if merge_onto.has_key?(key)
|
115
|
+
hash_only_merge(merge_onto[key], merge_with_value)
|
116
|
+
else
|
117
|
+
merge_with_value
|
118
|
+
end
|
119
|
+
|
120
|
+
if merge_onto.respond_to?(:public_method_that_only_deep_merge_should_use)
|
121
|
+
# we can't call ImmutableMash#[]= because its immutable, but we need to mutate it to build it in-place
|
122
|
+
merge_onto.public_method_that_only_deep_merge_should_use(key, value)
|
123
|
+
else
|
124
|
+
merge_onto[key] = value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
merge_onto
|
128
|
+
|
129
|
+
# If merge_with is nil, don't replace merge_onto
|
130
|
+
elsif merge_with.nil?
|
131
|
+
merge_onto
|
132
|
+
|
133
|
+
# In all other cases, replace merge_onto with merge_with
|
134
|
+
else
|
135
|
+
merge_with
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def deep_merge(source, dest)
|
140
|
+
deep_merge!(safe_dup(source), safe_dup(dest))
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
data/lib/chef_backup/helpers.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'json'
|
2
3
|
require 'mixlib/shellout'
|
3
4
|
require 'chef_backup/config'
|
4
5
|
require 'chef_backup/logger'
|
5
6
|
|
7
|
+
# rubocop:disable ModuleLength
|
6
8
|
# rubocop:disable IndentationWidth
|
7
9
|
module ChefBackup
|
8
10
|
# Common helper methods that are usefull in many classes
|
@@ -27,7 +29,7 @@ module Helpers
|
|
27
29
|
'ctl_command' => 'opscode-analytics-ctl'
|
28
30
|
},
|
29
31
|
'chef-ha' => {
|
30
|
-
'config_file' => 'etc/opscode/chef-server.rb'
|
32
|
+
'config_file' => '/etc/opscode/chef-server.rb'
|
31
33
|
},
|
32
34
|
'chef-sync' => {
|
33
35
|
'config_file' => '/etc/chef-sync/chef-sync.rb',
|
@@ -69,6 +71,19 @@ module Helpers
|
|
69
71
|
ChefBackup::Logger.logger.log(message, level)
|
70
72
|
end
|
71
73
|
|
74
|
+
# Note that when we are in the backup codepath, we have access to a running
|
75
|
+
# chef server and hence, the ctl command puts all our flags under the current
|
76
|
+
# running service namespace. The lets the default configuration of the server
|
77
|
+
# provide flags that the user doesn't necessarily provide on the command line.
|
78
|
+
#
|
79
|
+
# During the restore codepath, there may be no running chef server. This means
|
80
|
+
# that we need to be paranoid about the existence of the service_config hash.
|
81
|
+
def shell_timeout
|
82
|
+
option = config['shell_out_timeout'] ||
|
83
|
+
(service_config && service_config['backup']['shell_out_timeout'])
|
84
|
+
option.to_f unless option.nil?
|
85
|
+
end
|
86
|
+
|
72
87
|
#
|
73
88
|
# @param file [String] A path to a file on disk
|
74
89
|
# @param exception [Exception] An exception to raise if file is not present
|
@@ -81,7 +96,9 @@ module Helpers
|
|
81
96
|
end
|
82
97
|
|
83
98
|
def shell_out(*command)
|
84
|
-
|
99
|
+
options = command.last.is_a?(Hash) ? command.pop : {}
|
100
|
+
opts_with_defaults = { 'timeout' => shell_timeout }.merge(options)
|
101
|
+
cmd = Mixlib::ShellOut.new(*command, opts_with_defaults)
|
85
102
|
cmd.live_stream ||= $stdout.tty? ? $stdout : nil
|
86
103
|
cmd.run_command
|
87
104
|
cmd
|
@@ -101,6 +118,11 @@ module Helpers
|
|
101
118
|
"/opt/#{project_name}"
|
102
119
|
end
|
103
120
|
|
121
|
+
def addon_install_dir(name)
|
122
|
+
# can use extra field in SERVER_ADD_ONS to extend if someone isn't following this pattern.
|
123
|
+
"/opt/#{name}"
|
124
|
+
end
|
125
|
+
|
104
126
|
def base_config_dir
|
105
127
|
"/etc/#{project_name}"
|
106
128
|
end
|
@@ -114,7 +136,9 @@ module Helpers
|
|
114
136
|
end
|
115
137
|
|
116
138
|
def pg_options
|
117
|
-
config['pg_options'] ||
|
139
|
+
config['pg_options'] ||
|
140
|
+
(service_config && service_config['backup']['pg_options']) ||
|
141
|
+
DEFAULT_PG_OPTIONS
|
118
142
|
end
|
119
143
|
|
120
144
|
def all_services
|
@@ -126,7 +150,7 @@ module Helpers
|
|
126
150
|
end
|
127
151
|
|
128
152
|
def disabled_services
|
129
|
-
all_services.
|
153
|
+
all_services.reject { |sv| service_enabled?(sv) }
|
130
154
|
end
|
131
155
|
|
132
156
|
def service_enabled?(service)
|
@@ -177,12 +201,10 @@ module Helpers
|
|
177
201
|
end
|
178
202
|
|
179
203
|
def enabled_addons
|
180
|
-
SERVER_ADD_ONS.select do |
|
181
|
-
|
182
|
-
File.directory?(File.dirname(config['config_file']))
|
183
|
-
|
184
|
-
false
|
185
|
-
end
|
204
|
+
SERVER_ADD_ONS.select do |name, config|
|
205
|
+
!config['config_file'].nil? &&
|
206
|
+
File.directory?(File.dirname(config['config_file'])) &&
|
207
|
+
File.directory?(addon_install_dir(name))
|
186
208
|
end
|
187
209
|
end
|
188
210
|
|
@@ -242,6 +264,20 @@ module Helpers
|
|
242
264
|
true
|
243
265
|
end
|
244
266
|
|
267
|
+
def version_from_manifest_file(file)
|
268
|
+
return :no_version if file.nil?
|
269
|
+
|
270
|
+
path = File.expand_path(file)
|
271
|
+
if File.exist?(path)
|
272
|
+
config = JSON.parse(File.read(path))
|
273
|
+
{ 'version' => config['build_version'],
|
274
|
+
'revision' => config['build_git_revision'],
|
275
|
+
'path' => path }
|
276
|
+
else
|
277
|
+
:no_version
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
245
281
|
private
|
246
282
|
|
247
283
|
def safe_key
|
data/lib/chef_backup/logger.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "highline"
|
2
2
|
|
3
3
|
module ChefBackup
|
4
4
|
# Basic Logging Class
|
@@ -15,7 +15,7 @@ module ChefBackup
|
|
15
15
|
attr_accessor :stdout
|
16
16
|
|
17
17
|
def initialize(logfile = nil)
|
18
|
-
$stdout = logfile ? File.open(logfile,
|
18
|
+
$stdout = logfile ? File.open(logfile, "ab") : $stdout
|
19
19
|
@highline = HighLine.new($stdin, $stdout)
|
20
20
|
end
|
21
21
|
|
@@ -0,0 +1,226 @@
|
|
1
|
+
# Copyright 2009-2016, Dan Kubb
|
2
|
+
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
# ---
|
23
|
+
# ---
|
24
|
+
|
25
|
+
# Some portions of blank.rb and mash.rb are verbatim copies of software
|
26
|
+
# licensed under the MIT license. That license is included below:
|
27
|
+
|
28
|
+
# Copyright 2005-2016, David Heinemeier Hansson
|
29
|
+
|
30
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
31
|
+
# a copy of this software and associated documentation files (the
|
32
|
+
# "Software"), to deal in the Software without restriction, including
|
33
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
34
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
35
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
36
|
+
# the following conditions:
|
37
|
+
|
38
|
+
# The above copyright notice and this permission notice shall be
|
39
|
+
# included in all copies or substantial portions of the Software.
|
40
|
+
|
41
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
42
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
43
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
44
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
45
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
46
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
47
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
48
|
+
|
49
|
+
# This class has dubious semantics and we only have it so that people can write
|
50
|
+
# params[:key] instead of params['key'].
|
51
|
+
class Mash < Hash
|
52
|
+
|
53
|
+
# @param constructor<Object>
|
54
|
+
# The default value for the mash. Defaults to an empty hash.
|
55
|
+
#
|
56
|
+
# @details [Alternatives]
|
57
|
+
# If constructor is a Hash, a new mash will be created based on the keys of
|
58
|
+
# the hash and no default value will be set.
|
59
|
+
def initialize(constructor = {})
|
60
|
+
if constructor.is_a?(Hash)
|
61
|
+
super()
|
62
|
+
update(constructor)
|
63
|
+
else
|
64
|
+
super(constructor)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param orig<Object> Mash being copied
|
69
|
+
#
|
70
|
+
# @return [Object] A new copied Mash
|
71
|
+
def initialize_copy(orig)
|
72
|
+
super
|
73
|
+
# Handle nested values
|
74
|
+
each do |k, v|
|
75
|
+
if v.kind_of?(Mash) || v.is_a?(Array)
|
76
|
+
self[k] = v.dup
|
77
|
+
end
|
78
|
+
end
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param key<Object> The default value for the mash. Defaults to nil.
|
83
|
+
#
|
84
|
+
# @details [Alternatives]
|
85
|
+
# If key is a Symbol and it is a key in the mash, then the default value will
|
86
|
+
# be set to the value matching the key.
|
87
|
+
def default(key = nil)
|
88
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
89
|
+
self[key]
|
90
|
+
else
|
91
|
+
super
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
96
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
97
|
+
|
98
|
+
# @param key<Object> The key to set.
|
99
|
+
# @param value<Object>
|
100
|
+
# The value to set the key to.
|
101
|
+
#
|
102
|
+
# @see Mash#convert_key
|
103
|
+
# @see Mash#convert_value
|
104
|
+
def []=(key, value)
|
105
|
+
regular_writer(convert_key(key), convert_value(value))
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param other_hash<Hash>
|
109
|
+
# A hash to update values in the mash with. The keys and the values will be
|
110
|
+
# converted to Mash format.
|
111
|
+
#
|
112
|
+
# @return [Mash] The updated mash.
|
113
|
+
def update(other_hash)
|
114
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
115
|
+
self
|
116
|
+
end
|
117
|
+
|
118
|
+
alias_method :merge!, :update
|
119
|
+
|
120
|
+
# @param key<Object> The key to check for. This will be run through convert_key.
|
121
|
+
#
|
122
|
+
# @return [Boolean] True if the key exists in the mash.
|
123
|
+
def key?(key)
|
124
|
+
super(convert_key(key))
|
125
|
+
end
|
126
|
+
|
127
|
+
# def include? def has_key? def member?
|
128
|
+
alias_method :include?, :key?
|
129
|
+
alias_method :has_key?, :key?
|
130
|
+
alias_method :member?, :key?
|
131
|
+
|
132
|
+
# @param key<Object> The key to fetch. This will be run through convert_key.
|
133
|
+
# @param *extras<Array> Default value.
|
134
|
+
#
|
135
|
+
# @return [Object] The value at key or the default value.
|
136
|
+
def fetch(key, *extras)
|
137
|
+
super(convert_key(key), *extras)
|
138
|
+
end
|
139
|
+
|
140
|
+
# @param *indices<Array>
|
141
|
+
# The keys to retrieve values for. These will be run through +convert_key+.
|
142
|
+
#
|
143
|
+
# @return [Array] The values at each of the provided keys
|
144
|
+
def values_at(*indices)
|
145
|
+
indices.collect { |key| self[convert_key(key)] }
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param hash<Hash> The hash to merge with the mash.
|
149
|
+
#
|
150
|
+
# @return [Mash] A new mash with the hash values merged in.
|
151
|
+
def merge(hash)
|
152
|
+
self.dup.update(hash)
|
153
|
+
end
|
154
|
+
|
155
|
+
# @param key<Object>
|
156
|
+
# The key to delete from the mash.\
|
157
|
+
def delete(key)
|
158
|
+
super(convert_key(key))
|
159
|
+
end
|
160
|
+
|
161
|
+
# @param *rejected<Array[(String, Symbol)] The mash keys to exclude.
|
162
|
+
#
|
163
|
+
# @return [Mash] A new mash without the selected keys.
|
164
|
+
#
|
165
|
+
# @example
|
166
|
+
# { :one => 1, :two => 2, :three => 3 }.except(:one)
|
167
|
+
# #=> { "two" => 2, "three" => 3 }
|
168
|
+
def except(*keys)
|
169
|
+
super(*keys.map { |k| convert_key(k) })
|
170
|
+
end
|
171
|
+
|
172
|
+
# Used to provide the same interface as Hash.
|
173
|
+
#
|
174
|
+
# @return [Mash] This mash unchanged.
|
175
|
+
def stringify_keys!; self end
|
176
|
+
|
177
|
+
# @return [Hash] The mash as a Hash with symbolized keys.
|
178
|
+
def symbolize_keys
|
179
|
+
h = Hash.new(default)
|
180
|
+
each { |key, val| h[key.to_sym] = val }
|
181
|
+
h
|
182
|
+
end
|
183
|
+
|
184
|
+
# @return [Hash] The mash as a Hash with string keys.
|
185
|
+
def to_hash
|
186
|
+
Hash.new(default).merge(self)
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [Mash] Convert a Hash into a Mash
|
190
|
+
# The input Hash's default value is maintained
|
191
|
+
def self.from_hash(hash)
|
192
|
+
mash = Mash.new(hash)
|
193
|
+
mash.default = hash.default
|
194
|
+
mash
|
195
|
+
end
|
196
|
+
|
197
|
+
protected
|
198
|
+
|
199
|
+
# @param key<Object> The key to convert.
|
200
|
+
#
|
201
|
+
# @param [Object]
|
202
|
+
# The converted key. If the key was a symbol, it will be converted to a
|
203
|
+
# string.
|
204
|
+
#
|
205
|
+
# @api private
|
206
|
+
def convert_key(key)
|
207
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
208
|
+
end
|
209
|
+
|
210
|
+
# @param value<Object> The value to convert.
|
211
|
+
#
|
212
|
+
# @return [Object]
|
213
|
+
# The converted value. A Hash or an Array of hashes, will be converted to
|
214
|
+
# their Mash equivalents.
|
215
|
+
#
|
216
|
+
# @api private
|
217
|
+
def convert_value(value)
|
218
|
+
if value.class == Hash
|
219
|
+
Mash.from_hash(value)
|
220
|
+
elsif value.is_a?(Array)
|
221
|
+
value.collect { |e| convert_value(e) }
|
222
|
+
else
|
223
|
+
value
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|