inifile_alt 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
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.8.7
10
+ - 1.9.3
11
+ - jruby
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,193 @@
1
+ inifile [![Build Status](https://secure.travis-ci.org/TwP/inifile.png)](http://travis-ci.org/TwP/inifile)
2
+ =======
3
+
4
+ This is a native Ruby package for reading and writing INI files.
5
+
6
+
7
+ Description
8
+ -----------
9
+
10
+ Although made popular by Windows, INI files can be used on any system thanks
11
+ to their flexibility. They allow a program to store configuration data, which
12
+ can then be easily parsed and changed. Two notable systems that use the INI
13
+ format are Samba and Trac.
14
+
15
+ More information about INI files can be found on the [Wikipedia Page](http://en.wikipedia.org/wiki/INI_file).
16
+
17
+ ### Properties
18
+
19
+ The basic element contained in an INI file is the property. Every property has
20
+ a name and a value, delimited by an equals sign *=*. The name appears to the
21
+ left of the equals sign and the value to the right.
22
+
23
+ name=value
24
+
25
+ ### Sections
26
+
27
+ Section declarations start with *[* and end with *]* as in `[section1]` and
28
+ `[section2]` shown in the example below. The section declaration marks the
29
+ beginning of a section. All properties after the section declaration will be
30
+ associated with that section.
31
+
32
+ ### Comments
33
+
34
+ All lines beginning with a semicolon *;* or a number sign *#* are considered
35
+ to be comments. Comment lines are ignored when parsing INI files.
36
+
37
+ ### Example File Format
38
+
39
+ A typical INI file might look like this:
40
+
41
+ [section1]
42
+ ; some comment on section1
43
+ var1 = foo
44
+ var2 = doodle
45
+ var3 = multiline values \
46
+ are also possible
47
+
48
+ [section2]
49
+ # another comment
50
+ var1 = baz
51
+ var2 = shoodle
52
+
53
+
54
+ Implementation
55
+ --------------
56
+
57
+ The format of INI files is not well defined. Several assumptions are made by
58
+ the **inifile** gem when parsing INI files. Most of these assumptions can be
59
+ modified at, but the defaults are listed below.
60
+
61
+ ### Global Properties
62
+
63
+ If the INI file lacks any section declarations, or if there are properties
64
+ decalared before the first section, then these properties will be placed into
65
+ a default "global" section. The name of this section can be configured when
66
+ creating an `IniFile` instance.
67
+
68
+ ### Duplicate Properties
69
+
70
+ Duplicate properties are allowed in a single section. The last property value
71
+ set is the one that will be stored in the `IniFile` instance.
72
+
73
+ [section1]
74
+ var1 = foo
75
+ var2 = bar
76
+ var1 = poodle
77
+
78
+ The resulting value of `var1` will be `poodle`.
79
+
80
+ ### Duplicate Sections
81
+
82
+ If you have more than one section with the same name then the sections will be
83
+ merged. Duplicate properties between the two sections will follow the rules
84
+ discussed above. Properties in the latter section will override properties in
85
+ the earlier section.
86
+
87
+ ### Comments
88
+
89
+ The comment character can be either a semicolon *;* or a number sign *#*. The
90
+ comment character can appear anywhere on a line including at the end of a
91
+ name/value pair declaration. If you wish to use a comment character in your
92
+ value then you will need to either escape the character or put the value in
93
+ double quotations.
94
+
95
+ [section1]
96
+ var1 = foo # a comment
97
+ var2 = "foo # this is not a comment"
98
+ var3 = foo \# this is not a comment either
99
+
100
+ ### Multi-Line Values
101
+
102
+ Values can be continued onto multiple lines in two separate ways. Putting a
103
+ slash at the end of a line will continue the value declaration to the next
104
+ line. When parsing, the trailing slash will be consumed and **will not**
105
+ appear in the resulting value. Comments can appear to the right of the
106
+ trailing slash.
107
+
108
+ var1 = this is a \ # these comments will
109
+ multiline value # be ignored by the parser
110
+
111
+ In the above example the resulting value for `var1` will be `this is a
112
+ multiline value`. If you want to preserve newline characters in the value then
113
+ quotations should be used.
114
+
115
+ var2 = "this is a
116
+ multiline value"
117
+
118
+ The resulting value for `var2` will be `this is a\nmultiline value`.
119
+
120
+ ### Escape Characters
121
+
122
+ Several escape characters are supported within the **value** for a property.
123
+ These escape sequences will be applied to quoted and unquoted values alike.
124
+ You can enable or disable escaping by setting the **escape** flag to true or
125
+ false when creating an IniFile instance.
126
+
127
+ * \0 -- null character
128
+ * \n -- newline character
129
+ * \r -- carriage return character
130
+ * \t -- tab character
131
+ * \\\\ -- backslash character
132
+
133
+ The backslash escape sequence is only needed if you want one of the escape
134
+ sequences to appear literally in your value. For example:
135
+
136
+ property = this is not a tab \\t character
137
+
138
+
139
+ Install
140
+ -------
141
+
142
+ gem install inifile
143
+
144
+
145
+ Testing
146
+ -------
147
+
148
+ To run the tests:
149
+
150
+ $ rake
151
+
152
+
153
+ Contributing
154
+ ------------
155
+
156
+ Contributions are gladly welcome! For small modifications (fixing typos,
157
+ improving documentation) you can use GitHub's in-browser editing capabilities
158
+ to create a pull request. For larger modifications I would recommend forking
159
+ the project, creating your patch, and then submitting a pull request.
160
+
161
+ Mr Bones is used to manage rake tasks and to install dependent files. To setup
162
+ your environment ...
163
+
164
+ $ gem install bones
165
+ $ rake gem:install_dependencies
166
+
167
+ And always remember that `rake -T` will show you the list of available tasks.
168
+
169
+
170
+ License
171
+ -------
172
+
173
+ MIT License
174
+ Copyright (c) 2006 - 2012
175
+
176
+ Permission is hereby granted, free of charge, to any person obtaining
177
+ a copy of this software and associated documentation files (the
178
+ 'Software'), to deal in the Software without restriction, including
179
+ without limitation the rights to use, copy, modify, merge, publish,
180
+ distribute, sublicense, and/or sell copies of the Software, and to
181
+ permit persons to whom the Software is furnished to do so, subject to
182
+ the following conditions:
183
+
184
+ The above copyright notice and this permission notice shall be
185
+ included in all copies or substantial portions of the Software.
186
+
187
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
188
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
189
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
190
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
191
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
192
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
193
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rake/dsl_definition'
3
+
4
+ begin
5
+ require 'bones'
6
+ rescue LoadError
7
+ abort '### please install the "bones" gem ###'
8
+ end
9
+
10
+ ensure_in_path 'lib'
11
+ require 'inifile'
12
+
13
+ task :default => 'test:run'
14
+ task 'gem:release' => 'test:run'
15
+
16
+ Bones {
17
+ name 'inifile_alt'
18
+ summary 'INI file reader and writer'
19
+ authors 'Tim Pease'
20
+ email 'tim.pease@gmail.com'
21
+ url 'http://rubygems.org/gems/inifile'
22
+ version IniFile::VERSION
23
+
24
+ use_gmail
25
+ depend_on 'bones-git', :development => true
26
+ }
data/inifile.iml ADDED
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="RUBY_MODULE" version="4">
3
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
4
+ <exclude-output />
5
+ <content url="file://$MODULE_DIR$" />
6
+ <orderEntry type="jdk" jdkName="ruby-1.8.7-p358" jdkType="RUBY_SDK" />
7
+ <orderEntry type="sourceFolder" forTests="false" />
8
+ <orderEntry type="library" scope="PROVIDED" name="bones (v3.8.0, ruby-1.8.7-p358) [gem]" level="application" />
9
+ <orderEntry type="library" scope="PROVIDED" name="gemcutter (v0.7.1, ruby-1.8.7-p358) [gem]" level="application" />
10
+ <orderEntry type="library" scope="PROVIDED" name="inifile (v2.0.2, ruby-1.8.7-p358) [gem]" level="application" />
11
+ <orderEntry type="library" scope="PROVIDED" name="little-plugger (v1.1.3, ruby-1.8.7-p358) [gem]" level="application" />
12
+ <orderEntry type="library" scope="PROVIDED" name="loquacious (v1.9.1, ruby-1.8.7-p358) [gem]" level="application" />
13
+ <orderEntry type="library" scope="PROVIDED" name="rake (v10.0.3, ruby-1.8.7-p358) [gem]" level="application" />
14
+ </component>
15
+ </module>
16
+
data/lib/inifile.rb ADDED
@@ -0,0 +1,570 @@
1
+ #encoding: UTF-8
2
+ require 'strscan'
3
+
4
+ # This class represents the INI file and can be used to parse, modify,
5
+ # and write INI files.
6
+ #
7
+ class IniFile
8
+ include Enumerable
9
+
10
+ class Error < StandardError; end
11
+ VERSION = '2.0.2'
12
+
13
+ # Public: Open an INI file and load the contents.
14
+ #
15
+ # filename - The name of the fiel as a String
16
+ # opts - The Hash of options (default: {})
17
+ # :comment - String containing the comment character(s)
18
+ # :parameter - String used to separate parameter and value
19
+ # :encoding - Encoding String for reading / writing (Ruby 1.9)
20
+ # :escape - Boolean used to control character escaping
21
+ # :default - The String name of the default global section
22
+ #
23
+ # Examples
24
+ #
25
+ # IniFile.load('file.ini')
26
+ # #=> IniFile instance
27
+ #
28
+ # IniFile.load('does/not/exist.ini')
29
+ # #=> nil
30
+ #
31
+ # Returns an IniFile intsnace or nil if the file could not be opened.
32
+ #
33
+ def self.load( filename, opts = {} )
34
+ return unless File.file? filename
35
+ new(opts.merge(:filename => filename))
36
+ end
37
+
38
+ # Get and set the filename
39
+ attr_accessor :filename
40
+
41
+ # Get and set the encoding (Ruby 1.9)
42
+ attr_accessor :encoding
43
+
44
+ # Enable or disable character escaping
45
+ attr_accessor :escape
46
+
47
+ # Public: Create a new INI file from the given content String which
48
+ # contains the INI file lines. If the content are omitted, then the
49
+ # :filename option is used to read in the content of the INI file. If
50
+ # neither the content for a filename is provided then an empty INI file is
51
+ # created.
52
+ #
53
+ # content - The String containing the INI file contents
54
+ # opts - The Hash of options (default: {})
55
+ # :comment - String containing the comment character(s)
56
+ # :parameter - String used to separate parameter and value
57
+ # :encoding - Encoding String for reading / writing (Ruby 1.9)
58
+ # :escape - Boolean used to control character escaping
59
+ # :default - The String name of the default global section
60
+ # :filename - The filename as a String
61
+ #
62
+ # Examples
63
+ #
64
+ # IniFile.new
65
+ # #=> an empty IniFile instance
66
+ #
67
+ # IniFile.new( "[global]\nfoo=bar" )
68
+ # #=> an IniFile instance
69
+ #
70
+ # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' )
71
+ # #=> an IniFile instance
72
+ #
73
+ # IniFile.new( "[global]\nfoo=bar", :comment => '#' )
74
+ # #=> an IniFile instance
75
+ #
76
+ def initialize( content = nil, opts = {} )
77
+ opts, content = content, nil if Hash === content
78
+
79
+ @content = content
80
+
81
+ @comment = opts.fetch(:comment, ';#')
82
+ @param = opts.fetch(:parameter, '=')
83
+ @encoding = opts.fetch(:encoding, nil)
84
+ @escape = opts.fetch(:escape, true)
85
+ @default = opts.fetch(:default, 'global')
86
+ @filename = opts.fetch(:filename, nil)
87
+
88
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
89
+
90
+ if @content then parse!
91
+ elsif @filename then read
92
+ end
93
+ end
94
+
95
+ # Public: Write the contents of this IniFile to the file system. If left
96
+ # unspecified, the currently configured filename and encoding will be used.
97
+ # Otherwise the filename and encoding can be specified in the options hash.
98
+ #
99
+ # opts - The default options Hash
100
+ # :filename - The filename as a String
101
+ # :encoding - The encoding as a String (Ruby 1.9)
102
+ #
103
+ # Returns this IniFile instance.
104
+ #
105
+ def write( opts = {} )
106
+ filename = opts.fetch(:filename, @filename)
107
+ encoding = opts.fetch(:encoding, @encoding)
108
+ mode = (RUBY_VERSION >= '1.9' && encoding) ?
109
+ "w:#{encoding.to_s}" :
110
+ 'w'
111
+
112
+ File.open(filename, mode) do |f|
113
+ @ini.each do |section,hash|
114
+ f.puts "[#{section}]"
115
+ hash.each {|param,val| f.puts "#{param} #{@param} #{escape_value val}"}
116
+ f.puts
117
+ end
118
+ end
119
+
120
+ self
121
+ end
122
+ alias :save :write
123
+
124
+ # Public: Read the contents of the INI file from the file system and replace
125
+ # and set the state of this IniFile instance. If left unspecified the
126
+ # currently configured filename and encoding will be used when reading from
127
+ # the file system. Otherwise the filename and encoding can be specified in
128
+ # the options hash.
129
+ #
130
+ # opts - The default options Hash
131
+ # :filename - The filename as a String
132
+ # :encoding - The encoding as a String (Ruby 1.9)
133
+ #
134
+ # Returns this IniFile instance if the read was successful; nil is returned
135
+ # if the file could not be read.
136
+ #
137
+ def read( opts = {} )
138
+ filename = opts.fetch(:filename, @filename)
139
+ encoding = opts.fetch(:encoding, @encoding)
140
+ return unless File.file? filename
141
+
142
+ mode = (RUBY_VERSION >= '1.9' && encoding) ?
143
+ "r:#{encoding.to_s}" :
144
+ 'r'
145
+ fd = File.open(filename, mode)
146
+ @content = fd.read
147
+
148
+ parse!
149
+ self
150
+ ensure
151
+ fd.close if fd && !fd.closed?
152
+ end
153
+ alias :restore :read
154
+
155
+ # Returns this IniFile converted to a String.
156
+ #
157
+ def to_s
158
+ s = []
159
+ @ini.each do |section,hash|
160
+ s << "[#{section}]"
161
+ hash.each {|param,val| s << "#{param} #{@param} #{escape_value val}"}
162
+ s << ""
163
+ end
164
+ s.join("\n")
165
+ end
166
+
167
+ # Returns this IniFile converted to a Hash.
168
+ #
169
+ def to_h
170
+ @ini.dup
171
+ end
172
+
173
+ # Public: Creates a copy of this inifile with the entries from the
174
+ # other_inifile merged into the copy.
175
+ #
176
+ # other - The other IniFile.
177
+ #
178
+ # Returns a new IniFile.
179
+ #
180
+ def merge( other )
181
+ self.dup.merge!(other)
182
+ end
183
+
184
+ # Public: Merges other_inifile into this inifile, overwriting existing
185
+ # entries. Useful for having a system inifile with user over-ridable settings
186
+ # elsewhere.
187
+ #
188
+ # other - The other IniFile.
189
+ #
190
+ # Returns this IniFile.
191
+ #
192
+ def merge!( other )
193
+ my_keys = @ini.keys
194
+ other_keys =
195
+ case other
196
+ when IniFile; other.instance_variable_get(:@ini).keys
197
+ when Hash; other.keys
198
+ else raise "cannot merge contents from '#{other.class.name}'" end
199
+
200
+ (my_keys & other_keys).each do |key|
201
+ @ini[key].merge!(other[key])
202
+ end
203
+
204
+ (other_keys - my_keys).each do |key|
205
+ @ini[key] = other[key]
206
+ end
207
+
208
+ self
209
+ end
210
+
211
+ # Public: Yield each INI file section, parameter, and value in turn to the
212
+ # given block.
213
+ #
214
+ # block - The block that will be iterated by the each method. The block will
215
+ # be passed the current section and the parameter / value pair.
216
+ #
217
+ # Examples
218
+ #
219
+ # inifile.each do |section, parameter, value|
220
+ # puts "#{parameter} = #{value} [in section - #{section}]"
221
+ # end
222
+ #
223
+ # Returns this IniFile.
224
+ #
225
+ def each
226
+ return unless block_given?
227
+ @ini.each do |section,hash|
228
+ hash.each do |param,val|
229
+ yield section, param, val
230
+ end
231
+ end
232
+ self
233
+ end
234
+
235
+ # Public: Yield each section in turn to the given block.
236
+ #
237
+ # block - The block that will be iterated by the each method. The block will
238
+ # be passed the current section as a Hash.
239
+ #
240
+ # Examples
241
+ #
242
+ # inifile.each_section do |section|
243
+ # puts section.inspect
244
+ # end
245
+ #
246
+ # Returns this IniFile.
247
+ #
248
+ def each_section
249
+ return unless block_given?
250
+ @ini.each_key {|section| yield section}
251
+ self
252
+ end
253
+
254
+ # Public: Remove a section identified by name from the IniFile.
255
+ #
256
+ # section - The section name as a String.
257
+ #
258
+ # Returns the deleted section Hash.
259
+ #
260
+ def delete_section( section )
261
+ @ini.delete section.to_s
262
+ end
263
+
264
+ # Public: Get the section Hash by name. If the section does not exist, then
265
+ # it will be created.
266
+ #
267
+ # section - The section name as a String.
268
+ #
269
+ # Examples
270
+ #
271
+ # inifile['global']
272
+ # #=> global section Hash
273
+ #
274
+ # Returns the Hash of parameter/value pairs for this section.
275
+ #
276
+ def []( section )
277
+ return nil if section.nil?
278
+ @ini[section.to_s]
279
+ end
280
+
281
+ # Public: Set the section to a hash of parameter/value pairs.
282
+ #
283
+ # section - The section name as a String.
284
+ # value - The Hash of parameter/value pairs.
285
+ #
286
+ # Examples
287
+ #
288
+ # inifile['tenderloin'] = { 'gritty' => 'yes' }
289
+ # #=> { 'gritty' => 'yes' }
290
+ #
291
+ # Returns the value Hash.
292
+ #
293
+ def []=( section, value )
294
+ @ini[section.to_s] = value
295
+ end
296
+
297
+ # Public: Create a Hash containing only those INI file sections whose names
298
+ # match the given regular expression.
299
+ #
300
+ # regex - The Regexp used to match section names.
301
+ #
302
+ # Examples
303
+ #
304
+ # inifile.match(/^tree_/)
305
+ # #=> Hash of matching sections
306
+ #
307
+ # Return a Hash containing only those sections that match the given regular
308
+ # expression.
309
+ #
310
+ def match( regex )
311
+ @ini.dup.delete_if { |section, _| section !~ regex }
312
+ end
313
+
314
+ # Public: Check to see if the IniFile contains the section.
315
+ #
316
+ # section - The section name as a String.
317
+ #
318
+ # Returns true if the section exists in the IniFile.
319
+ #
320
+ def has_section?( section )
321
+ @ini.has_key? section.to_s
322
+ end
323
+
324
+ # Returns an Array of section names contained in this IniFile.
325
+ #
326
+ def sections
327
+ @ini.keys
328
+ end
329
+
330
+ # Public: Freeze the state of this IniFile object. Any attempts to change
331
+ # the object will raise an error.
332
+ #
333
+ # Returns this IniFile.
334
+ #
335
+ def freeze
336
+ super
337
+ @ini.each_value {|h| h.freeze}
338
+ @ini.freeze
339
+ self
340
+ end
341
+
342
+ # Public: Mark this IniFile as tainted -- this will traverse each section
343
+ # marking each as tainted.
344
+ #
345
+ # Returns this IniFile.
346
+ #
347
+ def taint
348
+ super
349
+ @ini.each_value {|h| h.taint}
350
+ @ini.taint
351
+ self
352
+ end
353
+
354
+ # Public: Produces a duplicate of this IniFile. The duplicate is independent
355
+ # of the original -- i.e. the duplicate can be modified without changing the
356
+ # original. The tainted state of the original is copied to the duplicate.
357
+ #
358
+ # Returns a new IniFile.
359
+ #
360
+ def dup
361
+ other = super
362
+ other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
363
+ @ini.each_pair {|s,h| other[s].merge! h}
364
+ other.taint if self.tainted?
365
+ other
366
+ end
367
+
368
+ # Public: Produces a duplicate of this IniFile. The duplicate is independent
369
+ # of the original -- i.e. the duplicate can be modified without changing the
370
+ # original. The tainted state and the frozen state of the original is copied
371
+ # to the duplicate.
372
+ #
373
+ # Returns a new IniFile.
374
+ #
375
+ def clone
376
+ other = dup
377
+ other.freeze if self.frozen?
378
+ other
379
+ end
380
+
381
+ # Public: Compare this IniFile to some other IniFile. For two INI files to
382
+ # be equivalent, they must have the same sections with the same parameter /
383
+ # value pairs in each section.
384
+ #
385
+ # other - The other IniFile.
386
+ #
387
+ # Returns true if the INI files are equivalent and false if they differ.
388
+ #
389
+ def eql?( other )
390
+ return true if equal? other
391
+ return false unless other.instance_of? self.class
392
+ @ini == other.instance_variable_get(:@ini)
393
+ end
394
+ alias :== :eql?
395
+
396
+
397
+ private
398
+
399
+ # Parse the ini file contents. This will clear any values currently stored
400
+ # in the ini hash.
401
+ #
402
+ def parse!
403
+ return unless @content
404
+
405
+ string = ''
406
+ property = ''
407
+
408
+ @ini.clear
409
+ @_line = nil
410
+ @_section = nil
411
+
412
+ scanner = StringScanner.new(@content)
413
+ until scanner.eos?
414
+
415
+ # keep track of the current line for error messages
416
+ @_line = scanner.check(%r/\A.*$/) if scanner.bol?
417
+
418
+ # look for escaped special characters \# \" etc
419
+ if @escape && scanner.scan(%r/\\([\[\]#{@param}#{@comment}"])/)
420
+ string << scanner[1]
421
+
422
+ # look for quoted strings
423
+ elsif scanner.scan(%r/"/)
424
+ quote = scanner.scan_until(/(?:\A|[^\\])"/)
425
+ parse_error('Unmatched quote') if quote.nil?
426
+
427
+ quote.chomp!('"')
428
+ string << quote
429
+
430
+ # look for comments, empty strings, end of lines
431
+ elsif scanner.skip(%r/\A\s*(?:[#{@comment}].*)?$/)
432
+ string << scanner.getch unless scanner.eos?
433
+
434
+ process_property(property, string)
435
+
436
+ # look for the separator between property name and value
437
+ elsif scanner.scan(%r/#{@param}/)
438
+ if property.empty?
439
+ property = string.strip
440
+ string.slice!(0, string.length)
441
+ elsif !@escape
442
+ scanner.pos = scanner.pos - @param.length
443
+ string << read_to_next_token(scanner, true)
444
+ else
445
+ parse_error
446
+ end
447
+
448
+ # look for the start of a new section
449
+ elsif scanner.scan(%r/\A\s*\[([^\]]+)\]/)
450
+ @_section = @ini[scanner[1]]
451
+
452
+ # otherwise scan and store characters till we hit the start of some
453
+ # special section like a quote, newline, comment, etc.
454
+ else
455
+ string << read_to_next_token(scanner, false)
456
+ end
457
+
458
+ end
459
+
460
+ process_property(property, string)
461
+ end
462
+
463
+ def read_to_next_token(scanner, read_value)
464
+
465
+ scan_regex = if read_value
466
+ %r/([\n"] | \z | \\[\[\]#{@param}#{@comment}"])/mx
467
+ elsif @escape then
468
+ %r/([\n"#{@param}#{@comment}] | \z | \\[\[\]#{@param}#{@comment}"])/mx
469
+ else
470
+ %r/([\n"#{@param}#{@comment}] | \z)/mx
471
+ end
472
+
473
+ tmp = scanner.scan_until(scan_regex)
474
+ parse_error if tmp.nil?
475
+
476
+ len = scanner[1].length
477
+ tmp.slice!(tmp.length - len, len)
478
+
479
+ scanner.pos = scanner.pos - len
480
+
481
+ tmp
482
+ end
483
+
484
+ # Store the property / value pair in the currently active section. This
485
+ # method checks for continuation of the value to the next line.
486
+ #
487
+ # property - The property name as a String.
488
+ # value - The property value as a String.
489
+ #
490
+ # Returns nil.
491
+ #
492
+ def process_property( property, value )
493
+ value.chomp!
494
+ return if property.empty? and value.empty?
495
+ return if value.sub!(%r/\\\s*\z/, '')
496
+
497
+ property.strip!
498
+ value.strip!
499
+
500
+ parse_error if property.empty?
501
+
502
+ current_section[property.dup] = unescape_value(value.dup)
503
+
504
+ property.slice!(0, property.length)
505
+ value.slice!(0, value.length)
506
+
507
+ nil
508
+ end
509
+
510
+ # Returns the current section Hash.
511
+ #
512
+ def current_section
513
+ @_section ||= @ini[@default]
514
+ end
515
+
516
+ # Raise a parse error using the given message and appending the current line
517
+ # being parsed.
518
+ #
519
+ # msg - The message String to use.
520
+ #
521
+ # Raises IniFile::Error
522
+ #
523
+ def parse_error( msg = 'Could not parse line' )
524
+ raise Error, "#{msg}: #{@_line.inspect}"
525
+ end
526
+
527
+ # Unescape special characters found in the value string. This will convert
528
+ # escaped null, tab, carriage return, newline, and backslash into their
529
+ # literal equivalents.
530
+ #
531
+ # value - The String value to unescape.
532
+ #
533
+ # Returns the unescaped value.
534
+ #
535
+ def unescape_value( value )
536
+ return value unless @escape
537
+
538
+ value = value.to_s
539
+ value.gsub!(%r/\\[0nrt\\]/) { |char|
540
+ case char
541
+ when '\0'; "\0"
542
+ when '\n'; "\n"
543
+ when '\r'; "\r"
544
+ when '\t'; "\t"
545
+ when '\\\\'; "\\"
546
+ end
547
+ }
548
+ value
549
+ end
550
+
551
+ # Escape special characters.
552
+ #
553
+ # value - The String value to escape.
554
+ #
555
+ # Returns the escaped value.
556
+ #
557
+ def escape_value( value )
558
+ return value unless @escape
559
+
560
+ value = value.to_s.dup
561
+ value.gsub!(%r/\\([0nrt])/, '\\\\\1')
562
+ value.gsub!(%r/\n/, '\n')
563
+ value.gsub!(%r/\r/, '\r')
564
+ value.gsub!(%r/\t/, '\t')
565
+ value.gsub!(%r/\0/, '\0')
566
+ value
567
+ end
568
+
569
+ end # IniFile
570
+