inifile 2.0.2 → 3.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b35f40f3c6c004a85d1b6b43766951b49a301e9
4
+ data.tar.gz: 789bc7312e534942372b41591b2099bf80508b46
5
+ SHA512:
6
+ metadata.gz: 2aa8846aa49b6557fd860f66557b78a9a02bea56d3f5bb2e7b8bec2577a2557eb172464a618dee4d32fe5dde70c8453cbe67f71a3c6da4528ea3ba40317afb70
7
+ data.tar.gz: 73e1a6df1802d34439e0ec2924ab84511b58190f64b13072d734bd90fee048224e35ce378c678d9b8a76805e5d51066fd00ab1d877dbc89fd7349b5885451551
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ announcement.txt
15
15
  coverage
16
16
  doc
17
17
  pkg
18
+ vendor
data/README.md CHANGED
@@ -135,6 +135,19 @@ sequences to appear literally in your value. For example:
135
135
 
136
136
  property = this is not a tab \\t character
137
137
 
138
+ ### Value Type Casting
139
+
140
+ Some values will be type cast when parsed by the code. Those values are
141
+ booleans, integers, floats, and empty strings are cast to `nil`.
142
+
143
+ * "" --> nil
144
+ * "42" --> 42
145
+ * "3.14159" --> 3.14159
146
+ * "true" --> true
147
+ * "false" --> false
148
+ * "normal string" --> "normal string"
149
+
150
+ Pretty basic stuff.
138
151
 
139
152
  Install
140
153
  -------
@@ -150,6 +163,15 @@ To run the tests:
150
163
  $ rake
151
164
 
152
165
 
166
+ Examples
167
+ --------
168
+
169
+ require 'inifile'
170
+ myini = IniFile.load('mytest.ini')
171
+ myini.each_section do |section|
172
+ puts "I want #{myini[section]['somevar']} printed here!"
173
+ end
174
+
153
175
  Contributing
154
176
  ------------
155
177
 
@@ -171,7 +193,7 @@ License
171
193
  -------
172
194
 
173
195
  MIT License
174
- Copyright (c) 2006 - 2012
196
+ Copyright (c) 2006 - 2014
175
197
 
176
198
  Permission is hereby granted, free of charge, to any person obtaining
177
199
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -20,5 +20,5 @@ Bones {
20
20
  version IniFile::VERSION
21
21
 
22
22
  use_gmail
23
- depend_on 'bones-git', :development => true
23
+ depend_on 'bones-git', "~> 1.3", :development => true
24
24
  }
@@ -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 = "inifile"
8
+ spec.version = IniFile::VERSION
9
+ spec.authors = ["Tim Pease"]
10
+ spec.email = ["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://rubygems.org/gems/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
@@ -1,23 +1,20 @@
1
1
  #encoding: UTF-8
2
- require 'strscan'
3
2
 
4
3
  # This class represents the INI file and can be used to parse, modify,
5
4
  # and write INI files.
6
- #
7
5
  class IniFile
8
6
  include Enumerable
9
7
 
10
8
  class Error < StandardError; end
11
- VERSION = '2.0.2'
9
+ VERSION = '3.0.0'
12
10
 
13
11
  # Public: Open an INI file and load the contents.
14
12
  #
15
- # filename - The name of the fiel as a String
13
+ # filename - The name of the file as a String
16
14
  # opts - The Hash of options (default: {})
17
15
  # :comment - String containing the comment character(s)
18
16
  # :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
17
+ # :encoding - Encoding String for reading / writing
21
18
  # :default - The String name of the default global section
22
19
  #
23
20
  # Examples
@@ -28,8 +25,7 @@ class IniFile
28
25
  # IniFile.load('does/not/exist.ini')
29
26
  # #=> nil
30
27
  #
31
- # Returns an IniFile intsnace or nil if the file could not be opened.
32
- #
28
+ # Returns an IniFile instance or nil if the file could not be opened.
33
29
  def self.load( filename, opts = {} )
34
30
  return unless File.file? filename
35
31
  new(opts.merge(:filename => filename))
@@ -38,57 +34,50 @@ class IniFile
38
34
  # Get and set the filename
39
35
  attr_accessor :filename
40
36
 
41
- # Get and set the encoding (Ruby 1.9)
37
+ # Get and set the encoding
42
38
  attr_accessor :encoding
43
39
 
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.
40
+ # Public: Create a new INI file from the given set of options. If :content
41
+ # is provided then it will be used to populate the INI file. If a :filename
42
+ # is provided then the contents of the file will be parsed and stored in the
43
+ # INI file. If neither the :content or :filename is provided then an empty
44
+ # INI file is created.
52
45
  #
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
46
+ # opts - The Hash of options (default: {})
47
+ # :content - The String/Hash containing the INI contents
48
+ # :comment - String containing the comment character(s)
49
+ # :parameter - String used to separate parameter and value
50
+ # :encoding - Encoding String for reading / writing
51
+ # :default - The String name of the default global section
52
+ # :filename - The filename as a String
61
53
  #
62
54
  # Examples
63
55
  #
64
56
  # IniFile.new
65
57
  # #=> an empty IniFile instance
66
58
  #
67
- # IniFile.new( "[global]\nfoo=bar" )
59
+ # IniFile.new( :content => "[global]\nfoo=bar" )
68
60
  # #=> an IniFile instance
69
61
  #
70
62
  # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' )
71
63
  # #=> an IniFile instance
72
64
  #
73
- # IniFile.new( "[global]\nfoo=bar", :comment => '#' )
65
+ # IniFile.new( :content => "[global]\nfoo=bar", :comment => '#' )
74
66
  # #=> an IniFile instance
75
67
  #
76
- def initialize( content = nil, opts = {} )
77
- opts, content = content, nil if Hash === content
78
-
79
- @content = content
80
-
68
+ def initialize( opts = {} )
81
69
  @comment = opts.fetch(:comment, ';#')
82
70
  @param = opts.fetch(:parameter, '=')
83
71
  @encoding = opts.fetch(:encoding, nil)
84
- @escape = opts.fetch(:escape, true)
85
72
  @default = opts.fetch(:default, 'global')
86
73
  @filename = opts.fetch(:filename, nil)
74
+ content = opts.fetch(:content, nil)
87
75
 
88
76
  @ini = Hash.new {|h,k| h[k] = Hash.new}
89
77
 
90
- if @content then parse!
91
- elsif @filename then read
78
+ if content.is_a?(Hash) then merge!(content)
79
+ elsif content then parse(content)
80
+ elsif @filename then read
92
81
  end
93
82
  end
94
83
 
@@ -98,16 +87,13 @@ class IniFile
98
87
  #
99
88
  # opts - The default options Hash
100
89
  # :filename - The filename as a String
101
- # :encoding - The encoding as a String (Ruby 1.9)
90
+ # :encoding - The encoding as a String
102
91
  #
103
92
  # Returns this IniFile instance.
104
- #
105
93
  def write( opts = {} )
106
94
  filename = opts.fetch(:filename, @filename)
107
95
  encoding = opts.fetch(:encoding, @encoding)
108
- mode = (RUBY_VERSION >= '1.9' && encoding) ?
109
- "w:#{encoding.to_s}" :
110
- 'w'
96
+ mode = encoding ? "w:#{encoding}" : "w"
111
97
 
112
98
  File.open(filename, mode) do |f|
113
99
  @ini.each do |section,hash|
@@ -129,31 +115,22 @@ class IniFile
129
115
  #
130
116
  # opts - The default options Hash
131
117
  # :filename - The filename as a String
132
- # :encoding - The encoding as a String (Ruby 1.9)
118
+ # :encoding - The encoding as a String
133
119
  #
134
120
  # Returns this IniFile instance if the read was successful; nil is returned
135
121
  # if the file could not be read.
136
- #
137
122
  def read( opts = {} )
138
123
  filename = opts.fetch(:filename, @filename)
139
124
  encoding = opts.fetch(:encoding, @encoding)
140
125
  return unless File.file? filename
141
126
 
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!
127
+ mode = encoding ? "r:#{encoding}" : "r"
128
+ File.open(filename, mode) { |fd| parse fd }
149
129
  self
150
- ensure
151
- fd.close if fd && !fd.closed?
152
130
  end
153
131
  alias :restore :read
154
132
 
155
133
  # Returns this IniFile converted to a String.
156
- #
157
134
  def to_s
158
135
  s = []
159
136
  @ini.each do |section,hash|
@@ -165,7 +142,6 @@ class IniFile
165
142
  end
166
143
 
167
144
  # Returns this IniFile converted to a Hash.
168
- #
169
145
  def to_h
170
146
  @ini.dup
171
147
  end
@@ -176,33 +152,50 @@ class IniFile
176
152
  # other - The other IniFile.
177
153
  #
178
154
  # Returns a new IniFile.
179
- #
180
155
  def merge( other )
181
156
  self.dup.merge!(other)
182
157
  end
183
158
 
184
159
  # Public: Merges other_inifile into this inifile, overwriting existing
185
- # entries. Useful for having a system inifile with user over-ridable settings
160
+ # entries. Useful for having a system inifile with user overridable settings
186
161
  # elsewhere.
187
162
  #
188
163
  # other - The other IniFile.
189
164
  #
190
165
  # Returns this IniFile.
191
- #
192
166
  def merge!( other )
167
+ return self if other.nil?
168
+
193
169
  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
170
+ other_keys = case other
171
+ when IniFile
172
+ other.instance_variable_get(:@ini).keys
173
+ when Hash
174
+ other.keys
175
+ else
176
+ raise Error, "cannot merge contents from '#{other.class.name}'"
177
+ end
199
178
 
200
179
  (my_keys & other_keys).each do |key|
201
- @ini[key].merge!(other[key])
180
+ case other[key]
181
+ when Hash
182
+ @ini[key].merge!(other[key])
183
+ when nil
184
+ nil
185
+ else
186
+ raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
187
+ end
202
188
  end
203
189
 
204
190
  (other_keys - my_keys).each do |key|
205
- @ini[key] = other[key]
191
+ @ini[key] = case other[key]
192
+ when Hash
193
+ other[key].dup
194
+ when nil
195
+ {}
196
+ else
197
+ raise Error, "cannot merge section #{key.inspect} - unsupported type: #{other[key].class.name}"
198
+ end
206
199
  end
207
200
 
208
201
  self
@@ -212,7 +205,7 @@ class IniFile
212
205
  # given block.
213
206
  #
214
207
  # 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.
208
+ # be passed the current section and the parameter/value pair.
216
209
  #
217
210
  # Examples
218
211
  #
@@ -221,7 +214,6 @@ class IniFile
221
214
  # end
222
215
  #
223
216
  # Returns this IniFile.
224
- #
225
217
  def each
226
218
  return unless block_given?
227
219
  @ini.each do |section,hash|
@@ -244,7 +236,6 @@ class IniFile
244
236
  # end
245
237
  #
246
238
  # Returns this IniFile.
247
- #
248
239
  def each_section
249
240
  return unless block_given?
250
241
  @ini.each_key {|section| yield section}
@@ -256,7 +247,6 @@ class IniFile
256
247
  # section - The section name as a String.
257
248
  #
258
249
  # Returns the deleted section Hash.
259
- #
260
250
  def delete_section( section )
261
251
  @ini.delete section.to_s
262
252
  end
@@ -272,7 +262,6 @@ class IniFile
272
262
  # #=> global section Hash
273
263
  #
274
264
  # Returns the Hash of parameter/value pairs for this section.
275
- #
276
265
  def []( section )
277
266
  return nil if section.nil?
278
267
  @ini[section.to_s]
@@ -289,7 +278,6 @@ class IniFile
289
278
  # #=> { 'gritty' => 'yes' }
290
279
  #
291
280
  # Returns the value Hash.
292
- #
293
281
  def []=( section, value )
294
282
  @ini[section.to_s] = value
295
283
  end
@@ -306,7 +294,6 @@ class IniFile
306
294
  #
307
295
  # Return a Hash containing only those sections that match the given regular
308
296
  # expression.
309
- #
310
297
  def match( regex )
311
298
  @ini.dup.delete_if { |section, _| section !~ regex }
312
299
  end
@@ -316,13 +303,11 @@ class IniFile
316
303
  # section - The section name as a String.
317
304
  #
318
305
  # Returns true if the section exists in the IniFile.
319
- #
320
306
  def has_section?( section )
321
307
  @ini.has_key? section.to_s
322
308
  end
323
309
 
324
310
  # Returns an Array of section names contained in this IniFile.
325
- #
326
311
  def sections
327
312
  @ini.keys
328
313
  end
@@ -331,7 +316,6 @@ class IniFile
331
316
  # the object will raise an error.
332
317
  #
333
318
  # Returns this IniFile.
334
- #
335
319
  def freeze
336
320
  super
337
321
  @ini.each_value {|h| h.freeze}
@@ -343,7 +327,6 @@ class IniFile
343
327
  # marking each as tainted.
344
328
  #
345
329
  # Returns this IniFile.
346
- #
347
330
  def taint
348
331
  super
349
332
  @ini.each_value {|h| h.taint}
@@ -356,7 +339,6 @@ class IniFile
356
339
  # original. The tainted state of the original is copied to the duplicate.
357
340
  #
358
341
  # Returns a new IniFile.
359
- #
360
342
  def dup
361
343
  other = super
362
344
  other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
@@ -371,7 +353,6 @@ class IniFile
371
353
  # to the duplicate.
372
354
  #
373
355
  # Returns a new IniFile.
374
- #
375
356
  def clone
376
357
  other = dup
377
358
  other.freeze if self.frozen?
@@ -385,7 +366,6 @@ class IniFile
385
366
  # other - The other IniFile.
386
367
  #
387
368
  # Returns true if the INI files are equivalent and false if they differ.
388
- #
389
369
  def eql?( other )
390
370
  return true if equal? other
391
371
  return false unless other.instance_of? self.class
@@ -393,159 +373,255 @@ class IniFile
393
373
  end
394
374
  alias :== :eql?
395
375
 
396
-
397
- private
398
-
399
- # Parse the ini file contents. This will clear any values currently stored
400
- # in the ini hash.
376
+ # Escape special characters.
401
377
  #
402
- def parse!
403
- return unless @content
404
-
405
- string = ''
406
- property = ''
378
+ # value - The String value to escape.
379
+ #
380
+ # Returns the escaped value.
381
+ def escape_value( value )
382
+ value = value.to_s.dup
383
+ value.gsub!(%r/\\([0nrt])/, '\\\\\1')
384
+ value.gsub!(%r/\n/, '\n')
385
+ value.gsub!(%r/\r/, '\r')
386
+ value.gsub!(%r/\t/, '\t')
387
+ value.gsub!(%r/\0/, '\0')
388
+ value
389
+ end
407
390
 
408
- @ini.clear
409
- @_line = nil
410
- @_section = nil
391
+ # Parse the given content and store the information in this IniFile
392
+ # instance. All data will be cleared out and replaced with the information
393
+ # read from the content.
394
+ #
395
+ # content - A String or a file descriptor (must respond to `each_line`)
396
+ #
397
+ # Returns this IniFile.
398
+ def parse( content )
399
+ parser = Parser.new(@ini, @param, @comment, @default)
400
+ parser.parse(content)
401
+ self
402
+ end
411
403
 
412
- scanner = StringScanner.new(@content)
413
- until scanner.eos?
404
+ # The IniFile::Parser has the responsibility of reading the contents of an
405
+ # .ini file and storing that information into a ruby Hash. The object being
406
+ # parsed must respond to `each_line` - this includes Strings and any IO
407
+ # object.
408
+ class Parser
409
+
410
+ attr_writer :section
411
+ attr_accessor :property
412
+ attr_accessor :value
413
+
414
+ # Create a new IniFile::Parser that can be used to parse the contents of
415
+ # an .ini file.
416
+ #
417
+ # hash - The Hash where parsed information will be stored
418
+ # param - String used to separate parameter and value
419
+ # comment - String containing the comment character(s)
420
+ # default - The String name of the default global section
421
+ #
422
+ def initialize( hash, param, comment, default )
423
+ @hash = hash
424
+ @default = default
425
+
426
+ comment = comment.to_s.empty? ? "\\z" : "\\s*(?:[#{comment}].*)?\\z"
427
+
428
+ @section_regexp = %r/\A\s*\[([^\]]+)\]#{comment}/
429
+ @ignore_regexp = %r/\A#{comment}/
430
+ @property_regexp = %r/\A(.*?)(?<!\\)#{param}(.*)\z/
431
+
432
+ @open_quote = %r/\A\s*(".*)\z/
433
+ @close_quote = %r/\A(.*(?<!\\)")#{comment}/
434
+ @full_quote = %r/\A\s*(".*(?<!\\)")#{comment}/
435
+ @trailing_slash = %r/\A(.*)(?<!\\)\\#{comment}/
436
+ @normal_value = %r/\A(.*?)#{comment}/
437
+ end
414
438
 
415
- # keep track of the current line for error messages
416
- @_line = scanner.check(%r/\A.*$/) if scanner.bol?
439
+ # Returns `true` if the current value starts with a leading double quote.
440
+ # Otherwise returns false.
441
+ def leading_quote?
442
+ value && value =~ %r/\A"/
443
+ end
417
444
 
418
- # look for escaped special characters \# \" etc
419
- if scanner.scan(%r/\\([\[\]#{@param}#{@comment}"])/)
420
- string << scanner[1]
445
+ # Given a string, attempt to parse out a value from that string. This
446
+ # value might be continued on the following line. So this method returns
447
+ # `true` if it is expecting more data.
448
+ #
449
+ # string - String to parse
450
+ #
451
+ # Returns `true` if the next line is also part of the current value.
452
+ # Returns `fase` if the string contained a complete value.
453
+ def parse_value( string )
454
+ continuation = false
455
+
456
+ # if our value starts with a double quote, then we are in a
457
+ # line continuation situation
458
+ if leading_quote?
459
+ # check for a closing quote at the end of the string
460
+ if string =~ @close_quote
461
+ value << $1
462
+
463
+ # otherwise just append the string to the value
464
+ else
465
+ value << string
466
+ continuation = true
467
+ end
421
468
 
422
- # look for quoted strings
423
- elsif scanner.scan(%r/"/)
424
- quote = scanner.scan_until(/(?:\A|[^\\])"/)
425
- parse_error('Unmatched quote') if quote.nil?
469
+ # not currently processing a continuation line
470
+ else
471
+ case string
472
+ when @full_quote
473
+ self.value = $1
426
474
 
427
- quote.chomp!('"')
428
- string << quote
475
+ when @open_quote
476
+ self.value = $1
477
+ continuation = true
429
478
 
430
- # look for comments, empty strings, end of lines
431
- elsif scanner.skip(%r/\A\s*(?:[#{@comment}].*)?$/)
432
- string << scanner.getch unless scanner.eos?
479
+ when @trailing_slash
480
+ self.value ? self.value << $1 : self.value = $1
481
+ continuation = true
433
482
 
434
- process_property(property, string)
483
+ when @normal_value
484
+ self.value ? self.value << $1 : self.value = $1
435
485
 
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
486
  else
442
- parse_error
487
+ error
443
488
  end
489
+ end
444
490
 
445
- # look for the start of a new section
446
- elsif scanner.scan(%r/\A\s*\[([^\]]+)\]/)
447
- @_section = @ini[scanner[1]]
448
-
449
- # otherwise scan and store characters till we hit the start of some
450
- # special section like a quote, newline, comment, etc.
491
+ if continuation
492
+ self.value << $/ if leading_quote?
451
493
  else
452
- tmp = scanner.scan_until(%r/([\n"#{@param}#{@comment}] | \z | \\[\[\]#{@param}#{@comment}"])/mx)
453
- parse_error if tmp.nil?
454
-
455
- len = scanner[1].length
456
- tmp.slice!(tmp.length - len, len)
457
-
458
- scanner.pos = scanner.pos - len
459
- string << tmp
494
+ process_property
460
495
  end
496
+
497
+ continuation
461
498
  end
462
499
 
463
- process_property(property, string)
464
- end
500
+ # Parse the ini file contents. This will clear any values currently stored
501
+ # in the ini hash.
502
+ #
503
+ # content - Any object that responds to `each_line`
504
+ #
505
+ # Returns nil.
506
+ def parse( content )
507
+ return unless content
465
508
 
466
- # Store the property / value pair in the currently active section. This
467
- # method checks for continuation of the value to the next line.
468
- #
469
- # property - The property name as a String.
470
- # value - The property value as a String.
471
- #
472
- # Returns nil.
473
- #
474
- def process_property( property, value )
475
- value.chomp!
476
- return if property.empty? and value.empty?
477
- return if value.sub!(%r/\\\s*\z/, '')
509
+ continuation = false
478
510
 
479
- property.strip!
480
- value.strip!
511
+ @hash.clear
512
+ @line = nil
513
+ self.section = nil
481
514
 
482
- parse_error if property.empty?
515
+ content.each_line do |line|
516
+ @line = line.chomp
483
517
 
484
- current_section[property.dup] = unescape_value(value.dup)
518
+ if continuation
519
+ continuation = parse_value @line
520
+ else
521
+ case @line
522
+ when @ignore_regexp
523
+ nil
524
+ when @section_regexp
525
+ self.section = @hash[$1]
526
+ when @property_regexp
527
+ self.property = $1.strip
528
+ error if property.empty?
529
+
530
+ continuation = parse_value $2
531
+ else
532
+ error
533
+ end
534
+ end
535
+ end
485
536
 
486
- property.slice!(0, property.length)
487
- value.slice!(0, value.length)
537
+ # check here if we have a dangling value ... usually means we have an
538
+ # unmatched open quote
539
+ if leading_quote?
540
+ error "Unmatched open quote"
541
+ elsif property && value
542
+ process_property
543
+ elsif value
544
+ error
545
+ end
488
546
 
489
- nil
490
- end
547
+ nil
548
+ end
491
549
 
492
- # Returns the current section Hash.
493
- #
494
- def current_section
495
- @_section ||= @ini[@default]
496
- end
550
+ # Store the property/value pair in the currently active section. This
551
+ # method checks for continuation of the value to the next line.
552
+ #
553
+ # Returns nil.
554
+ def process_property
555
+ property.strip!
556
+ value.strip!
497
557
 
498
- # Raise a parse error using the given message and appending the current line
499
- # being parsed.
500
- #
501
- # msg - The message String to use.
502
- #
503
- # Raises IniFile::Error
504
- #
505
- def parse_error( msg = 'Could not parse line' )
506
- raise Error, "#{msg}: #{@_line.inspect}"
507
- end
558
+ self.value = $1 if value =~ %r/\A"(.*)(?<!\\)"\z/m
508
559
 
509
- # Unescape special characters found in the value string. This will convert
510
- # escaped null, tab, carriage return, newline, and backslash into their
511
- # literal equivalents.
512
- #
513
- # value - The String value to unescape.
514
- #
515
- # Returns the unescaped value.
516
- #
517
- def unescape_value( value )
518
- return value unless @escape
560
+ section[property] = typecast(value)
519
561
 
520
- value = value.to_s
521
- value.gsub!(%r/\\[0nrt\\]/) { |char|
522
- case char
523
- when '\0'; "\0"
524
- when '\n'; "\n"
525
- when '\r'; "\r"
526
- when '\t'; "\t"
527
- when '\\\\'; "\\"
528
- end
529
- }
530
- value
531
- end
562
+ self.property = nil
563
+ self.value = nil
564
+ end
532
565
 
533
- # Escape special characters.
534
- #
535
- # value - The String value to escape.
536
- #
537
- # Returns the escaped value.
538
- #
539
- def escape_value( value )
540
- return value unless @escape
566
+ # Returns the current section Hash.
567
+ def section
568
+ @section ||= @hash[@default]
569
+ end
541
570
 
542
- value = value.to_s.dup
543
- value.gsub!(%r/\\([0nrt])/, '\\\\\1')
544
- value.gsub!(%r/\n/, '\n')
545
- value.gsub!(%r/\r/, '\r')
546
- value.gsub!(%r/\t/, '\t')
547
- value.gsub!(%r/\0/, '\0')
548
- value
571
+ # Raise a parse error using the given message and appending the current line
572
+ # being parsed.
573
+ #
574
+ # msg - The message String to use.
575
+ #
576
+ # Raises IniFile::Error
577
+ def error( msg = 'Could not parse line' )
578
+ raise Error, "#{msg}: #{@line.inspect}"
579
+ end
580
+
581
+ # Attempt to typecast the value string. We are looking for boolean values,
582
+ # integers, floats, and empty strings. Below is how each gets cast, but it
583
+ # is pretty logical and straightforward.
584
+ #
585
+ # "true" --> true
586
+ # "false" --> false
587
+ # "" --> nil
588
+ # "42" --> 42
589
+ # "3.14" --> 3.14
590
+ # "foo" --> "foo"
591
+ #
592
+ # Returns the typecast value.
593
+ def typecast( value )
594
+ case value
595
+ when %r/\Atrue\z/i; true
596
+ when %r/\Afalse\z/i; false
597
+ when %r/\A\s*\z/i; nil
598
+ else
599
+ Integer(value) rescue \
600
+ Float(value) rescue \
601
+ unescape_value(value)
602
+ end
603
+ end
604
+
605
+ # Unescape special characters found in the value string. This will convert
606
+ # escaped null, tab, carriage return, newline, and backslash into their
607
+ # literal equivalents.
608
+ #
609
+ # value - The String value to unescape.
610
+ #
611
+ # Returns the unescaped value.
612
+ def unescape_value( value )
613
+ value = value.to_s
614
+ value.gsub!(%r/\\[0nrt\\]/) { |char|
615
+ case char
616
+ when '\0'; "\0"
617
+ when '\n'; "\n"
618
+ when '\r'; "\r"
619
+ when '\t'; "\t"
620
+ when '\\\\'; "\\"
621
+ end
622
+ }
623
+ value
624
+ end
549
625
  end
550
626
 
551
627
  end # IniFile