inifile 2.0.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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