inifile 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.txt ADDED
@@ -0,0 +1,61 @@
1
+
2
+ = Ruby INI File Parser and Writer
3
+
4
+ == Introduction
5
+
6
+ An initialization file, or INI file, is a configuration file that contains
7
+ configuration data for Microsoft Windows based applications. Starting with
8
+ Windows 95, the INI file format was superseded but not entirely replaced by a
9
+ registry database in Microsoft operating systems.
10
+
11
+ Although made popular by Windows, INI files can be used on any system thanks
12
+ to their flexibility. They allow a program to store configuration data, which
13
+ can then be easily parsed and changed.
14
+
15
+ == File Format
16
+
17
+ A typical INI file might look like this:
18
+
19
+ [section1]
20
+
21
+ ; some comment on section1
22
+ var1 = foo
23
+ var2 = doodle
24
+
25
+ [section2]
26
+
27
+ ; another comment
28
+ var1 = baz
29
+ var2 = shoodle
30
+
31
+ === Format
32
+
33
+ This describes the elements of the INI file format:
34
+
35
+ * *Sections*: Section declarations start with '[' and end with ']' as in [section1] and [section2] above. And sections start with section declarations.
36
+ * *Parameters*: The "var1 = foo" above is an example of a parameter (also known as an item). Parameters are made up of a key ('var1'), equals sign ('='), and a value ('foo').
37
+ * *Comments*: All the lines starting with a ';' are assumed to be comments, and are ignored.
38
+
39
+ === Differences
40
+
41
+ The format of INI files is not well defined. Many programs interpret their
42
+ structure differently than the basic structure that was defined in the above
43
+ example. The following is a basic list of some of the differences:
44
+
45
+ * *Comments*: Programs like Samba accept either ';' or '#' as comments. Comments can be added after parameters with several formats.
46
+ * *Backslashes*: Adding a backslash '\' allows you to continue the value from one line to another. Some formats also allow various escapes with a '\', such as '\n' for newline.
47
+ * <b>Duplicate parameters</b>: Most of the time, you can't have two parameters with the same name in one section. Therefore one can say that parameters are local to the section. Although this behavior can vary between implementations, it is advisable to stick with this rule.
48
+ * <b>Duplicate sections</b>: If you have more than one section with the same name then the last section overrides the previous one. (Some implementations will merge them if they have different keys.)
49
+ * Some implementations allow ':' in place of '='.
50
+
51
+ == This Package
52
+
53
+ This package supports the standard INI file format described in the *Format*
54
+ section above. The following differences are also supported:
55
+
56
+ * *Comments*: The comment character can be specified when an +IniFile+ is created. The comment character must be the first non-whitespace character on a line.
57
+ * *Backslashes*: Backslashes are not supported by this package.
58
+ * <b>Duplicate parameters</b>: Duplicate parameters are allowed in a single section. The last parameter value is the one that will be stored in the +IniFile+.
59
+ * <b>Duplicate sections</b>: Duplicate sections will be merged. Parameters duplicated between to the two sections follow the duplicate parameters rule above.
60
+ * *Parameters*: The parameter separator character can be specified when an +IniFile+ is created.
61
+
data/lib/inifile.rb ADDED
@@ -0,0 +1,264 @@
1
+ # $Id: inifile.rb 57 2006-11-19 20:51:45Z tpease $
2
+
3
+ #
4
+ # This class represents the INI file and can be used to parse, modify,
5
+ # and write INI files.
6
+ #
7
+ class IniFile
8
+
9
+ # :stopdoc:
10
+ class Error < StandardError; end
11
+ # :startdoc:
12
+
13
+ #
14
+ # call-seq:
15
+ # IniFile.load( filename )
16
+ # IniFile.load( filename, options )
17
+ #
18
+ # Open the given _filename_ and load the contetns of the INI file.
19
+ # The following _options_ can be passed to this method:
20
+ #
21
+ # :comment => ';' The line comment character(s)
22
+ # :parameter => '=' The parameter / value separator
23
+ #
24
+ def self.load( filename, opts = {} )
25
+ new(filename, opts)
26
+ end
27
+
28
+ #
29
+ # call-seq:
30
+ # IniFile.new( filename )
31
+ # IniFile.new( filename, options )
32
+ #
33
+ # Create a new INI file using the given _filename_. If _filename_
34
+ # exists and is a regular file, then its contents will be parsed.
35
+ # The following _options_ can be passed to this method:
36
+ #
37
+ # :comment => ';' The line comment character(s)
38
+ # :parameter => '=' The parameter / value separator
39
+ #
40
+ def initialize( filename, opts = {} )
41
+ @fn = filename
42
+ @comment = opts[:comment] || ';'
43
+ @param = opts[:parameter] || '='
44
+ @ini = Hash.new {|h,k| h[k] = Hash.new}
45
+
46
+ @rgxp_comment = %r/\A\s*\z|\A\s*[#{@comment}]/
47
+ @rgxp_section = %r/\A\s*\[([^\]]+)\]/o
48
+ @rgxp_param = %r/\A([^#{@param}]+)#{@param}(.*)\z/
49
+
50
+ parse
51
+ end
52
+
53
+ #
54
+ # call-seq:
55
+ # write
56
+ # write( filename )
57
+ #
58
+ # Write the INI file contents to the filesystem. The given _filename_
59
+ # will be used to write the file. If _filename_ is not given, then the
60
+ # named used when constructing this object will be used.
61
+ #
62
+ def write( filename = nil )
63
+ @fn = filename unless filename.nil?
64
+
65
+ ::File.open(@fn, 'w') do |f|
66
+ @ini.each do |section,hash|
67
+ f.puts "[#{section}]"
68
+ hash.each {|param,val| f.puts "#{param} #{@param} #{val}"}
69
+ f.puts
70
+ end
71
+ end
72
+ self
73
+ end
74
+ alias :save :write
75
+
76
+ #
77
+ # call-seq:
78
+ # each {|section, parameter, value| block}
79
+ #
80
+ # Yield each _section_, _parameter_, _value_ in turn to the given
81
+ # _block_. The method returns immediately if no block is supplied.
82
+ #
83
+ def each
84
+ return unless block_given?
85
+ @ini.each do |section,hash|
86
+ hash.each do |param,val|
87
+ yield section, param, val
88
+ end
89
+ end
90
+ self
91
+ end
92
+
93
+ #
94
+ # call-seq:
95
+ # each_section {|section| block}
96
+ #
97
+ # Yield each _section_ in turn to the given _block_. The method returns
98
+ # immediately if no block is supplied.
99
+ #
100
+ def each_section
101
+ return unless block_given?
102
+ @ini.each_key {|section| yield section}
103
+ self
104
+ end
105
+
106
+ #
107
+ # call-seq:
108
+ # delete_section( section )
109
+ #
110
+ # Deletes the named _section_ from the INI file. Returns the
111
+ # parameter / value pairs if the section exists in the INI file. Otherwise,
112
+ # returns +nil+.
113
+ #
114
+ def delete_section( section )
115
+ @ini.delete section.to_s
116
+ end
117
+
118
+ #
119
+ # call-seq:
120
+ # ini_file[section]
121
+ #
122
+ # Get the hash of parameter/value pairs for the given _section_. If the
123
+ # _section_ hash does not exist it will be created.
124
+ #
125
+ def []( section )
126
+ return nil if section.nil?
127
+ @ini[section.to_s]
128
+ end
129
+
130
+ #
131
+ # call-seq:
132
+ # has_section?( section )
133
+ #
134
+ # Returns +true+ if the named _section_ exists in the INI file.
135
+ #
136
+ def has_section?( section )
137
+ @ini.has_key? section.to_s
138
+ end
139
+
140
+ #
141
+ # call-seq:
142
+ # sections
143
+ #
144
+ # Returns an array of the section names.
145
+ #
146
+ def sections
147
+ @ini.keys
148
+ end
149
+
150
+ #
151
+ # call-seq:
152
+ # freeze
153
+ #
154
+ # Freeze the state of the +IniFile+ object. Any attempts to change the
155
+ # object will raise an error.
156
+ #
157
+ def freeze
158
+ super
159
+ @ini.each_value {|h| h.freeze}
160
+ @ini.freeze
161
+ self
162
+ end
163
+
164
+ #
165
+ # call-seq:
166
+ # taint
167
+ #
168
+ # Marks the INI file as tainted -- this will traverse each section marking
169
+ # each section as tainted as well.
170
+ #
171
+ def taint
172
+ super
173
+ @ini.each_value {|h| h.taint}
174
+ @ini.taint
175
+ self
176
+ end
177
+
178
+ #
179
+ # call-seq:
180
+ # dup
181
+ #
182
+ # Produces a duplicate of this INI file. The duplicate is independent of the
183
+ # original -- i.e. the duplicate can be modified without changing the
184
+ # orgiinal. The tainted state of the original is copied to the duplicate.
185
+ #
186
+ def dup
187
+ other = super
188
+ other.instance_variable_set(:@ini, Hash.new {|h,k| h[k] = Hash.new})
189
+ @ini.each_pair {|s,h| other[s].merge! h}
190
+ other.taint if self.tainted?
191
+ other
192
+ end
193
+
194
+ #
195
+ # call-seq:
196
+ # clone
197
+ #
198
+ # Produces a duplicate of this INI file. The duplicate is independent of the
199
+ # original -- i.e. the duplicate can be modified without changing the
200
+ # orgiinal. The tainted state and the frozen state of the original is copied
201
+ # to the duplicate.
202
+ #
203
+ def clone
204
+ other = dup
205
+ other.freeze if self.frozen?
206
+ other
207
+ end
208
+
209
+ #
210
+ # call-seq:
211
+ # eql?( other )
212
+ #
213
+ # Returns +true+ if the _other_ object is equivalent to this INI file. For
214
+ # two INI files to be equivalent, they must have the same sections with the
215
+ # same parameter / value pairs in each section.
216
+ #
217
+ def eql?( other )
218
+ return true if equal? other
219
+ return false unless other.instance_of? self.class
220
+ @ini == other.instance_variable_get(:@ini)
221
+ end
222
+ alias :== :eql?
223
+
224
+
225
+ private
226
+ #
227
+ # call-seq
228
+ # parse
229
+ #
230
+ # Parse the ini file contents.
231
+ #
232
+ def parse
233
+ return unless ::Kernel.test ?f, @fn
234
+ section = nil
235
+
236
+ ::File.open(@fn, 'r') do |f|
237
+ while line = f.gets
238
+ line = line.chomp
239
+
240
+ case line
241
+ # ignore blank lines and comment lines
242
+ when @rgxp_comment: next
243
+
244
+ # this is a section declaration
245
+ when @rgxp_section: section = @ini[$1.strip]
246
+
247
+ # otherwise we have a parameter
248
+ when @rgxp_param
249
+ begin
250
+ section[$1.strip] = $2.strip
251
+ rescue NoMethodError
252
+ raise Error, "parameter encountered before first section"
253
+ end
254
+
255
+ else
256
+ raise Error, "could not parse line '#{line}"
257
+ end
258
+ end # while
259
+ end # File.open
260
+ end
261
+
262
+ end # class IniFile
263
+
264
+ # EOF
@@ -0,0 +1,6 @@
1
+ ; having a paramater / value pair outside a section is an error
2
+ one = 1
3
+
4
+ [section_one]
5
+ one = 1
6
+ two = 2
@@ -0,0 +1,6 @@
1
+ [section_one]
2
+ one = 1
3
+ two = 2
4
+
5
+ ; the following is not a valid line
6
+ invalid line
@@ -0,0 +1,5 @@
1
+ # comments should be ignored
2
+ [section_one]
3
+ one = 1
4
+ two = 2
5
+
@@ -0,0 +1,17 @@
1
+ [section_one]
2
+ one = 1
3
+ two = 2
4
+
5
+ [section_two]
6
+ three = 3
7
+
8
+ ; comments should be ignored
9
+ [section three]
10
+ four =4
11
+ five=5
12
+ six =6
13
+
14
+ [section_four]
15
+ [section_five]
16
+ seven and eight= 7 & 8
17
+
@@ -0,0 +1,7 @@
1
+ # comments should be ignored
2
+ ; multiple comments characeters are supported
3
+ ; (I'm lookin' at you, samba)
4
+ [section_one]
5
+ one = 1
6
+ two = 2
7
+
@@ -0,0 +1,5 @@
1
+ ; comments should be ignored
2
+ [section_one]
3
+ one : 1
4
+ two:2
5
+
@@ -0,0 +1,277 @@
1
+ # Code Generated by ZenTest v. 3.3.0
2
+ # classname: asrt / meth = ratio%
3
+ # Rini::IniFile: 0 / 9 = 0.00%
4
+
5
+ begin
6
+ require 'inifile'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ require 'inifile'
10
+ end
11
+
12
+ begin; require 'turn'; rescue LoadError; end
13
+ require 'test/unit' unless defined? $ZENTEST and $ZENTEST
14
+
15
+ class TestIniFile < Test::Unit::TestCase
16
+
17
+ def setup
18
+ @ini_file = ::IniFile.new 'test/data/good.ini'
19
+ @contents = [
20
+ ['section_one', 'one', '1'],
21
+ ['section_one', 'two', '2'],
22
+ ['section_two', 'three', '3'],
23
+ ['section three', 'four', '4'],
24
+ ['section three', 'five', '5'],
25
+ ['section three', 'six', '6'],
26
+ ['section_five', 'seven and eight', '7 & 8']
27
+ ].sort
28
+ end
29
+
30
+ def test_class_load
31
+ ini_file = ::IniFile.load 'test/data/good.ini'
32
+ assert_instance_of ::IniFile, ini_file
33
+
34
+ # see if we can parse different style comments
35
+ assert_raise(::IniFile::Error) {::IniFile.load 'test/data/comment.ini'}
36
+
37
+ ini_file = ::IniFile.load 'test/data/comment.ini', :comment => '#'
38
+ assert_instance_of ::IniFile, ini_file
39
+
40
+ # see if we can parse mixed style comments
41
+ assert_raise(::IniFile::Error) {::IniFile.load 'test/data/mixed_comment.ini'}
42
+
43
+ ini_file = ::IniFile.load 'test/data/mixed_comment.ini', :comment => ';#'
44
+ assert_instance_of ::IniFile, ini_file
45
+
46
+ # see if we can parse different style param separators
47
+ assert_raise(::IniFile::Error) {::IniFile.load 'test/data/param.ini'}
48
+
49
+ ini_file = ::IniFile.load 'test/data/param.ini', :parameter => ':'
50
+ assert_instance_of ::IniFile, ini_file
51
+
52
+ # make sure we error out on files with bad lines
53
+ assert_raise(::IniFile::Error) {::IniFile.load 'test/data/bad_1.ini'}
54
+ assert_raise(::IniFile::Error) {::IniFile.load 'test/data/bad_2.ini'}
55
+ end
56
+
57
+ def test_clone
58
+ clone = @ini_file.clone
59
+ assert_equal @ini_file, clone
60
+ assert !clone.tainted?
61
+ assert !clone.frozen?
62
+
63
+ # the clone should be completely independent of the original
64
+ clone['new_section']['one'] = 1
65
+ assert_not_equal @ini_file, clone
66
+
67
+ # the tainted state is copied to clones
68
+ @ini_file.taint
69
+ assert @ini_file.tainted?
70
+
71
+ clone = @ini_file.clone
72
+ assert clone.tainted?
73
+
74
+ # the frozen state is also copied to clones
75
+ @ini_file.freeze
76
+ assert @ini_file.frozen?
77
+
78
+ clone = @ini_file.clone
79
+ assert clone.tainted?
80
+ assert clone.frozen?
81
+ end
82
+
83
+ def test_delete_section
84
+ assert_nil @ini_file.delete_section('section_nil')
85
+
86
+ h = {'one' => '1', 'two' => '2'}
87
+ assert_equal true, @ini_file.has_section?('section_one')
88
+ assert_equal h, @ini_file.delete_section('section_one')
89
+ assert_equal false, @ini_file.has_section?('section_one')
90
+ assert_nil @ini_file.delete_section('section_one')
91
+ end
92
+
93
+ def test_dup
94
+ dup = @ini_file.dup
95
+ assert_equal @ini_file, dup
96
+ assert !dup.tainted?
97
+ assert !dup.frozen?
98
+
99
+ # the duplicate should be completely independent of the original
100
+ dup['new_section']['one'] = 1
101
+ assert_not_equal @ini_file, dup
102
+
103
+ # the tainted state is copied to duplicates
104
+ @ini_file.taint
105
+ assert @ini_file.tainted?
106
+
107
+ dup = @ini_file.dup
108
+ assert dup.tainted?
109
+
110
+ # the frozen state, however, is not
111
+ @ini_file.freeze
112
+ assert @ini_file.frozen?
113
+
114
+ dup = @ini_file.dup
115
+ assert dup.tainted?
116
+ assert !dup.frozen?
117
+ end
118
+
119
+ def test_each
120
+ ary = []
121
+ @ini_file.each {|*args| ary << args}
122
+
123
+ assert_equal @contents, ary.sort
124
+
125
+ ary = []
126
+ ::IniFile.new('temp.ini').each {|*args| ary << args}
127
+ assert_equal [], ary
128
+ end
129
+
130
+ def test_each_section
131
+ expected = [
132
+ 'section_one', 'section_two', 'section three',
133
+ 'section_four', 'section_five'
134
+ ].sort
135
+
136
+ ary = []
137
+ @ini_file.each_section {|section| ary << section}
138
+
139
+ assert_equal expected, ary.sort
140
+
141
+ ary = []
142
+ ::IniFile.new('temp.ini').each_section {|section| ary << section}
143
+ assert_equal [], ary
144
+ end
145
+
146
+ def test_eql_eh
147
+ assert @ini_file.eql?(@ini_file)
148
+ assert @ini_file.eql?(@ini_file.clone)
149
+ assert !@ini_file.eql?('string')
150
+ assert !@ini_file.eql?(IniFile.new(''))
151
+ end
152
+
153
+ def test_freeze
154
+ assert_equal false, @ini_file.frozen?
155
+ @ini_file.each_section do |s|
156
+ assert_equal false, @ini_file[s].frozen?
157
+ end
158
+
159
+ @ini_file.freeze
160
+
161
+ assert_equal true, @ini_file.frozen?
162
+ @ini_file.each_section do |s|
163
+ assert_equal true, @ini_file[s].frozen?
164
+ end
165
+ end
166
+
167
+ def test_has_section_eh
168
+ assert_equal true, @ini_file.has_section?('section_one')
169
+ assert_equal false, @ini_file.has_section?('section_ten')
170
+ assert_equal true, @ini_file.has_section?(:section_two)
171
+ assert_equal false, @ini_file.has_section?(nil)
172
+
173
+ ini_file = ::IniFile.new 'temp.ini'
174
+ assert_equal false, ini_file.has_section?('section_one')
175
+ assert_equal false, ini_file.has_section?('one')
176
+ assert_equal false, ini_file.has_section?('two')
177
+ end
178
+
179
+ def test_index
180
+ expected = {
181
+ 'one' => '1',
182
+ 'two' => '2'
183
+ }
184
+ assert_equal expected, @ini_file[:section_one]
185
+
186
+ expected = {'three' => '3'}
187
+ assert_equal expected, @ini_file['section_two']
188
+
189
+ expected = {
190
+ 'four' => '4',
191
+ 'five' => '5',
192
+ 'six' => '6',
193
+ }
194
+ assert_equal expected, @ini_file['section three']
195
+
196
+ expected = {}
197
+ assert_equal expected, @ini_file['section_four']
198
+
199
+ expected = {'seven and eight' => '7 & 8'}
200
+ assert_equal expected, @ini_file['section_five']
201
+
202
+ expected = {}
203
+ assert_equal expected, @ini_file['section_six']
204
+
205
+ assert_nil @ini_file[nil]
206
+
207
+ expected = {}
208
+ ini_file = ::IniFile.new 'temp.ini'
209
+ assert_equal expected, ini_file['section_one']
210
+ assert_equal expected, ini_file['one']
211
+ assert_nil ini_file[nil]
212
+ end
213
+
214
+ def test_initialize
215
+ # see if we can parse different style comments
216
+ assert_raise(::IniFile::Error) {::IniFile.new 'test/data/comment.ini'}
217
+
218
+ ini_file = ::IniFile.new 'test/data/comment.ini', :comment => '#'
219
+ assert_equal true, ini_file.has_section?('section_one')
220
+
221
+ # see if we can parse different style param separators
222
+ assert_raise(::IniFile::Error) {::IniFile.new 'test/data/param.ini'}
223
+
224
+ ini_file = ::IniFile.new 'test/data/param.ini', :parameter => ':'
225
+ assert_equal true, ini_file.has_section?('section_one')
226
+ assert_equal '1', ini_file['section_one']['one']
227
+ assert_equal '2', ini_file['section_one']['two']
228
+
229
+ # make sure we error out on files with bad lines
230
+ assert_raise(::IniFile::Error) {::IniFile.new 'test/data/bad_1.ini'}
231
+ assert_raise(::IniFile::Error) {::IniFile.new 'test/data/bad_2.ini'}
232
+ end
233
+
234
+ def test_sections
235
+ expected = [
236
+ 'section_one', 'section_two', 'section three',
237
+ 'section_four', 'section_five'
238
+ ].sort
239
+
240
+ assert_equal expected, @ini_file.sections.sort
241
+
242
+ ini_file = ::IniFile.new 'temp.ini'
243
+ assert_equal [], ini_file.sections
244
+ end
245
+
246
+ def test_taint
247
+ assert_equal false, @ini_file.tainted?
248
+ @ini_file.each_section do |s|
249
+ assert_equal false, @ini_file[s].tainted?
250
+ end
251
+
252
+ @ini_file.taint
253
+
254
+ assert_equal true, @ini_file.tainted?
255
+ @ini_file.each_section do |s|
256
+ assert_equal true, @ini_file[s].tainted?
257
+ end
258
+ end
259
+
260
+ def test_write
261
+ tmp = 'test/data/temp.ini'
262
+ ::File.delete tmp if ::Kernel.test(?f, tmp)
263
+
264
+ @ini_file.save tmp
265
+ assert_equal true, ::Kernel.test(?f, tmp)
266
+
267
+ ::File.delete tmp if ::Kernel.test(?f, tmp)
268
+
269
+ ini_file = ::IniFile.new tmp
270
+ ini_file.save
271
+ assert_nil ::Kernel.test(?s, tmp)
272
+
273
+ ::File.delete tmp if ::Kernel.test(?f, tmp)
274
+ end
275
+ end
276
+
277
+ # EOF
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: inifile
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-11-26 00:00:00 -07:00
8
+ summary: INI file reader and writer
9
+ require_paths:
10
+ - lib
11
+ - test
12
+ email: tim.pease@gmail.com
13
+ homepage:
14
+ rubyforge_project: codeforpeople.com
15
+ description: Although made popular by Windows, INI files can be used on any system thanks to their flexibility. They allow a program to store configuration data, which can then be easily parsed and changed. Two notable systems that use the INI format are Samba and Trac. This is a native Ruby package for reading and writing INI files.
16
+ autorequire:
17
+ default_executable:
18
+ bindir: bin
19
+ has_rdoc: true
20
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
21
+ requirements:
22
+ - - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ post_install_message:
30
+ authors:
31
+ - Tim Pease
32
+ files:
33
+ - README.txt
34
+ - lib/inifile.rb
35
+ - test/data/bad_1.ini
36
+ - test/data/bad_2.ini
37
+ - test/data/comment.ini
38
+ - test/data/good.ini
39
+ - test/data/mixed_comment.ini
40
+ - test/data/param.ini
41
+ - test/test_inifile.rb
42
+ test_files:
43
+ - test/test_inifile.rb
44
+ rdoc_options: []
45
+
46
+ extra_rdoc_files: []
47
+
48
+ executables: []
49
+
50
+ extensions: []
51
+
52
+ requirements: []
53
+
54
+ dependencies: []
55
+