configliere 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/.document CHANGED
@@ -1,6 +1,4 @@
1
- README.rdoc
1
+ README.textile
2
2
  lib/**/*.rb
3
3
  bin/*
4
- features/**/*.feature
5
4
  LICENSE
6
- examples/*.rb
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 wise, lightweight configuration management for ruby programs.
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 manage 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.
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
- Config[:dest_time] #=> '1955-11-05'
82
+ Settings[:dest_time] #=> '1955-11-05'
81
83
  # deep keys
82
- Config[:delorean][:power_source] #=> 'plutonium'
83
- Config[:delorean][:missing] #=> nil
84
- Config[:delorean][:missing][:fail] #=> raises an error
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
- Config['delorean.power_source'] #=> 'plutonium'
87
- Config['delorean.missing'] #=> nil
88
- Config['delorean.missing.fail'] #=> nil
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
- You can save defaults with
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', 'TM_PASS' => 'password', 'POWER_SOURCE' => 'delorean.power_source'
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 Config[:delorean][:power_source]). You can alternatively set up environment variables with @define 'delorean.power_source', :environment => 'POWER_SOURCE'@ - see below.
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. --param='' sets @Configliere[:param]@ to be nil.
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.2
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.2"
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-08}
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
- if val == nil then val = true # --flag option on its own means 'set that option'
39
- elsif val == '' then val = nil end # --flag='' the explicit empty string means nil
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
- def die str
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 -1
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
- $stderr.puts help
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
- # Config blocks to be executed at end of resolution (just before validation)
5
- attr_accessor :final_blocks
6
- def final_blocks
7
- @final_blocks ||= []
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.define :dest_time, :type => Date, :description => 'Arrival time. If only a date is given, the current time of day on that date is assumed.'
15
- # Settings.define 'delorean.power_source', :description => 'Delorean subsytem supplying power to the Flux Capacitor.'
16
- # Settings.define :password, :required => true, :obscure => true
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
- # calls superclass resolution
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
- merge! hsh2, &Hash::DEEP_MERGER
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 = args.empty? ? self : args.inject(self){|hsh, key| hsh[key] ||= {} }
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