configliere 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +1 -3
- data/CHANGELOG.textile +19 -0
- data/README.textile +49 -15
- data/VERSION +1 -1
- data/configliere.gemspec +12 -4
- data/examples/config_block.rb +11 -0
- data/examples/encrypted_script.rb +17 -0
- data/lib/configliere/commandline.rb +23 -6
- data/lib/configliere/config_block.rb +29 -13
- data/lib/configliere/config_file.rb +3 -1
- data/lib/configliere/core_ext/hash.rb +4 -2
- data/lib/configliere/core_ext/sash.rb +169 -0
- data/lib/configliere/define.rb +20 -2
- data/lib/configliere/encrypted.rb +2 -2
- data/lib/configliere/environment.rb +5 -4
- data/lib/configliere/param.rb +57 -34
- data/spec/configliere/config_file_spec.rb +7 -0
- data/spec/configliere/core_ext/hash_spec.rb +78 -0
- data/spec/configliere/core_ext/sash_spec.rb +313 -0
- data/spec/configliere/environment_spec.rb +2 -1
- data/spec/configliere/param_spec.rb +16 -10
- metadata +12 -4
- data/lib/configliere/commandline/commands.rb +0 -30
- data/lib/configliere/commandline/options.rb +0 -4
data/.document
CHANGED
data/CHANGELOG.textile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Note that while the version # is still in the 0.0.x range, the interface may change arbitrarily while we figure out the best simplest convenientest powerfullest way to arrange things
|
2
|
+
|
3
|
+
h2. Version 0.0.3 2010-01-15
|
4
|
+
|
5
|
+
* @Settings.param@ now only works for params that have been @#define@'d :
|
6
|
+
<pre>
|
7
|
+
Settings :no_yuo => 'oops'
|
8
|
+
Settings.no_yuo
|
9
|
+
#=> NoMethodError: undefined method `no_yuo' for {:no_yuo=>"oops"}:Configliere::Param
|
10
|
+
Settings.define :happy_param, :default => 'yay'
|
11
|
+
Settings.happy_param
|
12
|
+
#=> "yay"
|
13
|
+
</pre>
|
14
|
+
|
15
|
+
* Note that you *must* use symbols as keys (except for dotted notation for deep keys). See the README.
|
16
|
+
* You must now call @#use_environment@ as @Settings.use_environment :param => 'ENV_VAR'@. (The param is used as the key everywhere else, made this consistent).
|
17
|
+
* die takes an error code as option
|
18
|
+
* Added example scripts for encrypted and config_block scripts
|
19
|
+
* The directory path to a config_file will now be created automatically
|
data/README.textile
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
h1. Configliere
|
2
2
|
|
3
|
-
Configliere provides
|
3
|
+
Configliere provides discreet configuration for ruby scripts.
|
4
4
|
|
5
5
|
bq. So, Consigliere of mine, I think you should tell your Don what everyone knows. -- Don Corleone
|
6
6
|
|
7
7
|
You've got a script. It's got some settings. Some settings are for this module, some are for that module. Most of them don't change. Except on your laptop, where the paths are different. Or when you're in production mode. Or when you're testing from the command line.
|
8
8
|
|
9
|
-
Configliere
|
9
|
+
Configliere manages settings from many sources: static constants, simple config files, environment variables, commandline options, straight ruby. You don't have to predefine anything, but you can ask configliere to type-convert, require, document or password-obscure any of its fields. Modules can define config settings independently of each other and the main program.
|
10
10
|
|
11
11
|
h3. Example
|
12
12
|
|
@@ -18,7 +18,7 @@ Here's a simple example, using params from a config file and the command line. A
|
|
18
18
|
:sprats:
|
19
19
|
:jack: lean</pre>
|
20
20
|
|
21
|
-
A simple script:
|
21
|
+
(Note that all simple keys should be symbols, with an exception described below.) A simple script:
|
22
22
|
|
23
23
|
<pre>
|
24
24
|
#/usr/bin/env ruby
|
@@ -26,7 +26,7 @@ A simple script:
|
|
26
26
|
|
27
27
|
Settings.use(:commandline, :config_file,
|
28
28
|
:dish => 'spoon', :cow => 'moon')
|
29
|
-
Settings.read 'my_script.yaml'
|
29
|
+
Settings.read 'my_script.yaml' # reads ~/.configliere/my_script.yaml
|
30
30
|
Settings.resolve!
|
31
31
|
|
32
32
|
p Settings</pre>
|
@@ -37,6 +37,8 @@ Output:
|
|
37
37
|
./simple_script.rb --sprats.wife=fat --spider=drainspout
|
38
38
|
{:spider=>"drainspout", :cat=>"hat", :sprats=>{:wife=>"fat", :jack=>"lean"}, :cow=>"moon"} </pre>
|
39
39
|
|
40
|
+
For an extensive usage in production, see the "wukong gem.":http://github.com/mrflip/wukong
|
41
|
+
|
40
42
|
h3. Design goals:
|
41
43
|
|
42
44
|
* *Don't go outside the family*. Requires almost no external resources and almost no code in your script.
|
@@ -77,19 +79,49 @@ Retrieve them as:
|
|
77
79
|
|
78
80
|
<pre>
|
79
81
|
# hash keys
|
80
|
-
|
82
|
+
Settings[:dest_time] #=> '1955-11-05'
|
81
83
|
# deep keys
|
82
|
-
|
83
|
-
|
84
|
-
|
84
|
+
Settings[:delorean][:power_source] #=> 'plutonium'
|
85
|
+
Settings[:delorean][:missing] #=> nil
|
86
|
+
Settings[:delorean][:missing][:fail] #=> raises an error
|
85
87
|
# dotted keys resolve to deep keys
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
Settings['delorean.power_source'] #=> 'plutonium'
|
89
|
+
Settings['delorean.missing'] #=> nil
|
90
|
+
Settings['delorean.missing.fail'] #=> nil
|
89
91
|
# method-like (no deep keys tho)
|
90
92
|
Settings.dest_time #=> '1955-11-05'
|
91
93
|
</pre>
|
92
94
|
|
95
|
+
h3. Shortcut syntax for deep keys
|
96
|
+
|
97
|
+
You can use a 'dotted key' like 'delorean.power_source' as simple notation for a deep key: Settings['delorean.power_source'] is equivalent to Settings[:delorean][:power_source]. You can use a dotted key in any simple reference:
|
98
|
+
<pre>
|
99
|
+
Settings['delorean.power_source'] = "Mr. Fusion"
|
100
|
+
Settings[:delorean][:power_source]
|
101
|
+
#=> "Mr. Fusion"
|
102
|
+
Settings.delete('delorean.power_source')
|
103
|
+
#=> "Mr. Fusion"
|
104
|
+
Settings
|
105
|
+
#=> { :delorean => {} }
|
106
|
+
</pre>
|
107
|
+
|
108
|
+
Intermediate keys "auto-vivify" (automatically create any intervening hashes):
|
109
|
+
<pre>
|
110
|
+
Settings['one.two.three'] = "To tha Fo'"
|
111
|
+
# Settings is { :one => { :two => { :three => "To tha Fo'" } }, :delorean => { :power_source => "Mr. Fusion" }
|
112
|
+
</pre>
|
113
|
+
|
114
|
+
Do *not* use a dotted key except as a simple reference. You'll ruin Christmas:
|
115
|
+
|
116
|
+
<pre>
|
117
|
+
Settings.defaults :this => "that", "made.of.fail" => "oops"
|
118
|
+
#=> { :this => "that", :"made.of.fail" => "oops" } # !!! BROKEN !!!
|
119
|
+
</pre>
|
120
|
+
|
121
|
+
This may change once we figure out how to handle it all more cleanly, and how to balance "Keep it Simple, Stupid" with "Keep it Convenient, Kilroy".
|
122
|
+
|
123
|
+
h3. Only basic functionality loaded by default
|
124
|
+
|
93
125
|
Configliere doesn't load any other functionality by default -- you may not want to load config files, or environment variable handling, and so forth. You can require each file directly, or call @Configliere.use@ with a list of mixins (:all to load all functionality).
|
94
126
|
|
95
127
|
<pre>
|
@@ -128,7 +160,7 @@ When you read directly from a file you should leave off the top-level settings g
|
|
128
160
|
:roads_needed: ~
|
129
161
|
</pre>
|
130
162
|
|
131
|
-
|
163
|
+
Save defaults by inserting a line like:
|
132
164
|
|
133
165
|
<pre>
|
134
166
|
Settings.save(:time_machine) # merges into ~/.configliere.yaml, under :time_machine
|
@@ -139,10 +171,12 @@ You can save defaults with
|
|
139
171
|
h2. Environment Variables
|
140
172
|
|
141
173
|
<pre>
|
142
|
-
Settings.use_environment 'DEST_TIME',
|
174
|
+
Settings.use_environment 'DEST_TIME', :password => 'TM_PASS', 'delorean.power_source' => 'POWER_SOURCE'
|
143
175
|
</pre>
|
144
176
|
|
145
|
-
As usual, dotted keys set the corresponeding nested key ('delorean.power_source' sets
|
177
|
+
As usual, dotted keys set the corresponeding nested key (@'delorean.power_source'@ sets @Settings[:delorean][:power_source]@). You can alternatively set up environment variables with @define 'delorean.power_source', :environment => 'POWER_SOURCE'@ - see below.
|
178
|
+
|
179
|
+
**NOTE**: The interface to #use_environment has changed since v0.2. You must now call it as @Settings.use_environment :param => 'ENV_VAR'@, (since everywhere else the param is used as the key).
|
146
180
|
|
147
181
|
h2. Command-line parameters
|
148
182
|
|
@@ -155,7 +189,7 @@ h2. Command-line parameters
|
|
155
189
|
|
156
190
|
Interpretation of command-line parameters:
|
157
191
|
* *name-val params*: @--param=val@ sets @Configliere[:param]@ to val.
|
158
|
-
* *boolean params*: @--param@ sets @Configliere[:param]@ to be true.
|
192
|
+
* *boolean params*: @--param@ sets @Configliere[:param]@ to be true. @--param=""@ sets @Configliere[:param]@ to be nil.
|
159
193
|
* *scoped params*: @--group-sub_group-param=val@ sets @Configliere[:group][:subgroup][:param]@ to val (and similarly for boolean parameters).
|
160
194
|
** A dash or dot within a parameter name scopes that parameter: @--group.sub_group.param=val@ and @--group-sub_group-param=val@ do the same thing. A _ within a parameter name is left as part of the segment.
|
161
195
|
** Only @[\w\.\-]+@ are accepted in parameter names.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/configliere.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{configliere}
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["mrflip"]
|
12
|
-
s.date = %q{2010-01-
|
12
|
+
s.date = %q{2010-01-15}
|
13
13
|
s.default_executable = %q{configliere}
|
14
14
|
s.description = %q{ You've got a script. It's got some settings. Some settings are for this module, some are for that module. Most of them don't change. Except on your laptop, where the paths are different. Or when you're in production mode. Or when you're testing from the command line.
|
15
15
|
|
@@ -26,6 +26,7 @@ Configliere manage settings from many sources: static constants, simple config f
|
|
26
26
|
s.files = [
|
27
27
|
".document",
|
28
28
|
".gitignore",
|
29
|
+
"CHANGELOG.textile",
|
29
30
|
"LICENSE",
|
30
31
|
"README.textile",
|
31
32
|
"Rakefile",
|
@@ -34,18 +35,19 @@ Configliere manage settings from many sources: static constants, simple config f
|
|
34
35
|
"configliere.gemspec",
|
35
36
|
"examples/commandline_script.rb",
|
36
37
|
"examples/commandline_script.yaml",
|
38
|
+
"examples/config_block.rb",
|
39
|
+
"examples/encrypted_script.rb",
|
37
40
|
"examples/foo.yaml",
|
38
41
|
"examples/simple_script.rb",
|
39
42
|
"examples/simple_script.yaml",
|
40
43
|
"lib/configliere.rb",
|
41
44
|
"lib/configliere/commandline.rb",
|
42
|
-
"lib/configliere/commandline/commands.rb",
|
43
|
-
"lib/configliere/commandline/options.rb",
|
44
45
|
"lib/configliere/config_block.rb",
|
45
46
|
"lib/configliere/config_file.rb",
|
46
47
|
"lib/configliere/core_ext.rb",
|
47
48
|
"lib/configliere/core_ext/blank.rb",
|
48
49
|
"lib/configliere/core_ext/hash.rb",
|
50
|
+
"lib/configliere/core_ext/sash.rb",
|
49
51
|
"lib/configliere/crypter.rb",
|
50
52
|
"lib/configliere/define.rb",
|
51
53
|
"lib/configliere/encrypted.rb",
|
@@ -54,6 +56,8 @@ Configliere manage settings from many sources: static constants, simple config f
|
|
54
56
|
"spec/configliere/commandline_spec.rb",
|
55
57
|
"spec/configliere/config_block_spec.rb",
|
56
58
|
"spec/configliere/config_file_spec.rb",
|
59
|
+
"spec/configliere/core_ext/hash_spec.rb",
|
60
|
+
"spec/configliere/core_ext/sash_spec.rb",
|
57
61
|
"spec/configliere/crypter_spec.rb",
|
58
62
|
"spec/configliere/define_spec.rb",
|
59
63
|
"spec/configliere/encrypted_spec.rb",
|
@@ -72,6 +76,8 @@ Configliere manage settings from many sources: static constants, simple config f
|
|
72
76
|
"spec/configliere/commandline_spec.rb",
|
73
77
|
"spec/configliere/config_block_spec.rb",
|
74
78
|
"spec/configliere/config_file_spec.rb",
|
79
|
+
"spec/configliere/core_ext/hash_spec.rb",
|
80
|
+
"spec/configliere/core_ext/sash_spec.rb",
|
75
81
|
"spec/configliere/crypter_spec.rb",
|
76
82
|
"spec/configliere/define_spec.rb",
|
77
83
|
"spec/configliere/encrypted_spec.rb",
|
@@ -80,6 +86,8 @@ Configliere manage settings from many sources: static constants, simple config f
|
|
80
86
|
"spec/configliere_spec.rb",
|
81
87
|
"spec/spec_helper.rb",
|
82
88
|
"examples/commandline_script.rb",
|
89
|
+
"examples/config_block.rb",
|
90
|
+
"examples/encrypted_script.rb",
|
83
91
|
"examples/simple_script.rb"
|
84
92
|
]
|
85
93
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'configliere'
|
3
|
+
|
4
|
+
Settings :passenger => 'einstein', :dest_time => '1955-11-05', 'delorean.power_source' => :plutonium
|
5
|
+
Settings.finally do |c|
|
6
|
+
p [self, 'finally', c[:passenger], c.passenger]
|
7
|
+
# Einstein the dog should only be sent one minute into the future.
|
8
|
+
dest_time = (Time.now + 60) if c.passenger == 'einstein'
|
9
|
+
end
|
10
|
+
Settings.resolve!
|
11
|
+
p Settings
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'configliere'
|
3
|
+
|
4
|
+
DUMP_FILENAME = '/tmp/encrypted_script.yml'
|
5
|
+
Settings.use :config_file, :define, :encrypt_pass => 'password1'
|
6
|
+
Settings.define :password, :encrypted => true
|
7
|
+
|
8
|
+
Settings :password => 'plaintext'
|
9
|
+
Settings.resolve!
|
10
|
+
p ["saved version will have encrypted password (see #{DUMP_FILENAME}).", Settings.send(:export)]
|
11
|
+
p ['live version still has password in plaintext', Settings]
|
12
|
+
Settings.save!(DUMP_FILENAME)
|
13
|
+
|
14
|
+
Settings[:password] = 'before read'
|
15
|
+
Settings.read('/tmp/encrypted_script.yml')
|
16
|
+
Settings.resolve!
|
17
|
+
p ["value is decrypted on resolve.", Settings]
|
@@ -7,6 +7,14 @@ module Configliere
|
|
7
7
|
module Commandline
|
8
8
|
attr_accessor :rest
|
9
9
|
|
10
|
+
# Processing to reconcile all options
|
11
|
+
#
|
12
|
+
# Configliere::Commandline's resolve!:
|
13
|
+
# * processes all commandline params
|
14
|
+
# * if the --help param was given, prints out a usage statement (using
|
15
|
+
# any +:description+ set with #define) and then exits
|
16
|
+
# * lastly, calls the next method in the resolve! chain.
|
17
|
+
#
|
10
18
|
def resolve!
|
11
19
|
process_argv!
|
12
20
|
dump_help_if_requested
|
@@ -34,9 +42,10 @@ module Configliere
|
|
34
42
|
break
|
35
43
|
when arg =~ /\A--([\w\-\.]+)(?:=(.*))?\z/
|
36
44
|
param, val = [$1, $2]
|
37
|
-
param.gsub!(/\-/, '.')
|
38
|
-
|
39
|
-
|
45
|
+
param.gsub!(/\-/, '.') # translate --scoped-flag to --scoped.flag
|
46
|
+
param = param.to_sym unless (param =~ /\./) # symbolize non-scoped keys
|
47
|
+
if val == nil then val = true # --flag option on its own means 'set that option'
|
48
|
+
elsif val == '' then val = nil end # --flag='' the explicit empty string means nil
|
40
49
|
self[param] = val
|
41
50
|
else
|
42
51
|
self.rest << arg
|
@@ -59,10 +68,13 @@ module Configliere
|
|
59
68
|
end
|
60
69
|
|
61
70
|
# die with a warning
|
62
|
-
|
71
|
+
#
|
72
|
+
# @param str [String] the string to dump out before exiting
|
73
|
+
# @param exit_code [Integer] UNIX exit code to set, default -1
|
74
|
+
def die str, exit_code=-1
|
63
75
|
puts help
|
64
76
|
warn "\n****\n#{str}\n****"
|
65
|
-
exit
|
77
|
+
exit exit_code
|
66
78
|
end
|
67
79
|
|
68
80
|
# Retrieve the given param, or prompt for it
|
@@ -80,6 +92,11 @@ module Configliere
|
|
80
92
|
help_str.join("\n")
|
81
93
|
end
|
82
94
|
|
95
|
+
def dump_help extra_msg=nil
|
96
|
+
$stderr.puts help
|
97
|
+
$stderr.puts "\n\n"+extra_msg unless extra_msg.blank?
|
98
|
+
end
|
99
|
+
|
83
100
|
# Usage line
|
84
101
|
def usage
|
85
102
|
%Q{usage: #{File.basename($0)} [...--param=val...]}
|
@@ -90,7 +107,7 @@ module Configliere
|
|
90
107
|
# Ouput the help string if requested
|
91
108
|
def dump_help_if_requested
|
92
109
|
return unless self[:help]
|
93
|
-
|
110
|
+
dump_help
|
94
111
|
exit
|
95
112
|
end
|
96
113
|
end
|
@@ -1,25 +1,36 @@
|
|
1
1
|
Configliere.use :define
|
2
2
|
module Configliere
|
3
|
+
#
|
4
|
+
# ConfigBlock lets you use pure ruby to change and define settings. Call
|
5
|
+
# +#finally+ with a block of code to be run after all other settings are in
|
6
|
+
# place.
|
7
|
+
#
|
8
|
+
# Settings.finally{|c| c.your_mom[:college] = 'went' unless (! c.mom_jokes_allowed) }
|
9
|
+
#
|
3
10
|
module ConfigBlock
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
9
|
-
|
10
|
-
# @param param the setting to describe. Either a simple symbol or a dotted param string.
|
11
|
-
# @param definitions the defineables to set (:description, :type, :encrypted, etc.)
|
11
|
+
#
|
12
|
+
# @param &block each +finally+ block is called once, in the order it was
|
13
|
+
# defined, when the resolve! method is invoked. +config_block+ resolution
|
14
|
+
# is guaranteed to run last in the resolve chain, right before validation.
|
12
15
|
#
|
13
16
|
# @example
|
14
|
-
# Settings.
|
15
|
-
#
|
16
|
-
#
|
17
|
+
# Settings.finally do |c|
|
18
|
+
# c.dest_time = (Time.now + 60) if c.username == 'einstein'
|
19
|
+
# # you can use hash syntax too
|
20
|
+
# c[:dest_time] = (Time.now + 60) if c[:username] == 'einstein'
|
21
|
+
# end
|
22
|
+
# # ...
|
23
|
+
# # after rest of setup:
|
24
|
+
# Settings.resolve!
|
17
25
|
#
|
18
26
|
def finally &block
|
19
27
|
self.final_blocks << block
|
20
28
|
end
|
21
29
|
|
22
|
-
#
|
30
|
+
# Processing to reconcile all options
|
31
|
+
#
|
32
|
+
# The resolve! for config_block is made to run last of all in the +resolve!+
|
33
|
+
# chain, and runs each +finally+ block in the order it was defined.
|
23
34
|
def resolve!
|
24
35
|
begin ; super() ; rescue NoMethodError ; nil ; end
|
25
36
|
resolve_finally_blocks!
|
@@ -27,12 +38,17 @@ module Configliere
|
|
27
38
|
end
|
28
39
|
|
29
40
|
protected
|
41
|
+
# Config blocks to be executed at end of resolution (just before validation)
|
42
|
+
attr_accessor :final_blocks
|
43
|
+
def final_blocks
|
44
|
+
@final_blocks ||= []
|
45
|
+
end
|
46
|
+
# call each +finally+ config block in the order it was defined
|
30
47
|
def resolve_finally_blocks!
|
31
48
|
final_blocks.each do |block|
|
32
49
|
block.call(self)
|
33
50
|
end
|
34
51
|
end
|
35
|
-
|
36
52
|
end
|
37
53
|
|
38
54
|
Param.class_eval do
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'fileutils'
|
2
3
|
module Configliere
|
3
4
|
#
|
4
5
|
# ConfigFile -- load configuration from a simple YAML file
|
@@ -36,10 +37,11 @@ module Configliere
|
|
36
37
|
# form suitable for serialization to disk
|
37
38
|
# (e.g. the encryption done in configliere/encrypted)
|
38
39
|
def export
|
39
|
-
to_hash
|
40
|
+
super.to_hash
|
40
41
|
end
|
41
42
|
|
42
43
|
def self.write_yaml_file filename, hsh
|
44
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
43
45
|
File.open(filename, 'w'){|f| f << YAML.dump(hsh) }
|
44
46
|
end
|
45
47
|
|
@@ -33,7 +33,8 @@ class Hash
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def deep_merge! hsh2
|
36
|
-
|
36
|
+
update hsh2, &Hash::DEEP_MERGER
|
37
|
+
self
|
37
38
|
end
|
38
39
|
|
39
40
|
#
|
@@ -50,7 +51,8 @@ class Hash
|
|
50
51
|
val = args.pop
|
51
52
|
last_key = args.pop
|
52
53
|
# dig down to last subtree (building out if necessary)
|
53
|
-
hsh =
|
54
|
+
hsh = self
|
55
|
+
args.each{|key| hsh = (hsh[key] ||= self.class.new) }
|
54
56
|
# set leaf value
|
55
57
|
hsh[last_key] = val
|
56
58
|
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'configliere/core_ext/hash'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Hash with indifferent access
|
5
|
+
#
|
6
|
+
# Adapted from extlib/lib/mash.rb
|
7
|
+
#
|
8
|
+
class Sash < ::Hash
|
9
|
+
|
10
|
+
# @param constructor<Object>
|
11
|
+
# The default value for the mash. Defaults to an empty hash.
|
12
|
+
#
|
13
|
+
# @details [Alternatives]
|
14
|
+
# If constructor is a Hash, a new mash will be created based on the keys of
|
15
|
+
# the hash and no default value will be set.
|
16
|
+
def initialize(constructor = {})
|
17
|
+
if constructor.is_a?(Hash)
|
18
|
+
super()
|
19
|
+
update(constructor) unless constructor.empty?
|
20
|
+
else
|
21
|
+
super(constructor)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
26
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
27
|
+
|
28
|
+
# @param key<Object> The key to set.
|
29
|
+
# @param value<Object>
|
30
|
+
# The value to set the key to.
|
31
|
+
#
|
32
|
+
# @see Mash#convert_key
|
33
|
+
# @see Mash#convert_value
|
34
|
+
def []=(key, value)
|
35
|
+
regular_writer(convert_key(key), convert_value(value))
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :merge!, :update
|
39
|
+
|
40
|
+
# @param key<Object> The key to check for. This will be run through convert_key.
|
41
|
+
#
|
42
|
+
# @return [Boolean] True if the key exists in the mash.
|
43
|
+
def key?(key)
|
44
|
+
super(convert_key(key))
|
45
|
+
end
|
46
|
+
|
47
|
+
# def include? def has_key? def member?
|
48
|
+
alias_method :include?, :key?
|
49
|
+
alias_method :has_key?, :key?
|
50
|
+
alias_method :member?, :key?
|
51
|
+
|
52
|
+
# @param key<Object> The key to fetch. This will be run through convert_key.
|
53
|
+
# @param *extras<Array> Default value.
|
54
|
+
#
|
55
|
+
# @return [Object] The value at key or the default value.
|
56
|
+
def fetch(key, *extras)
|
57
|
+
super(convert_key(key), *extras)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param *indices<Array>
|
61
|
+
# The keys to retrieve values for. These will be run through +convert_key+.
|
62
|
+
#
|
63
|
+
# @return [Array] The values at each of the provided keys
|
64
|
+
def values_at(*indices)
|
65
|
+
indices.collect{|key| self[convert_key(key)]}
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param hash<Hash> The hash to merge with the mash.
|
69
|
+
#
|
70
|
+
# @return [Mash] A new mash with the hash values merged in.
|
71
|
+
def merge(hash, &block)
|
72
|
+
self.dup.update(hash, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param key<Object>
|
76
|
+
# The key to delete from the mash.\
|
77
|
+
def delete(key)
|
78
|
+
super(convert_key(key))
|
79
|
+
end
|
80
|
+
|
81
|
+
# @return [Hash] The mash as a Hash with string keys.
|
82
|
+
def to_hash
|
83
|
+
Hash.new(default).merge(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param key<Object> The default value for the mash. Defaults to nil.
|
87
|
+
#
|
88
|
+
# @details [Alternatives]
|
89
|
+
# If key is a Symbol and it is a key in the mash, then the default value will
|
90
|
+
# be set to the value matching the key.
|
91
|
+
def default(key = nil)
|
92
|
+
if key.is_a?(String) && include?(key = key.to_sym)
|
93
|
+
self[key]
|
94
|
+
else
|
95
|
+
super(key)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param other_hash<Hash>
|
100
|
+
# A hash to update values in the mash with. The keys and the values will be
|
101
|
+
# converted to Mash format.
|
102
|
+
#
|
103
|
+
# @return [Mash] The updated mash.
|
104
|
+
def update(other_hash, &block)
|
105
|
+
sash = self.class.new
|
106
|
+
other_hash.each_pair do |key, value|
|
107
|
+
val = convert_value(value)
|
108
|
+
sash[convert_key(key)] = val
|
109
|
+
end
|
110
|
+
regular_update(sash, &block)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Used to provide the same interface as Hash.
|
114
|
+
#
|
115
|
+
# @return [Sash] This sash unchanged.
|
116
|
+
def symbolize_keys!; self end
|
117
|
+
|
118
|
+
# @return [Hash] The sash as a Hash with stringified keys.
|
119
|
+
def stringify_keys
|
120
|
+
h = Hash.new(default)
|
121
|
+
each { |key, val| h[key.to_sym] = val }
|
122
|
+
h
|
123
|
+
end
|
124
|
+
|
125
|
+
protected
|
126
|
+
# @param key<Object> The key to convert.
|
127
|
+
#
|
128
|
+
# @param [Object]
|
129
|
+
# The converted key. If the key was a string, it will be converted to a
|
130
|
+
# symbol.
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
def convert_key(key)
|
134
|
+
key.is_a?(String) ? key.to_sym : key
|
135
|
+
end
|
136
|
+
|
137
|
+
# @param value<Object> The value to convert.
|
138
|
+
#
|
139
|
+
# @return [Object]
|
140
|
+
# The converted value. A Hash or an Array of hashes, will be converted to
|
141
|
+
# their Mash equivalents.
|
142
|
+
#
|
143
|
+
# @api private
|
144
|
+
def convert_value(value)
|
145
|
+
if value.class == Hash
|
146
|
+
value.to_sash
|
147
|
+
elsif value.is_a?(Array)
|
148
|
+
value.collect { |e| convert_value(e) }
|
149
|
+
else
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
class ::Hash
|
157
|
+
|
158
|
+
# Convert to Sash. This class has semantics of ActiveSupport's
|
159
|
+
# HashWithIndifferentAccess and we only have it so that people can write
|
160
|
+
# params[:key] instead of params['key'].
|
161
|
+
#
|
162
|
+
# @return [Mash] This hash as a Mash for string or symbol key access.
|
163
|
+
def to_sash
|
164
|
+
hash = Sash.new(self)
|
165
|
+
hash.default = default
|
166
|
+
hash
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|