inifile 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,13 @@
1
+ == 2.0.0 / 2012-08-29
2
+
3
+ Major Enhancements
4
+ - Can now initialize from Strings [issue #9]
5
+ - Quoted multi-line values are supported
6
+ - Comments can appear at the end of a line
7
+ Enhancements
8
+ - TomDoc documentation
9
+ - Parity between read/write methods
10
+
1
11
  == 1.1.0 / 2012-02-28
2
12
 
3
13
  Enhancements
data/README.md CHANGED
@@ -87,17 +87,42 @@ the earlier section.
87
87
  ### Comments
88
88
 
89
89
  The comment character can be either a semicolon *;* or a number sign *#*. The
90
- comment character must be the first non-whitespace character on the line. This
91
- means it is perfectly valid to include a comment character inside a **value**
92
- or event a property **name** (although this is not recommended). For this
93
- reason, comments cannot be placed on the end of a line after a name/value
94
- pair.
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`.
95
119
 
96
120
  ### Escape Characters
97
121
 
98
122
  Several escape characters are supported within the **value** for a property.
99
- Most notably, a backslash *\* at the end of a line will continue the value
100
- onto the next line. When parsed, a literal newline will appear in the value.
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.
101
126
 
102
127
  * \0 -- null character
103
128
  * \n -- newline character
@@ -108,7 +133,7 @@ onto the next line. When parsed, a literal newline will appear in the value.
108
133
  The backslash escape sequence is only needed if you want one of the escape
109
134
  sequences to appear literally in your value. For example:
110
135
 
111
- property=this is not a tab \\t character
136
+ property = this is not a tab \\t character
112
137
 
113
138
 
114
139
  Install
data/a.rb ADDED
@@ -0,0 +1,125 @@
1
+ require 'strscan'
2
+ require 'pp'
3
+
4
+ @content = <<CONTENT
5
+ [section_one]
6
+ one = 1
7
+ two = 2
8
+
9
+ [section_two] # inline section comment
10
+ three = 3
11
+ multi = multiline \\ # inline multiline comment
12
+ support
13
+ quote = "quoted # strings are the best!"
14
+
15
+ ; comments should be ignored
16
+ [section three]
17
+ four =4
18
+ five=5
19
+ six =6
20
+ "[foobar]" = 7
21
+
22
+ [section_four]
23
+ [section_five]
24
+ seven and eight= 7 & 8
25
+ CONTENT
26
+
27
+ @comment = ';#'
28
+ @param = '='
29
+ @default = 'global'
30
+
31
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
32
+
33
+ def parse
34
+
35
+ string = ''
36
+ property = ''
37
+
38
+ @ini.clear
39
+ @_line = nil
40
+ @_section = nil
41
+
42
+ scanner = StringScanner.new(@content)
43
+ until scanner.eos?
44
+
45
+ # keep track of the current line for error messages
46
+ if scanner.bol?
47
+ @_line = scanner.check(%r/\A.*$/) if scanner.bol?
48
+ puts "[line] #@_line"
49
+ end
50
+
51
+ # look for escaped special characters \# \" etc
52
+ if scanner.scan(%r/\\([\[\]#{@param}#{@comment}"])/)
53
+ string << scanner[1]
54
+
55
+ # look for quoted strings
56
+ elsif scanner.scan(%r/"/)
57
+ quote = scanner.scan_until(/(?:\A|[^\\])"/)
58
+ parse_error('Unmatched quote') if quote.nil?
59
+
60
+ quote.chomp!('"')
61
+ string << quote
62
+
63
+ # look for comments, empty strings, end of lines
64
+ elsif scanner.skip(%r/\A\s*(?:[#{@comment}].*)?$/)
65
+ string << scanner.getch unless scanner.eos?
66
+
67
+ process_property(property, string)
68
+
69
+ # look for the separator between property name and value
70
+ elsif scanner.scan(%r/#{@param}/)
71
+ if property.empty?
72
+ property = string.strip
73
+ string.clear
74
+ else
75
+ parse_error
76
+ end
77
+
78
+ # look for the start of a new section
79
+ elsif scanner.scan(%r/\A\s*\[([^\]]+)\]/)
80
+ @_section = @ini[scanner[1]]
81
+
82
+ # store the next character and loop again
83
+ else
84
+ tmp = scanner.scan_until(%r/([\n"#{@param}#{@comment}]|\\[\[\]#{@param}#{@comment}"])/m)
85
+ len = scanner[1].length
86
+ tmp.slice!(tmp.length - len, len)
87
+
88
+ scanner.pos = scanner.pos - len
89
+ string << tmp
90
+ end
91
+ end
92
+
93
+ process_property(property, string)
94
+ end
95
+
96
+ def process_property(property, value)
97
+ value.chomp!
98
+ return if property.empty? and value.empty?
99
+ return if value.sub!(%r/\\\s*\z/, '')
100
+
101
+ property.strip!
102
+ value.strip!
103
+
104
+ parse_error if property.empty?
105
+
106
+ current_section[property.dup] = value.dup
107
+
108
+ property.clear
109
+ value.clear
110
+
111
+ nil
112
+ end
113
+
114
+ def current_section
115
+ @_section ||= @ini[@default]
116
+ end
117
+
118
+ def parse_error( msg = 'Could not parse line' )
119
+ raise "#{msg}: #{@_line.inspect}"
120
+ end
121
+
122
+ parse
123
+
124
+ puts '='*72
125
+ pp @ini
@@ -1,143 +1,193 @@
1
- #
1
+ #encoding: UTF-8
2
+ require 'strscan'
3
+
2
4
  # This class represents the INI file and can be used to parse, modify,
3
5
  # and write INI files.
4
6
  #
5
-
6
- #encoding: UTF-8
7
-
8
7
  class IniFile
9
-
10
- # Inifile is enumerable.
11
8
  include Enumerable
12
9
 
13
- # :stopdoc:
14
10
  class Error < StandardError; end
15
- VERSION = '1.1.0'
16
- # :startdoc:
11
+ VERSION = '2.0.0'
17
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
18
22
  #
19
- # call-seq:
20
- # IniFile.load( filename )
21
- # IniFile.load( filename, options )
23
+ # Examples
22
24
  #
23
- # Open the given _filename_ and load the contents of the INI file.
24
- # The following _options_ can be passed to this method:
25
+ # IniFile.load('file.ini')
26
+ # #=> IniFile instance
25
27
  #
26
- # :comment => ';' The line comment character(s)
27
- # :parameter => '=' The parameter / value separator
28
- # :encoding => nil The encoding used for read/write (RUBY 1.9)
29
- # :escape => true Whether or not to escape values when reading/writing
30
- # :default => 'global' Default global section name
28
+ # IniFile.load('does/not/exist.ini')
29
+ # #=> nil
30
+ #
31
+ # Returns an IniFile intsnace or nil if the file could not be opened.
31
32
  #
32
33
  def self.load( filename, opts = {} )
33
- new(filename, opts)
34
+ return unless File.file? filename
35
+ new(opts.merge(:filename => filename))
34
36
  end
35
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
36
66
  #
37
- # call-seq:
38
- # IniFile.new( filename )
39
- # IniFile.new( filename, options )
67
+ # IniFile.new( "[global]\nfoo=bar" )
68
+ # #=> an IniFile instance
40
69
  #
41
- # Create a new INI file using the given _filename_. If _filename_
42
- # exists and is a regular file, then its contents will be parsed.
43
- # The following _options_ can be passed to this method:
70
+ # IniFile.new( :filename => 'file.ini', :encoding => 'UTF-8' )
71
+ # #=> an IniFile instance
44
72
  #
45
- # :comment => ';' The line comment character(s)
46
- # :parameter => '=' The parameter / value separator
47
- # :encoding => nil The encoding used for read/write (RUBY 1.9)
48
- # :escape => true Whether or not to escape values when reading/writing
49
- # :default => 'global' Default global section name
73
+ # IniFile.new( "[global]\nfoo=bar", :comment => '#' )
74
+ # #=> an IniFile instance
50
75
  #
51
- def initialize( filename, opts = {} )
52
- @fn = filename
53
- @comment = opts.fetch(:comment, ';#')
54
- @param = opts.fetch(:parameter, '=')
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, '=')
55
83
  @encoding = opts.fetch(:encoding, nil)
56
- @escape = opts.fetch(:escape, true)
57
- @default = opts.fetch(:default, 'global')
58
- @ini = Hash.new {|h,k| h[k] = Hash.new}
84
+ @escape = opts.fetch(:escape, true)
85
+ @default = opts.fetch(:default, 'global')
86
+ @filename = opts.fetch(:filename, nil)
59
87
 
60
- @rgxp_comment = %r/\A\s*\z|\A\s*[#{@comment}]/
61
- @rgxp_section = %r/\A\s*\[([^\]]+)\]/
62
- @rgxp_param = %r/[^\\]#{@param}/
88
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
63
89
 
64
- parse
90
+ if @content then parse!
91
+ elsif @filename then read
92
+ end
65
93
  end
66
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.
67
98
  #
68
- # call-seq:
69
- # write
70
- # write( filename )
99
+ # opts - The default options Hash
100
+ # :filename - The filename as a String
101
+ # :encoding - The encoding as a String (Ruby 1.9)
71
102
  #
72
- # Write the INI file contents to the file system. The given _filename_
73
- # will be used to write the file. If _filename_ is not given, then the
74
- # named used when constructing this object will be used.
75
- # The following _options_ can be passed to this method:
103
+ # Returns this IniFile instance.
76
104
  #
77
- # :encoding => nil The encoding used for writing (RUBY 1.9)
78
- #
79
- def write( filename = nil, opts={} )
80
- @fn = filename unless filename.nil?
81
-
82
- encoding = opts[:encoding] || @encoding
83
- mode = (RUBY_VERSION >= '1.9' && @encoding) ?
105
+ def write( opts = {} )
106
+ filename = opts.fetch(:filename, @filename)
107
+ encoding = opts.fetch(:encoding, @encoding)
108
+ mode = (RUBY_VERSION >= '1.9' && encoding) ?
84
109
  "w:#{encoding.to_s}" :
85
110
  'w'
86
111
 
87
- File.open(@fn, mode) do |f|
112
+ File.open(filename, mode) do |f|
88
113
  @ini.each do |section,hash|
89
114
  f.puts "[#{section}]"
90
- hash.each {|param,val| f.puts "#{param} #{@param} #{escape val}"}
115
+ hash.each {|param,val| f.puts "#{param} #{@param} #{escape_value val}"}
91
116
  f.puts
92
117
  end
93
118
  end
119
+
94
120
  self
95
121
  end
96
122
  alias :save :write
97
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.
98
129
  #
99
- # call-seq:
100
- # to_s
130
+ # opts - The default options Hash
131
+ # :filename - The filename as a String
132
+ # :encoding - The encoding as a String (Ruby 1.9)
101
133
  #
102
- # Convert IniFile to text format.
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.
103
156
  #
104
157
  def to_s
105
158
  s = []
106
159
  @ini.each do |section,hash|
107
160
  s << "[#{section}]"
108
- hash.each {|param,val| s << "#{param} #{@param} #{escape val}"}
161
+ hash.each {|param,val| s << "#{param} #{@param} #{escape_value val}"}
109
162
  s << ""
110
163
  end
111
164
  s.join("\n")
112
165
  end
113
166
 
114
- #
115
- # call-seq:
116
- # to_h
117
- #
118
- # Convert IniFile to hash format.
167
+ # Returns this IniFile converted to a Hash.
119
168
  #
120
169
  def to_h
121
170
  @ini.dup
122
171
  end
123
172
 
173
+ # Public: Creates a copy of this inifile with the entries from the
174
+ # other_inifile merged into the copy.
124
175
  #
125
- # call-seq:
126
- # merge( other_inifile )
176
+ # other - The other IniFile.
127
177
  #
128
- # Returns a copy of this inifile with the entries from the other_inifile
129
- # merged into the copy.
178
+ # Returns a new IniFile.
130
179
  #
131
180
  def merge( other )
132
181
  self.dup.merge!(other)
133
182
  end
134
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.
135
187
  #
136
- # call-seq:
137
- # merge!( other_inifile )
188
+ # other - The other IniFile.
138
189
  #
139
- # Merges other_inifile into this inifile, overwriting existing entries.
140
- # Useful for having a system inifile with user overridable settings elsewhere.
190
+ # Returns this IniFile.
141
191
  #
142
192
  def merge!( other )
143
193
  my_keys = @ini.keys
@@ -158,12 +208,19 @@ class IniFile
158
208
  self
159
209
  end
160
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.
161
216
  #
162
- # call-seq:
163
- # each {|section, parameter, value| block}
217
+ # Examples
164
218
  #
165
- # Yield each _section_, _parameter_, _value_ in turn to the given
166
- # _block_. The method returns immediately if no block is supplied.
219
+ # inifile.each do |section, parameter, value|
220
+ # puts "#{parameter} = #{value} [in section - #{section}]"
221
+ # end
222
+ #
223
+ # Returns this IniFile.
167
224
  #
168
225
  def each
169
226
  return unless block_given?
@@ -175,12 +232,18 @@ class IniFile
175
232
  self
176
233
  end
177
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.
178
239
  #
179
- # call-seq:
180
- # each_section {|section| block}
240
+ # Examples
181
241
  #
182
- # Yield each _section_ in turn to the given _block_. The method returns
183
- # immediately if no block is supplied.
242
+ # inifile.each_section do |section|
243
+ # puts section.inspect
244
+ # end
245
+ #
246
+ # Returns this IniFile.
184
247
  #
185
248
  def each_section
186
249
  return unless block_given?
@@ -188,77 +251,86 @@ class IniFile
188
251
  self
189
252
  end
190
253
 
254
+ # Public: Remove a section identified by name from the IniFile.
191
255
  #
192
- # call-seq:
193
- # delete_section( section )
256
+ # section - The section name as a String.
194
257
  #
195
- # Deletes the named _section_ from the INI file. Returns the
196
- # parameter / value pairs if the section exists in the INI file. Otherwise,
197
- # returns +nil+.
258
+ # Returns the deleted section Hash.
198
259
  #
199
260
  def delete_section( section )
200
261
  @ini.delete section.to_s
201
262
  end
202
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
203
270
  #
204
- # call-seq:
205
- # ini_file[section]
271
+ # inifile['global']
272
+ # #=> global section Hash
206
273
  #
207
- # Get the hash of parameter/value pairs for the given _section_. If the
208
- # _section_ hash does not exist it will be created.
274
+ # Returns the Hash of parameter/value pairs for this section.
209
275
  #
210
276
  def []( section )
211
277
  return nil if section.nil?
212
278
  @ini[section.to_s]
213
279
  end
214
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
215
287
  #
216
- # call-seq:
217
- # ini_file[section] = hash
288
+ # inifile['tenderloin'] = { 'gritty' => 'yes' }
289
+ # #=> { 'gritty' => 'yes' }
218
290
  #
219
- # Set the hash of parameter/value pairs for the given _section_.
291
+ # Returns the value Hash.
220
292
  #
221
293
  def []=( section, value )
222
294
  @ini[section.to_s] = value
223
295
  end
224
296
 
297
+ # Public: Create a Hash containing only those INI file sections whose names
298
+ # match the given regular expression.
225
299
  #
226
- # call-seq:
227
- # ini_file.match( /section/ ) #=> hash
300
+ # regex - The Regexp used to match section names.
228
301
  #
229
- # Return a hash containing only those sections that match the given regular
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
230
308
  # expression.
231
309
  #
232
310
  def match( regex )
233
311
  @ini.dup.delete_if { |section, _| section !~ regex }
234
312
  end
235
313
 
314
+ # Public: Check to see if the IniFile contains the section.
236
315
  #
237
- # call-seq:
238
- # has_section?( section )
316
+ # section - The section name as a String.
239
317
  #
240
- # Returns +true+ if the named _section_ exists in the INI file.
318
+ # Returns true if the section exists in the IniFile.
241
319
  #
242
320
  def has_section?( section )
243
321
  @ini.has_key? section.to_s
244
322
  end
245
323
 
246
- #
247
- # call-seq:
248
- # sections
249
- #
250
- # Returns an array of the section names.
324
+ # Returns an Array of section names contained in this IniFile.
251
325
  #
252
326
  def sections
253
327
  @ini.keys
254
328
  end
255
329
 
330
+ # Public: Freeze the state of this IniFile object. Any attempts to change
331
+ # the object will raise an error.
256
332
  #
257
- # call-seq:
258
- # freeze
259
- #
260
- # Freeze the state of the +IniFile+ object. Any attempts to change the
261
- # object will raise an error.
333
+ # Returns this IniFile.
262
334
  #
263
335
  def freeze
264
336
  super
@@ -267,12 +339,10 @@ class IniFile
267
339
  self
268
340
  end
269
341
 
342
+ # Public: Mark this IniFile as tainted -- this will traverse each section
343
+ # marking each as tainted.
270
344
  #
271
- # call-seq:
272
- # taint
273
- #
274
- # Marks the INI file as tainted -- this will traverse each section marking
275
- # each section as tainted as well.
345
+ # Returns this IniFile.
276
346
  #
277
347
  def taint
278
348
  super
@@ -281,14 +351,12 @@ class IniFile
281
351
  self
282
352
  end
283
353
 
284
- #
285
- # call-seq:
286
- # dup
287
- #
288
- # Produces a duplicate of this INI file. The duplicate is independent of the
289
- # original -- i.e. the duplicate can be modified without changing the
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
290
356
  # original. The tainted state of the original is copied to the duplicate.
291
357
  #
358
+ # Returns a new IniFile.
359
+ #
292
360
  def dup
293
361
  other = super
294
362
  other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
@@ -297,28 +365,26 @@ class IniFile
297
365
  other
298
366
  end
299
367
 
300
- #
301
- # call-seq:
302
- # clone
303
- #
304
- # Produces a duplicate of this INI file. The duplicate is independent of the
305
- # original -- i.e. the duplicate can be modified without changing the
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
306
370
  # original. The tainted state and the frozen state of the original is copied
307
371
  # to the duplicate.
308
372
  #
373
+ # Returns a new IniFile.
374
+ #
309
375
  def clone
310
376
  other = dup
311
377
  other.freeze if self.frozen?
312
378
  other
313
379
  end
314
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.
315
384
  #
316
- # call-seq:
317
- # eql?( other )
385
+ # other - The other IniFile.
318
386
  #
319
- # Returns +true+ if the _other_ object is equivalent to this INI file. For
320
- # two INI files to be equivalent, they must have the same sections with the
321
- # same parameter / value pairs in each section.
387
+ # Returns true if the INI files are equivalent and false if they differ.
322
388
  #
323
389
  def eql?( other )
324
390
  return true if equal? other
@@ -327,105 +393,126 @@ class IniFile
327
393
  end
328
394
  alias :== :eql?
329
395
 
330
- #
331
- # call-seq:
332
- # restore
333
- #
334
- # Restore data from the ini file. If the state of this object has been
335
- # changed but not yet saved, this will effectively undo the changes.
336
- #
337
- def restore
338
- parse
339
- end
340
396
 
341
397
  private
342
398
 
343
- # Parse the ini file contents.
399
+ # Parse the ini file contents. This will clear any values currently stored
400
+ # in the ini hash.
344
401
  #
345
- def parse
346
- return unless File.file?(@fn)
402
+ def parse!
403
+ return unless @content
347
404
 
348
- @_current_section = nil
349
- @_current_param = nil
350
- @_current_value = nil
405
+ string = ''
406
+ property = ''
351
407
 
352
- fd = (RUBY_VERSION >= '1.9' && @encoding) ?
353
- File.open(@fn, 'r', :encoding => @encoding) :
354
- File.open(@fn, 'r')
408
+ @ini.clear
409
+ @_line = nil
410
+ @_section = nil
355
411
 
356
- while line = fd.gets
357
- line = line.chomp
412
+ scanner = StringScanner.new(@content)
413
+ until scanner.eos?
358
414
 
359
- # we ignore comment lines and blank lines
360
- if line =~ @rgxp_comment
361
- finish_property
362
- next
363
- end
415
+ # keep track of the current line for error messages
416
+ @_line = scanner.check(%r/\A.*$/) if scanner.bol?
364
417
 
365
- # place values in the current section
366
- if line =~ @rgxp_section
367
- finish_property
368
- @_current_section = @ini[$1.strip]
369
- next
370
- end
418
+ # look for escaped special characters \# \" etc
419
+ if scanner.scan(%r/\\([\[\]#{@param}#{@comment}"])/)
420
+ string << scanner[1]
371
421
 
372
- parse_property line
422
+ # look for quoted strings
423
+ elsif scanner.scan(%r/"/)
424
+ quote = scanner.scan_until(/(?:\A|[^\\])"/)
425
+ parse_error('Unmatched quote') if quote.nil?
373
426
 
374
- end # while
427
+ quote.chomp!('"')
428
+ string << quote
375
429
 
376
- finish_property
377
- ensure
378
- fd.close if defined? fd and fd
379
- @_current_section = nil
380
- @_current_param = nil
381
- @_current_value = nil
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
+ else
442
+ parse_error
443
+ end
444
+
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.
451
+ else
452
+ tmp = scanner.scan_until(%r/([\n"#{@param}#{@comment}]|\\[\[\]#{@param}#{@comment}"])/m)
453
+ len = scanner[1].length
454
+ tmp.slice!(tmp.length - len, len)
455
+
456
+ scanner.pos = scanner.pos - len
457
+ string << tmp
458
+ end
459
+ end
460
+
461
+ process_property(property, string)
382
462
  end
383
463
 
384
- # Attempt to parse a property name and value from the given _line_. This
385
- # method takes into account multi-line values.
464
+ # Store the property / value pair in the currently active section. This
465
+ # method checks for continuation of the value to the next line.
466
+ #
467
+ # property - The property name as a String.
468
+ # value - The property value as a String.
386
469
  #
387
- def parse_property( line )
388
- p = v = nil
389
- split = line =~ @rgxp_param
470
+ # Returns nil.
471
+ #
472
+ def process_property( property, value )
473
+ value.chomp!
474
+ return if property.empty? and value.empty?
475
+ return if value.sub!(%r/\\\s*\z/, '')
390
476
 
391
- if split
392
- p = line.slice(0, split+1).strip
393
- v = line.slice(split+2, line.length).strip
394
- else
395
- v = line
396
- end
477
+ property.strip!
478
+ value.strip!
397
479
 
398
- if p.nil? and @_current_param.nil?
399
- raise Error, "could not parse line '#{line}'"
400
- end
480
+ parse_error if property.empty?
401
481
 
402
- @_current_param = p unless p.nil?
482
+ current_section[property.dup] = unescape_value(value.dup)
403
483
 
404
- if @_current_value then @_current_value << v
405
- else @_current_value = v end
484
+ property.slice!(0, property.length)
485
+ value.slice!(0, value.length)
406
486
 
407
- finish_property unless @_current_value.sub!(%r/\\\z/, "\n")
487
+ nil
408
488
  end
409
489
 
410
- # If there is a current property being parsed, finish this parse step by
411
- # storing the name and value in the current section and resetting for the
412
- # next parse step.
490
+ # Returns the current section Hash.
413
491
  #
414
- def finish_property
415
- return unless @_current_param
416
-
417
- @_current_section = @ini[@default] if @_current_section.nil?
418
- @_current_section[@_current_param] = unescape @_current_value
492
+ def current_section
493
+ @_section ||= @ini[@default]
494
+ end
419
495
 
420
- @_current_param = nil
421
- @_current_value = nil
496
+ # Raise a parse error using the given message and appending the current line
497
+ # being parsed.
498
+ #
499
+ # msg - The message String to use.
500
+ #
501
+ # Raises IniFile::Error
502
+ #
503
+ def parse_error( msg = 'Could not parse line' )
504
+ raise Error, "#{msg}: #{@_line.inspect}"
422
505
  end
423
506
 
424
507
  # Unescape special characters found in the value string. This will convert
425
508
  # escaped null, tab, carriage return, newline, and backslash into their
426
509
  # literal equivalents.
427
510
  #
428
- def unescape( value )
511
+ # value - The String value to unescape.
512
+ #
513
+ # Returns the unescaped value.
514
+ #
515
+ def unescape_value( value )
429
516
  return value unless @escape
430
517
 
431
518
  value = value.to_s
@@ -441,9 +528,13 @@ private
441
528
  value
442
529
  end
443
530
 
444
- # Escape special characters
531
+ # Escape special characters.
532
+ #
533
+ # value - The String value to escape.
534
+ #
535
+ # Returns the escaped value.
445
536
  #
446
- def escape( value )
537
+ def escape_value( value )
447
538
  return value unless @escape
448
539
 
449
540
  value = value.to_s.dup
@@ -3,3 +3,8 @@
3
3
  one = 1
4
4
  two = 2
5
5
 
6
+ [section_two] # you can comment here
7
+ one = 42 # and even here!
8
+ multi = 20 \ # and here, too
9
+ + 22 \= 42
10
+
@@ -6,7 +6,8 @@ foo = http://en.wikipedia.org/wiki/Foobar
6
6
  tabs = There is a tab\tcharacter in here somewhere
7
7
  carriage return = Who uses these anyways?\r
8
8
  newline = Trust newline!\nAlways there when you need him.\nSplittin' those lines.
9
- null = Who'd be silly enough to put\0 a null character in the middle of a string?\
9
+ null = Who'd be silly enough to put\0 a null character in the middle of a string? \
10
10
  Stroustrup would not approve!
11
11
  backslash = This string \\t contains \\n no \\r special \\0 characters!
12
+ quoted = "Escaping works\tinside quoted strings!"
12
13
 
@@ -4,7 +4,7 @@ two = 2
4
4
 
5
5
  [section_two]
6
6
  three = 3
7
- multi = multiline\
7
+ multi = multiline \
8
8
  support
9
9
 
10
10
  ; comments should be ignored
@@ -7,14 +7,17 @@ three = 3
7
7
 
8
8
  ; comments should be ignored
9
9
  [section_three]
10
- three = hello\
10
+ three = hello \
11
11
  multiline
12
12
  other = "stuff"
13
13
 
14
14
  [section_four]
15
- four = hello\
16
- multiple\
17
- multilines
15
+ four = hello \ # comments work here, too
16
+ multiple \ # and here !!!
17
+ multilines # and even here (OMG)
18
+ five = "multiple lines
19
+ inside of quotations
20
+ preserve everything"
18
21
 
19
22
  [empty_lines]
20
23
  empty =
@@ -1,7 +1,3 @@
1
- # Code Generated by ZenTest v. 3.3.0
2
- # classname: asrt / meth = ratio%
3
- # Rini::IniFile: 0 / 9 = 0.00%
4
-
5
1
  # encoding: UTF-8
6
2
 
7
3
  libpath = File.expand_path '../../lib', __FILE__
@@ -13,12 +9,12 @@ require 'test/unit'
13
9
  class TestIniFile < Test::Unit::TestCase
14
10
 
15
11
  def setup
16
- @ini_file = IniFile.new 'test/data/good.ini'
12
+ @ini_file = IniFile.new(:filename => 'test/data/good.ini')
17
13
  @contents = [
18
14
  ['section_one', 'one', '1'],
19
15
  ['section_one', 'two', '2'],
20
16
  ['section_two', 'three', '3'],
21
- ['section_two', 'multi', "multiline\nsupport"],
17
+ ['section_two', 'multi', "multiline support"],
22
18
  ['section three', 'four', '4'],
23
19
  ['section three', 'five', '5'],
24
20
  ['section three', 'six', '6'],
@@ -128,7 +124,7 @@ class TestIniFile < Test::Unit::TestCase
128
124
  assert_equal @contents, ary.sort
129
125
 
130
126
  ary = []
131
- IniFile.new('temp.ini').each {|*args| ary << args}
127
+ IniFile.new(:filename => 'temp.ini').each {|*args| ary << args}
132
128
  assert_equal [], ary
133
129
  end
134
130
 
@@ -144,7 +140,7 @@ class TestIniFile < Test::Unit::TestCase
144
140
  assert_equal expected, ary.sort
145
141
 
146
142
  ary = []
147
- IniFile.new('temp.ini').each_section {|section| ary << section}
143
+ IniFile.new(:filename => 'temp.ini').each_section {|section| ary << section}
148
144
  assert_equal [], ary
149
145
  end
150
146
 
@@ -175,7 +171,7 @@ class TestIniFile < Test::Unit::TestCase
175
171
  assert_equal true, @ini_file.has_section?(:section_two)
176
172
  assert_equal false, @ini_file.has_section?(nil)
177
173
 
178
- ini_file = IniFile.new 'temp.ini'
174
+ ini_file = IniFile.new(:filename => 'temp.ini')
179
175
  assert_equal false, ini_file.has_section?('section_one')
180
176
  assert_equal false, ini_file.has_section?('one')
181
177
  assert_equal false, ini_file.has_section?('two')
@@ -188,7 +184,7 @@ class TestIniFile < Test::Unit::TestCase
188
184
  }
189
185
  assert_equal expected, @ini_file[:section_one]
190
186
 
191
- expected = {'three' => '3', 'multi' => "multiline\nsupport"}
187
+ expected = {'three' => '3', 'multi' => "multiline support"}
192
188
  assert_equal expected, @ini_file['section_two']
193
189
 
194
190
  expected = {
@@ -210,7 +206,7 @@ class TestIniFile < Test::Unit::TestCase
210
206
  assert_nil @ini_file[nil]
211
207
 
212
208
  expected = {}
213
- ini_file = IniFile.new 'temp.ini'
209
+ ini_file = IniFile.new(:filename => 'temp.ini')
214
210
  assert_equal expected, ini_file['section_one']
215
211
  assert_equal expected, ini_file['one']
216
212
  assert_nil ini_file[nil]
@@ -220,7 +216,7 @@ class TestIniFile < Test::Unit::TestCase
220
216
  expected = {
221
217
  "section_two" =>
222
218
  {
223
- "three"=>"3", "multi"=>"multiline\nsupport"
219
+ "three"=>"3", "multi"=>"multiline support"
224
220
  },
225
221
  "section three" =>
226
222
  {
@@ -240,19 +236,33 @@ class TestIniFile < Test::Unit::TestCase
240
236
  # see if we can parse different style comments
241
237
  #assert_raise(IniFile::Error) {IniFile.new 'test/data/comment.ini'}
242
238
 
243
- ini_file = IniFile.new 'test/data/comment.ini', :comment => '#'
244
- assert_equal true, ini_file.has_section?('section_one')
239
+ ini_file = IniFile.new(:filename => 'test/data/comment.ini', :comment => '#')
240
+ assert ini_file.has_section?('section_one')
241
+ assert_equal '20 + 22 = 42', ini_file['section_two']['multi']
245
242
 
246
243
  # see if we can parse different style param separators
247
- assert_raise(IniFile::Error) {IniFile.new 'test/data/param.ini'}
244
+ assert_raise(IniFile::Error) {IniFile.new(:filename => 'test/data/param.ini')}
248
245
 
249
- ini_file = IniFile.new 'test/data/param.ini', :parameter => ':'
250
- assert_equal true, ini_file.has_section?('section_one')
246
+ ini_file = IniFile.new(:filename => 'test/data/param.ini', :parameter => ':')
247
+ assert ini_file.has_section?('section_one')
251
248
  assert_equal '1', ini_file['section_one']['one']
252
249
  assert_equal '2', ini_file['section_one']['two']
253
250
 
254
251
  # make sure we error out on files with bad lines
255
- assert_raise(IniFile::Error) {IniFile.new 'test/data/bad_1.ini'}
252
+ assert_raise(IniFile::Error) {IniFile.new :filename => 'test/data/bad_1.ini'}
253
+ end
254
+
255
+ def test_initialize_from_string
256
+ content = File.read('test/data/good.ini')
257
+
258
+ ini_file = IniFile.new(content, :comment => ';')
259
+ assert ini_file.has_section?('section_one')
260
+ assert ini_file.has_section?('section_two')
261
+ assert ini_file.has_section?('section three')
262
+ assert ini_file.has_section?('section_four')
263
+ assert ini_file.has_section?('section_five')
264
+
265
+ assert_equal '7 & 8', ini_file['section_five']['seven and eight']
256
266
  end
257
267
 
258
268
  def test_sections
@@ -263,7 +273,7 @@ class TestIniFile < Test::Unit::TestCase
263
273
 
264
274
  assert_equal expected, @ini_file.sections.sort
265
275
 
266
- ini_file = IniFile.new 'temp.ini'
276
+ ini_file = IniFile.new(:filename => 'temp.ini')
267
277
  assert_equal [], ini_file.sections
268
278
  end
269
279
 
@@ -285,18 +295,34 @@ class TestIniFile < Test::Unit::TestCase
285
295
  tmp = 'test/data/temp.ini'
286
296
  File.delete tmp if Kernel.test(?f, tmp)
287
297
 
288
- @ini_file.save tmp
298
+ @ini_file.save(:filename => tmp)
289
299
  assert_equal true, Kernel.test(?f, tmp)
290
300
 
291
301
  File.delete tmp if Kernel.test(?f, tmp)
292
302
 
293
- ini_file = IniFile.new tmp
303
+ ini_file = IniFile.new(:filename => tmp)
294
304
  ini_file.save
295
305
  assert_nil Kernel.test(?s, tmp)
296
306
 
297
307
  File.delete tmp if Kernel.test(?f, tmp)
298
308
  end
299
309
 
310
+ def test_read
311
+ assert @ini_file.has_section?('section_one')
312
+
313
+ @ini_file['section_one']['one'] = 42
314
+ @ini_file['section_one']['two'] = 42
315
+ assert_equal 42, @ini_file['section_one']['one']
316
+ assert_equal 42, @ini_file['section_one']['two']
317
+
318
+ @ini_file.read
319
+ assert_equal '1', @ini_file['section_one']['one']
320
+ assert_equal '2', @ini_file['section_one']['two']
321
+
322
+ @ini_file.read(:filename => 'test/data/mixed_comment.ini')
323
+ assert_equal false, @ini_file.has_section?('section_two')
324
+ end
325
+
300
326
  def test_modifies_current_keys
301
327
  ini = IniFile.load("test/data/tmp.ini")
302
328
  ini["section one"]["one"] = 17
@@ -306,7 +332,7 @@ class TestIniFile < Test::Unit::TestCase
306
332
  end
307
333
 
308
334
  def test_can_add_key_to_inifile
309
- ini_file = IniFile.new("test/data/tmp.ini")
335
+ ini_file = IniFile.new(:filename => "test/data/tmp.ini")
310
336
  ini_file["new_section"] = {}
311
337
  ini_file.save
312
338
 
@@ -314,7 +340,7 @@ class TestIniFile < Test::Unit::TestCase
314
340
  end
315
341
 
316
342
  def test_adds_correct_key_to_inifile
317
- ini_file = IniFile.new("test/data/tmp.ini")
343
+ ini_file = IniFile.new(:filename => "test/data/tmp.ini")
318
344
  ini_file["foo"] = {}
319
345
  ini_file.save
320
346
 
@@ -322,7 +348,7 @@ class TestIniFile < Test::Unit::TestCase
322
348
  end
323
349
 
324
350
  def test_assigns_values_to_inifile
325
- ini_file = IniFile.new("test/data/tmp.ini")
351
+ ini_file = IniFile.new(:filename => "test/data/tmp.ini")
326
352
  ini_file["foo"] = {
327
353
  :bar => "baz"
328
354
  }
@@ -331,7 +357,7 @@ class TestIniFile < Test::Unit::TestCase
331
357
  end
332
358
 
333
359
  def test_assigns_correct_values_to_inifile
334
- ini_file = IniFile.new("test/data/tmp.ini")
360
+ ini_file = IniFile.new(:filename => "test/data/tmp.ini")
335
361
  ini_file["foo"] = {
336
362
  :one => "two"
337
363
  }
@@ -340,7 +366,7 @@ class TestIniFile < Test::Unit::TestCase
340
366
  end
341
367
 
342
368
  def test_assignment_stringifies_key
343
- ini_file = IniFile.new("test/data/tmp.ini")
369
+ ini_file = IniFile.new(:filename => "test/data/tmp.ini")
344
370
  ini_file["foo"] = {:one => :two}
345
371
  ini_file[:foo] = {}
346
372
  assert_equal ini_file["foo"], {}
@@ -350,11 +376,12 @@ class TestIniFile < Test::Unit::TestCase
350
376
  ini_file = IniFile.load('test/data/multiline.ini')
351
377
 
352
378
  multiline = ini_file['section_three']
353
- expected = {"three" => "hello\nmultiline", "other" => '"stuff"'}
379
+ expected = {"three" => "hello multiline", "other" => "stuff"}
354
380
  assert_equal expected, multiline
355
381
 
356
382
  multiple = ini_file['section_four']
357
- expected = {"four" => "hello\nmultiple\nmultilines"}
383
+ expected = {"four" => "hello multiple multilines",
384
+ "five" => "multiple lines\ninside of quotations\npreserve everything" }
358
385
  assert_equal expected, multiple
359
386
 
360
387
  multiple = ini_file['empty_lines']
@@ -397,7 +424,7 @@ class TestIniFile < Test::Unit::TestCase
397
424
 
398
425
  if RUBY_VERSION >= '1.9'
399
426
  def test_parse_encoding
400
- ini_file = IniFile.new("test/data/browscap.ini", :encoding => 'ISO-8859-1')
427
+ ini_file = IniFile.new(:filename => "test/data/browscap.ini", :encoding => 'ISO-8859-1')
401
428
  assert_equal ini_file['www.substancia.com AutoHTTPAgent (ver *)']['Browser'], "Subst\xE2ncia".force_encoding('ISO-8859-1')
402
429
  end
403
430
 
@@ -405,10 +432,10 @@ class TestIniFile < Test::Unit::TestCase
405
432
  tmp = 'test/data/tmp.ini'
406
433
  File.delete tmp if Kernel.test(?f, tmp)
407
434
 
408
- @ini_file = IniFile.new(tmp, :encoding => 'UTF-8')
435
+ @ini_file = IniFile.new(:filename => tmp, :encoding => 'UTF-8')
409
436
  @ini_file['testutf-8'] = {"utf-8" => "appr\u20accier"}
410
437
 
411
- @ini_file.save tmp
438
+ @ini_file.save(:filename => tmp)
412
439
 
413
440
  test = File.open(tmp)
414
441
  assert_equal test.external_encoding.to_s, 'UTF-8'
@@ -422,8 +449,9 @@ class TestIniFile < Test::Unit::TestCase
422
449
  assert_equal %Q{There is a tab\tcharacter in here somewhere}, escaped['tabs']
423
450
  assert_equal %Q{Who uses these anyways?\r}, escaped['carriage return']
424
451
  assert_equal %Q{Trust newline!\nAlways there when you need him.\nSplittin' those lines.}, escaped['newline']
425
- assert_equal %Q{Who'd be silly enough to put\0 a null character in the middle of a string?\nStroustrup would not approve!}, escaped['null']
452
+ assert_equal %Q{Who'd be silly enough to put\0 a null character in the middle of a string? Stroustrup would not approve!}, escaped['null']
426
453
  assert_equal %q{This string \t contains \n no \r special \0 characters!}, escaped['backslash']
454
+ assert_equal %Q{Escaping works\tinside quoted strings!}, escaped['quoted']
427
455
  end
428
456
 
429
457
  def test_value_escaping_disabled
@@ -433,8 +461,9 @@ class TestIniFile < Test::Unit::TestCase
433
461
  assert_equal %q{There is a tab\tcharacter in here somewhere}, escaped['tabs']
434
462
  assert_equal %q{Who uses these anyways?\r}, escaped['carriage return']
435
463
  assert_equal %q{Trust newline!\nAlways there when you need him.\nSplittin' those lines.}, escaped['newline']
436
- assert_equal %Q{Who'd be silly enough to put\\0 a null character in the middle of a string?\nStroustrup would not approve!}, escaped['null']
464
+ assert_equal %Q{Who'd be silly enough to put\\0 a null character in the middle of a string? Stroustrup would not approve!}, escaped['null']
437
465
  assert_equal %q{This string \\\\t contains \\\\n no \\\\r special \\\\0 characters!}, escaped['backslash']
466
+ assert_equal %q{Escaping works\tinside quoted strings!}, escaped['quoted']
438
467
  end
439
468
 
440
469
  def test_global_section
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inifile
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,30 +9,40 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-28 00:00:00.000000000Z
12
+ date: 2012-08-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bones-git
16
- requirement: &2167250880 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
- version: 1.2.5
21
+ version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2167250880
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: bones
27
- requirement: &2167250400 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
31
36
  - !ruby/object:Gem::Version
32
- version: 3.7.2
37
+ version: 3.8.0
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *2167250400
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.8.0
36
46
  description: ! "Although made popular by Windows, INI files can be used on any system
37
47
  thanks\nto their flexibility. They allow a program to store configuration data,
38
48
  which\ncan then be easily parsed and changed. Two notable systems that use the INI\nformat
@@ -61,6 +71,7 @@ files:
61
71
  - History.txt
62
72
  - README.md
63
73
  - Rakefile
74
+ - a.rb
64
75
  - lib/inifile.rb
65
76
  - test/data/bad_1.ini
66
77
  - test/data/browscap.ini
@@ -95,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
95
106
  version: '0'
96
107
  requirements: []
97
108
  rubyforge_project: inifile
98
- rubygems_version: 1.8.11
109
+ rubygems_version: 1.8.24
99
110
  signing_key:
100
111
  specification_version: 3
101
112
  summary: INI file reader and writer