o-inifile 4.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f60e4b8e5246f6327f832017a37ad425a603cdc0214fe882a7cab21b6345197e
4
+ data.tar.gz: ca427a71f5c017bd951a086a46e83f1ab909a3e2b8f52c2254485120b861a345
5
+ SHA512:
6
+ metadata.gz: fc1a611372e22d90d4dbcdac29d93d65bc2e7b6d4a3437eac0490e2a9d086eb1619906671db2fa2af05446cdb564659ed28522f2d81bf7b0a5f5a4ced87caf07
7
+ data.tar.gz: 7cbd4400167dbbfbb1eb4a118d9d29b40299cd03d78ad352f36c6b85f31e331e55891c48a97ac87afb3c9528270e6c9c5bbab8f66365a6c6cdde18fa2a8a81ee
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ # The list of files that should be ignored by Mr Bones.
2
+ # Lines that start with '#' are comments.
3
+ #
4
+ # A .gitignore file can be used instead by setting it as the ignore
5
+ # file in your Rakefile:
6
+ #
7
+ # PROJ.ignore_file = '.gitignore'
8
+ #
9
+ # For a project with a C extension, the following would be a good set of
10
+ # exclude patterns (uncomment them if you want to use them):
11
+ # *.[oa]
12
+ *~
13
+ *.sw[op]
14
+ announcement.txt
15
+ coverage
16
+ doc
17
+ pkg
18
+ vendor
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+
3
+ before_install: "gem install bones"
4
+ install: "rake gem:install_dependencies"
5
+
6
+ script: "rake"
7
+
8
+ rvm:
9
+ - 1.9.3
10
+ - 2.0.0
11
+ - 2.1.1
12
+
data/History.txt ADDED
@@ -0,0 +1,59 @@
1
+ == 2.0.2 / 2012-09-14
2
+
3
+ Bug Fixes
4
+ - Lack of newline at end of file causes error (thanks erebor) [issue #12]
5
+
6
+ == 2.0.1 / 2012-09-06
7
+
8
+ Bug Fixes
9
+ - Better error message for malformed INI files
10
+
11
+ == 2.0.0 / 2012-08-29
12
+
13
+ Major Enhancements
14
+ - Can now initialize from Strings [issue #9]
15
+ - Quoted multi-line values are supported
16
+ - Comments can appear at the end of a line
17
+ Enhancements
18
+ - TomDoc documentation
19
+ - Parity between read/write methods
20
+
21
+ == 1.1.0 / 2012-02-28
22
+
23
+ Enhancements
24
+ - Added support for a default "global" section
25
+
26
+ == 1.0.0 / 2012-02-27
27
+
28
+ Major Enhancements
29
+ - Changed how multi-line values are handled [issue #7]
30
+ - Added backslash escaping support for values
31
+ Enhancements
32
+ - Inifile merge functionality [Jens Hilligsøe]
33
+ - Use regular expressions to find sections [issue 8]
34
+
35
+ == 0.4.1 / 2011-02-22
36
+
37
+ Bug Fixes
38
+ - Empty quotes [Steffen Beyer]
39
+
40
+ == 0.4.0 / 2011-02-15
41
+
42
+ Enhancements
43
+ - Added multiline support [André Gawron]
44
+ - Added file encoding support [Gilles Devaux]
45
+
46
+ == 0.3.0 / 2009-12-10
47
+
48
+ * 1 minor enhancement
49
+ - Added a []= operator to the inifile for setting entire sections
50
+
51
+ == 0.2.0 / 2009-10-27
52
+
53
+ * 1 minor enhancement
54
+ - Using Mr Bones for rake tasks and gem management
55
+
56
+ == 0.1.0 / 2006-11-26
57
+
58
+ * 1 major enhancement
59
+ * Birthday!
data/README.md ADDED
@@ -0,0 +1,243 @@
1
+ inifile
2
+ =======
3
+
4
+ This is a native Ruby library for reading and writing INI files.
5
+
6
+ This project is a fork of the [original inifile library](https://github.com/TwP/inifile).
7
+ by Tim Pease (TwP).
8
+
9
+
10
+ Description
11
+ -----------
12
+
13
+ Although made popular by Windows, INI files can be used on any system thanks
14
+ to their flexibility. They allow a program to store configuration data, which
15
+ can then be easily parsed and changed. Two notable systems that use the INI
16
+ format are Samba and Trac.
17
+
18
+ More information about INI files can be found on the [Wikipedia Page](http://en.wikipedia.org/wiki/INI_file).
19
+
20
+ ### Properties
21
+
22
+ A basic element contained in an INI file is the property. Every property has
23
+ a name and a value, delimited by an equals sign *=*. The name appears to the
24
+ left of the equals sign and the value to the right.
25
+
26
+ name=value
27
+
28
+ ### Switches
29
+
30
+ Switches are sometimes present in INI files to indicate whether a feature
31
+ should be on or off. Switches are made up by a single word on a line on its
32
+ own.
33
+
34
+ name
35
+
36
+ Switches are represented internally as an empty hash `{}` to avoid clashing
37
+ with other values and are deactivated by removing the line from the file
38
+ completely.
39
+
40
+ ### Sections
41
+
42
+ Section declarations start with *[* and end with *]* as in `[section1]` and
43
+ `[section2]` shown in the example below. The section declaration marks the
44
+ beginning of a section. All properties after the section declaration will be
45
+ associated with that section.
46
+
47
+ ### Comments
48
+
49
+ All lines beginning with a semicolon *;* or a number sign *#* are considered
50
+ to be comments. Comment lines are ignored when parsing INI files.
51
+
52
+ ### Example File Format
53
+
54
+ A typical INI file might look like this:
55
+
56
+ [section1]
57
+ ; some comment on section1
58
+ var1 = foo
59
+ var2 = doodle
60
+ var3 = multiline values \
61
+ are also possible
62
+
63
+ [section2]
64
+ # another comment
65
+ var1 = baz
66
+ var2 = shoodle
67
+ a_switch
68
+
69
+
70
+ Implementation
71
+ --------------
72
+
73
+ The format of INI files is not well defined. Several assumptions are made by
74
+ the **inifile** gem when parsing INI files. Most of these assumptions can be
75
+ modified at, but the defaults are listed below.
76
+
77
+ ### Global Properties
78
+
79
+ If the INI file lacks any section declarations, or if there are properties
80
+ decalared before the first section, then these properties will be placed into
81
+ a default "global" section. The name of this section can be configured when
82
+ creating an `IniFile` instance.
83
+
84
+ ### Duplicate Properties
85
+
86
+ Duplicate properties are allowed in a single section. The last property value
87
+ set is the one that will be stored in the `IniFile` instance.
88
+
89
+ [section1]
90
+ var1 = foo
91
+ var2 = bar
92
+ var1 = poodle
93
+
94
+ The resulting value of `var1` will be `poodle`.
95
+
96
+ ### Duplicate Sections
97
+
98
+ If you have more than one section with the same name then the sections will be
99
+ merged. Duplicate properties between the two sections will follow the rules
100
+ discussed above. Properties in the latter section will override properties in
101
+ the earlier section.
102
+
103
+ ### Comments
104
+
105
+ The comment character can be either a semicolon *;* or a number sign *#*. The
106
+ comment character can appear anywhere on a line including at the end of a
107
+ name/value pair declaration. If you wish to use a comment character in your
108
+ value then you will need to either escape the character or put the value in
109
+ double quotations.
110
+
111
+ [section1]
112
+ var1 = foo # a comment
113
+ var2 = "foo # this is not a comment"
114
+ var3 = foo \# this is not a comment either
115
+
116
+ ### Multi-Line Values
117
+
118
+ Values can be continued onto multiple lines in two separate ways. Putting a
119
+ slash at the end of a line will continue the value declaration to the next
120
+ line. When parsing, the trailing slash will be consumed and **will not**
121
+ appear in the resulting value. Comments can appear to the right of the
122
+ trailing slash.
123
+
124
+ var1 = this is a \ # these comments will
125
+ multiline value # be ignored by the parser
126
+
127
+ In the above example the resulting value for `var1` will be `this is a
128
+ multiline value`. If you want to preserve newline characters in the value then
129
+ quotations should be used.
130
+
131
+ var2 = "this is a
132
+ multiline value"
133
+
134
+ The resulting value for `var2` will be `this is a\nmultiline value`.
135
+
136
+ ### Escape Characters
137
+
138
+ Several escape characters are supported within the **value** for a property.
139
+ These escape sequences will be applied to quoted and unquoted values alike.
140
+ You can enable or disable escaping by setting the **escape** flag to true or
141
+ false when creating an IniFile instance.
142
+
143
+ * \0 -- null character
144
+ * \n -- newline character
145
+ * \r -- carriage return character
146
+ * \t -- tab character
147
+ * \\\\ -- backslash character
148
+
149
+ The backslash escape sequence is only needed if you want one of the escape
150
+ sequences to appear literally in your value. For example:
151
+
152
+ property = this is not a tab \\t character
153
+
154
+ ### Value Type Casting
155
+
156
+ Some values will be type cast when parsed by the code. Those values are
157
+ booleans, integers, floats, and empty strings are cast to `nil`.
158
+
159
+ * "" --> nil
160
+ * "42" --> 42
161
+ * "3.14159" --> 3.14159
162
+ * "true" --> true
163
+ * "false" --> false
164
+ * "normal string" --> "normal string"
165
+
166
+ Pretty basic stuff.
167
+
168
+ Install
169
+ -------
170
+
171
+ gem install inifile
172
+
173
+
174
+ Testing
175
+ -------
176
+
177
+ To run the tests:
178
+
179
+ $ rake
180
+
181
+
182
+ Examples
183
+ --------
184
+
185
+ Load from a file:
186
+
187
+ require 'inifile'
188
+ myini = IniFile.load('mytest.ini')
189
+ myini.each_section do |section|
190
+ puts "I want #{myini[section]['somevar']} printed here!"
191
+ end
192
+
193
+ Read INI content from a string:
194
+
195
+ myini = IniFile.new(:content => "[global]\nfoo=bar")
196
+
197
+ Create and save a new INI file:
198
+
199
+ myini = IniFile.new(filename: 'mytest.ini')
200
+ myini['section'] = {key: 'value'}
201
+ myini.write
202
+
203
+ Contributing
204
+ ------------
205
+
206
+ Contributions are gladly welcome! For small modifications (fixing typos,
207
+ improving documentation) you can use GitHub's in-browser editing capabilities
208
+ to create a pull request. For larger modifications I would recommend forking
209
+ the project, creating your patch, and then submitting a pull request.
210
+
211
+ Mr Bones is used to manage rake tasks and to install dependent files. To setup
212
+ your environment ...
213
+
214
+ $ gem install bones
215
+ $ rake gem:install_dependencies
216
+
217
+ And always remember that `rake -T` will show you the list of available tasks.
218
+
219
+
220
+ License
221
+ -------
222
+
223
+ MIT License
224
+ Copyright (c) 2006 - 2021
225
+
226
+ Permission is hereby granted, free of charge, to any person obtaining
227
+ a copy of this software and associated documentation files (the
228
+ 'Software'), to deal in the Software without restriction, including
229
+ without limitation the rights to use, copy, modify, merge, publish,
230
+ distribute, sublicense, and/or sell copies of the Software, and to
231
+ permit persons to whom the Software is furnished to do so, subject to
232
+ the following conditions:
233
+
234
+ The above copyright notice and this permission notice shall be
235
+ included in all copies or substantial portions of the Software.
236
+
237
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
238
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
239
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
240
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
241
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
242
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
243
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+
2
+ begin
3
+ require 'bones'
4
+ rescue LoadError
5
+ abort '### please install the "bones" gem ###'
6
+ end
7
+
8
+ ensure_in_path 'lib'
9
+ require 'inifile'
10
+
11
+ task :default => 'test:run'
12
+ task 'gem:release' => 'test:run'
13
+
14
+ Bones {
15
+ name 'o-inifile'
16
+ summary 'INI file reader and writer'
17
+ authors 'Oleg Pudeyev'
18
+ email 'code@olegp.name'
19
+ url 'https://github.com/p/o-inifile'
20
+ version IniFile::VERSION
21
+
22
+ use_gmail
23
+ depend_on 'bones-git', "~> 1.3", :development => true
24
+ }
data/inifile.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'inifile'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "o-inifile"
8
+ spec.version = IniFile::VERSION
9
+ spec.authors = ['Oleg Pudeyev', "Tim Pease"]
10
+ spec.email = ['code@olegp.name', "tim.pease@gmail.com"]
11
+ spec.summary = %q{INI file reader and writer}
12
+ spec.description = %q{INI file reader and writer}
13
+ spec.description = %q{IniFile is a pure ruby gem that can read and write most INI file formats}
14
+ spec.homepage = "https://github.com/p/o-inifile"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bones", "~> 3.8"
23
+ spec.add_development_dependency "bones-git", "~> 1.3"
24
+ end
data/lib/inifile.rb ADDED
@@ -0,0 +1,682 @@
1
+ #encoding: UTF-8
2
+
3
+ # This class represents the INI file and can be used to parse, modify,
4
+ # and write INI files.
5
+ class IniFile
6
+ include Enumerable
7
+
8
+ VERSION = '4.0.0'
9
+
10
+ class Error < StandardError; end
11
+
12
+ # Public: Open an INI file and load the contents.
13
+ #
14
+ # filename - The name of the file as a String
15
+ # opts - The Hash of options (default: {})
16
+ # :comment - String containing the comment character(s)
17
+ # :parameter - String used to separate parameter and value
18
+ # :encoding - Encoding String for reading / writing
19
+ # :default - The String name of the default global section
20
+ # :continuation - Use backslash as a line contintuation
21
+ #
22
+ # Examples
23
+ #
24
+ # IniFile.load('file.ini')
25
+ # #=> IniFile instance
26
+ #
27
+ # IniFile.load('does/not/exist.ini')
28
+ # #=> nil
29
+ #
30
+ # Returns an IniFile instance or nil if the file could not be opened.
31
+ def self.load( filename, opts = {} )
32
+ return unless File.file? filename
33
+ new(opts.merge(:filename => filename))
34
+ end
35
+
36
+ # Get and set the filename
37
+ attr_accessor :filename
38
+
39
+ # Get and set the encoding
40
+ attr_accessor :encoding
41
+
42
+ # Public: Create a new INI file from the given set of options. If :content
43
+ # is provided then it will be used to populate the INI file. If a :filename
44
+ # is provided then the contents of the file will be parsed and stored in the
45
+ # INI file. If neither the :content or :filename is provided then an empty
46
+ # INI file is created.
47
+ #
48
+ # opts - The Hash of options (default: {})
49
+ # :content - The String/Hash containing the INI contents
50
+ # :comment - String containing the comment character(s)
51
+ # :parameter - String used to separate parameter and value
52
+ # :encoding - Encoding String for reading / writing
53
+ # :default - The String name of the default global section
54
+ # :filename - The filename as a String
55
+ # :permissions - Permission bits to assign the new file
56
+ # :continuation - Use backslash as a line continuation
57
+ # :separator - what to output between the key, operator, and value
58
+ # :force_array - Keep all values with same key in an array
59
+ #
60
+ # Examples
61
+ #
62
+ # IniFile.new
63
+ # #=> an empty IniFile instance
64
+ #
65
+ # IniFile.new( :content => "[global]\nfoo=bar" )
66
+ # #=> an IniFile instance
67
+ #
68
+ # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' )
69
+ # #=> an IniFile instance
70
+ #
71
+ # IniFile.new( :content => "[global]\nfoo=bar", :comment => '#' )
72
+ # #=> an IniFile instance
73
+ #
74
+ # IniFile.new( :permissions => 0644 )
75
+ # #=> an IniFile instance
76
+ #
77
+ def initialize( opts = {} )
78
+ @comment = opts.fetch(:comment, ';#')
79
+ @param = opts.fetch(:parameter, '=')
80
+ @encoding = opts.fetch(:encoding, nil)
81
+ @default = opts.fetch(:default, 'global')
82
+ @filename = opts.fetch(:filename, nil)
83
+ @permissions = opts.fetch(:permissions, nil)
84
+ @continuation = opts.fetch(:continuation, true)
85
+ @separator = opts.fetch(:separator, ' ')
86
+ @force_array = opts.fetch(:force_array, nil)
87
+ content = opts.fetch(:content, nil)
88
+
89
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
90
+
91
+ if content.is_a?(Hash) then merge!(content)
92
+ elsif content then parse(content)
93
+ elsif @filename then read
94
+ end
95
+ end
96
+
97
+ # Public: Write the contents of this IniFile to the file system. If left
98
+ # unspecified, the currently configured filename and encoding will be used.
99
+ # Otherwise the filename and encoding can be specified in the options hash.
100
+ #
101
+ # opts - The default options Hash
102
+ # :filename - The filename as a String
103
+ # :encoding - The encoding as a String
104
+ # :permissions - The permission bits as a Fixnum
105
+ #
106
+ # Returns this IniFile instance.
107
+ def write( opts = {} )
108
+ filename = opts.fetch(:filename, @filename)
109
+ encoding = opts.fetch(:encoding, @encoding)
110
+ permissions = opts.fetch(:permissions, @permissions)
111
+ mode = encoding ? "w:#{encoding}" : "w"
112
+
113
+ File.open(filename, mode, permissions) do |f|
114
+ @ini.each do |section,hash|
115
+ f.puts "[#{section}]"
116
+ hash.each {|param,val|
117
+ if val.class == Hash and val.empty?
118
+ f.puts param
119
+ else
120
+ if !val.is_a?(Array) || !@force_array
121
+ f.puts "#{param}#{@separator}#{@param}#{@separator}#{escape_value val}"
122
+ else
123
+ val.each do |subval|
124
+ f.puts "#{param}#{@separator}#{@param}#{@separator}#{escape_value subval}"
125
+ end
126
+ end
127
+ end
128
+ }
129
+ f.puts
130
+ end
131
+ end
132
+
133
+ self
134
+ end
135
+ alias :save :write
136
+
137
+ # Public: Read the contents of the INI file from the file system and replace
138
+ # and set the state of this IniFile instance. If left unspecified the
139
+ # currently configured filename and encoding will be used when reading from
140
+ # the file system. Otherwise the filename and encoding can be specified in
141
+ # the options hash.
142
+ #
143
+ # opts - The default options Hash
144
+ # :filename - The filename as a String
145
+ # :encoding - The encoding as a String
146
+ #
147
+ # Returns this IniFile instance if the read was successful; nil is returned
148
+ # if the file could not be read.
149
+ def read( opts = {} )
150
+ filename = opts.fetch(:filename, @filename)
151
+ encoding = opts.fetch(:encoding, @encoding)
152
+ return unless File.file? filename
153
+
154
+ mode = encoding ? "r:#{encoding}" : "r"
155
+ File.open(filename, mode) { |fd| parse fd }
156
+ self
157
+ end
158
+ alias :restore :read
159
+
160
+ # Returns this IniFile converted to a String.
161
+ def to_s
162
+ s = []
163
+ hash = @ini.dup
164
+ default = hash.delete(@default)
165
+ default.each {|param,val| s << "#{param} #{@param} #{escape_value val}"}
166
+ hash.each do |section,hash|
167
+ s << "[#{section}]"
168
+ hash.each {|param,val| s << "#{param}#{@separator}#{@param}#{@separator}#{escape_value val}"}
169
+ s << ""
170
+ end
171
+ s.join("\n")
172
+ end
173
+
174
+ # Returns this IniFile converted to a Hash.
175
+ def to_h
176
+ @ini.dup
177
+ end
178
+
179
+ # Public: Creates a copy of this inifile with the entries from the
180
+ # other_inifile merged into the copy.
181
+ #
182
+ # other - The other IniFile.
183
+ #
184
+ # Returns a new IniFile.
185
+ def merge( other )
186
+ self.dup.merge!(other)
187
+ end
188
+
189
+ # Public: Merges other_inifile into this inifile, overwriting existing
190
+ # entries. Useful for having a system inifile with user overridable settings
191
+ # elsewhere.
192
+ #
193
+ # other - The other IniFile.
194
+ #
195
+ # Returns this IniFile.
196
+ def merge!( other )
197
+ return self if other.nil?
198
+
199
+ my_keys = @ini.keys
200
+ other_keys = case other
201
+ when IniFile
202
+ other.instance_variable_get(:@ini).keys
203
+ when Hash
204
+ other.keys
205
+ else
206
+ raise Error, "cannot merge contents from '#{other.class.name}'"
207
+ end
208
+
209
+ (my_keys & other_keys).each do |key|
210
+ case other[key]
211
+ when Hash
212
+ @ini[key].merge!(other[key])
213
+ when nil
214
+ nil
215
+ else
216
+ raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
217
+ end
218
+ end
219
+
220
+ (other_keys - my_keys).each do |key|
221
+ case other[key]
222
+ when Hash
223
+ @ini[key] = other[key].dup
224
+ when nil
225
+ @ini[key] = {}
226
+ when String
227
+ @ini[@default].merge!({key => other[key]})
228
+ else
229
+ raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
230
+ end
231
+ end
232
+
233
+ self
234
+ end
235
+
236
+ # Public: Yield each INI file section, parameter, and value in turn to the
237
+ # given block.
238
+ #
239
+ # block - The block that will be iterated by the each method. The block will
240
+ # be passed the current section and the parameter/value pair.
241
+ #
242
+ # Examples
243
+ #
244
+ # inifile.each do |section, parameter, value|
245
+ # puts "#{parameter} = #{value} [in section - #{section}]"
246
+ # end
247
+ #
248
+ # Returns this IniFile.
249
+ def each
250
+ return unless block_given?
251
+ @ini.each do |section,hash|
252
+ hash.each do |param,val|
253
+ yield section, param, val
254
+ end
255
+ end
256
+ self
257
+ end
258
+
259
+ # Public: Yield each section in turn to the given block.
260
+ #
261
+ # block - The block that will be iterated by the each method. The block will
262
+ # be passed the current section as a Hash.
263
+ #
264
+ # Examples
265
+ #
266
+ # inifile.each_section do |section|
267
+ # puts section.inspect
268
+ # end
269
+ #
270
+ # Returns this IniFile.
271
+ def each_section
272
+ return unless block_given?
273
+ @ini.each_key {|section| yield section}
274
+ self
275
+ end
276
+
277
+ # Public: Remove a section identified by name from the IniFile.
278
+ #
279
+ # section - The section name as a String.
280
+ #
281
+ # Returns the deleted section Hash.
282
+ def delete_section( section )
283
+ @ini.delete section.to_s
284
+ end
285
+
286
+ # Public: Get the section Hash by name. If the section does not exist, then
287
+ # it will be created.
288
+ #
289
+ # section - The section name as a String.
290
+ #
291
+ # Examples
292
+ #
293
+ # inifile['global']
294
+ # #=> global section Hash
295
+ #
296
+ # Returns the Hash of parameter/value pairs for this section.
297
+ def []( section )
298
+ return nil if section.nil?
299
+ @ini[section.to_s]
300
+ end
301
+
302
+ # Public: Set the section to a hash of parameter/value pairs.
303
+ #
304
+ # section - The section name as a String.
305
+ # value - The Hash of parameter/value pairs.
306
+ #
307
+ # Examples
308
+ #
309
+ # inifile['tenderloin'] = { 'gritty' => 'yes' }
310
+ # #=> { 'gritty' => 'yes' }
311
+ #
312
+ # Returns the value Hash.
313
+ def []=( section, value )
314
+ @ini[section.to_s] = value
315
+ end
316
+
317
+ # Public: Create a Hash containing only those INI file sections whose names
318
+ # match the given regular expression.
319
+ #
320
+ # regex - The Regexp used to match section names.
321
+ #
322
+ # Examples
323
+ #
324
+ # inifile.match(/^tree_/)
325
+ # #=> Hash of matching sections
326
+ #
327
+ # Return a Hash containing only those sections that match the given regular
328
+ # expression.
329
+ def match( regex )
330
+ @ini.dup.delete_if { |section, _| section !~ regex }
331
+ end
332
+
333
+ # Public: Check to see if the IniFile contains the section.
334
+ #
335
+ # section - The section name as a String.
336
+ #
337
+ # Returns true if the section exists in the IniFile.
338
+ def has_section?( section )
339
+ @ini.has_key? section.to_s
340
+ end
341
+
342
+ # Returns an Array of section names contained in this IniFile.
343
+ def sections
344
+ @ini.keys
345
+ end
346
+
347
+ # Public: Freeze the state of this IniFile object. Any attempts to change
348
+ # the object will raise an error.
349
+ #
350
+ # Returns this IniFile.
351
+ def freeze
352
+ super
353
+ @ini.each_value {|h| h.freeze}
354
+ @ini.freeze
355
+ self
356
+ end
357
+
358
+ # Public: Mark this IniFile as tainted -- this will traverse each section
359
+ # marking each as tainted.
360
+ #
361
+ # Returns this IniFile.
362
+ def taint
363
+ super
364
+ @ini.each_value {|h| h.taint}
365
+ @ini.taint
366
+ self
367
+ end
368
+
369
+ # Public: Produces a duplicate of this IniFile. The duplicate is independent
370
+ # of the original -- i.e. the duplicate can be modified without changing the
371
+ # original. The tainted state of the original is copied to the duplicate.
372
+ #
373
+ # Returns a new IniFile.
374
+ def dup
375
+ other = super
376
+ other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
377
+ @ini.each_pair {|s,h| other[s].merge! h}
378
+ other.taint if self.tainted?
379
+ other
380
+ end
381
+
382
+ # Public: Produces a duplicate of this IniFile. The duplicate is independent
383
+ # of the original -- i.e. the duplicate can be modified without changing the
384
+ # original. The tainted state and the frozen state of the original is copied
385
+ # to the duplicate.
386
+ #
387
+ # Returns a new IniFile.
388
+ def clone
389
+ other = dup
390
+ other.freeze if self.frozen?
391
+ other
392
+ end
393
+
394
+ # Public: Compare this IniFile to some other IniFile. For two INI files to
395
+ # be equivalent, they must have the same sections with the same parameter /
396
+ # value pairs in each section.
397
+ #
398
+ # other - The other IniFile.
399
+ #
400
+ # Returns true if the INI files are equivalent and false if they differ.
401
+ def eql?( other )
402
+ return true if equal? other
403
+ return false unless other.instance_of? self.class
404
+ @ini == other.instance_variable_get(:@ini)
405
+ end
406
+ alias :== :eql?
407
+
408
+ # Escape special characters.
409
+ #
410
+ # value - The String value to escape.
411
+ #
412
+ # Returns the escaped value.
413
+ def escape_value( value )
414
+ value = value.to_s.dup
415
+ value.gsub!(%r/\\([0nrt])/, '\\\\\1')
416
+ value.gsub!(%r/\n/, '\n')
417
+ value.gsub!(%r/\r/, '\r')
418
+ value.gsub!(%r/\t/, '\t')
419
+ value.gsub!(%r/\0/, '\0')
420
+ value
421
+ end
422
+
423
+ # Parse the given content and store the information in this IniFile
424
+ # instance. All data will be cleared out and replaced with the information
425
+ # read from the content.
426
+ #
427
+ # content - A String or a file descriptor (must respond to `each_line`)
428
+ #
429
+ # Returns this IniFile.
430
+ def parse( content )
431
+ parser = Parser.new(@ini, @param, @comment, @default, @continuation, @force_array)
432
+ parser.parse(content)
433
+ self
434
+ end
435
+
436
+ # The IniFile::Parser has the responsibility of reading the contents of an
437
+ # .ini file and storing that information into a ruby Hash. The object being
438
+ # parsed must respond to `each_line` - this includes Strings and any IO
439
+ # object.
440
+ class Parser
441
+
442
+ attr_writer :section
443
+ attr_accessor :property
444
+ attr_accessor :value
445
+
446
+ # Create a new IniFile::Parser that can be used to parse the contents of
447
+ # an .ini file.
448
+ #
449
+ # hash - The Hash where parsed information will be stored
450
+ # param - String used to separate parameter and value
451
+ # comment - String containing the comment character(s)
452
+ # default - The String name of the default global section
453
+ # continuation - Use backslash as a line continuation character
454
+ #
455
+ def initialize( hash, param, comment, default, continuation, force_array )
456
+ @hash = hash
457
+ @default = default
458
+ @continuation = continuation
459
+ @force_array = force_array
460
+
461
+ comment = comment.to_s.empty? ? "\\z" : "\\s*(?:[#{comment}].*)?\\z"
462
+
463
+ @section_regexp = %r/\A\s*\[([^\]]+)\]#{comment}/
464
+ @ignore_regexp = %r/\A#{comment}/
465
+ @property_regexp = %r/\A(.*?)(?<!\\)#{param}(.*)\z/
466
+ @switch_regexp = %r/\A\s*([\w\.]+)\s*#{comment}/
467
+
468
+ @open_quote = %r/\A\s*(".*)\z/
469
+ @close_quote = %r/\A(.*(?<!\\)")#{comment}/
470
+ @full_quote = %r/\A\s*(".*(?<!\\)")#{comment}/
471
+ if @continuation
472
+ @trailing_slash = %r/\A(.*)(?<!\\)\\#{comment}/
473
+ else
474
+ @trailing_slash = %r/\A(.*\\)#{comment}/
475
+ end
476
+ @normal_value = %r/\A(.*?)#{comment}/
477
+ end
478
+
479
+ # Returns `true` if the current value starts with a leading double quote.
480
+ # Otherwise returns false.
481
+ def leading_quote?
482
+ value && value =~ %r/\A"/
483
+ end
484
+
485
+ # Given a string, attempt to parse out a value from that string. This
486
+ # value might be continued on the following line. So this method returns
487
+ # `true` if it is expecting more data.
488
+ #
489
+ # string - String to parse
490
+ #
491
+ # Returns `true` if the next line is also part of the current value.
492
+ # Returns `fase` if the string contained a complete value.
493
+ def parse_value( string )
494
+ continuation = false
495
+
496
+ # if our value starts with a double quote, then we are in a
497
+ # line continuation situation
498
+ if leading_quote?
499
+ # check for a closing quote at the end of the string
500
+ if string =~ @close_quote
501
+ value << $1
502
+
503
+ # otherwise just append the string to the value
504
+ else
505
+ value << string
506
+ continuation = true
507
+ end
508
+
509
+ # not currently processing a continuation line
510
+ else
511
+ case string
512
+ when @full_quote
513
+ self.value = $1
514
+
515
+ when @open_quote
516
+ self.value = $1
517
+ continuation = true
518
+
519
+ when @trailing_slash
520
+ self.value ? self.value << $1 : self.value = $1
521
+ continuation = @continuation
522
+
523
+ when @normal_value
524
+ self.value ? self.value << $1 : self.value = $1
525
+
526
+ else
527
+ error
528
+ end
529
+ end
530
+
531
+ if continuation
532
+ self.value << $/ if leading_quote?
533
+ else
534
+ process_property
535
+ end
536
+
537
+ continuation
538
+ end
539
+
540
+ # Parse the ini file contents. This will clear any values currently stored
541
+ # in the ini hash.
542
+ #
543
+ # content - Any object that responds to `each_line`
544
+ #
545
+ # Returns nil.
546
+ def parse( content )
547
+ return unless content
548
+
549
+ continuation = false
550
+
551
+ @hash.clear
552
+ @line = nil
553
+ self.section = nil
554
+
555
+ content.each_line do |line|
556
+ @line = line.chomp
557
+
558
+ if continuation
559
+ continuation = parse_value @line
560
+ else
561
+ case @line
562
+ when @ignore_regexp
563
+ nil
564
+ when @section_regexp
565
+ self.section = @hash[$1]
566
+ when @property_regexp
567
+ self.property = $1.strip
568
+ error if property.empty?
569
+
570
+ continuation = parse_value $2
571
+ when @switch_regexp
572
+ #self.property = $1.strip
573
+ self.section[$1.strip] = {}
574
+ else
575
+ error
576
+ end
577
+ end
578
+ end
579
+ # check here if we have a dangling value ... usually means we have an
580
+ # unmatched open quote
581
+ if leading_quote?
582
+ error "Unmatched open quote"
583
+ elsif property && value
584
+ process_property
585
+ elsif value
586
+ error
587
+ end
588
+
589
+ nil
590
+ end
591
+
592
+ # Store the property/value pair in the currently active section. This
593
+ # method checks for continuation of the value to the next line.
594
+ #
595
+ # Returns nil.
596
+ def process_property
597
+ property.strip!
598
+ value.strip!
599
+
600
+ self.value = $1 if value =~ %r/\A"(.*)(?<!\\)"\z/m
601
+
602
+ if section[property].nil? || !@force_array
603
+ section[property] = typecast(value)
604
+ elsif section[property].is_a?(Array)
605
+ section[property] << typecast(value)
606
+ else
607
+ section[property] = [section[property], typecast(value)]
608
+ end
609
+
610
+ self.property = nil
611
+ self.value = nil
612
+ end
613
+
614
+ # Returns the current section Hash.
615
+ def section
616
+ @section ||= @hash[@default]
617
+ end
618
+
619
+ # Raise a parse error using the given message and appending the current line
620
+ # being parsed.
621
+ #
622
+ # msg - The message String to use.
623
+ #
624
+ # Raises IniFile::Error
625
+ def error( msg = 'Could not parse line' )
626
+ raise Error, "#{msg}: #{@line.inspect}"
627
+ end
628
+
629
+ # Attempt to typecast the value string. We are looking for boolean values,
630
+ # integers, floats, and empty strings. Below is how each gets cast, but it
631
+ # is pretty logical and straightforward.
632
+ #
633
+ # "true" --> true
634
+ # "false" --> false
635
+ # "" --> nil
636
+ # "42" --> 42
637
+ # "3.14" --> 3.14
638
+ # "foo" --> "foo"
639
+ #
640
+ # Returns the typecast value.
641
+ def typecast( value )
642
+ case value
643
+ when %r/\Atrue\z/i; true
644
+ when %r/\Afalse\z/i; false
645
+ when %r/\A\s*\z/i; nil
646
+ else
647
+ stripped_value = value.strip
648
+ if stripped_value =~ /^\d*\.\d+$/
649
+ Float(stripped_value)
650
+ elsif stripped_value =~ /^[^0]\d*$/
651
+ Integer(stripped_value)
652
+ else
653
+ unescape_value(value)
654
+ end
655
+ end
656
+ rescue
657
+ unescape_value(value)
658
+ end
659
+
660
+ # Unescape special characters found in the value string. This will convert
661
+ # escaped null, tab, carriage return, newline, and backslash into their
662
+ # literal equivalents.
663
+ #
664
+ # value - The String value to unescape.
665
+ #
666
+ # Returns the unescaped value.
667
+ def unescape_value( value )
668
+ value = value.to_s
669
+ value.gsub!(%r/\\[0nrt\\]/) { |char|
670
+ case char
671
+ when '\0'; "\0"
672
+ when '\n'; "\n"
673
+ when '\r'; "\r"
674
+ when '\t'; "\t"
675
+ when '\\\\'; "\\"
676
+ end
677
+ }
678
+ value
679
+ end
680
+ end
681
+
682
+ end # IniFile