ini_file 0.0.1

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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Jerry D'Antonio
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1 @@
1
+ README for ini_file
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "rubygems"
2
+ require "bundler/gem_tasks"
3
+ require "rspec"
4
+ require "rspec/core/rake_task"
5
+
6
+ $:.unshift 'tasks'
7
+ Dir.glob('tasks/**/*.rake').each do|rakefile|
8
+ load rakefile
9
+ end
10
+
11
+ Bundler::GemHelper.install_tasks
12
+ RSpec::Core::RakeTask.new(:spec) do |t|
13
+ t.rspec_opts = '-fd --color'
14
+ end
15
+
16
+ #task :default => [:default_task]
data/lib/ini_file.rb ADDED
@@ -0,0 +1,32 @@
1
+ $:.push File.join(File.dirname(__FILE__))
2
+
3
+ require "ini_file/content"
4
+ require "ini_file/parser"
5
+ require "ini_file/version"
6
+
7
+ # http://en.wikipedia.org/wiki/INI_file
8
+
9
+ module IniFile
10
+ extend self
11
+
12
+ def load(path, safe = false)
13
+ contents = File.open(File.expand_path(path), 'r') {|f| f.read }
14
+ return Content.parse(contents)
15
+ rescue
16
+ if safe
17
+ return nil
18
+ else
19
+ raise $!
20
+ end
21
+ end
22
+
23
+ def parse(contents, safe = false)
24
+ return Parser.parse(contents)
25
+ rescue
26
+ if safe
27
+ return nil
28
+ else
29
+ raise $!
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,76 @@
1
+ require 'ini_file/parser'
2
+
3
+ module IniFile
4
+
5
+ class Content
6
+
7
+ attr_reader :name
8
+
9
+ def empty?
10
+ return @content.empty?
11
+ end
12
+
13
+ def [](key)
14
+ if @content[key].is_a? Hash
15
+ return Content.from_hash(key, @content[key])
16
+ else
17
+ return @content[key]
18
+ end
19
+ end
20
+
21
+ def to_hash
22
+ return Marshal.load( Marshal.dump(@content) )
23
+ end
24
+
25
+ def each(&block)
26
+ @content.each do |key, value|
27
+ yield(key, value) unless value.is_a? Hash
28
+ end
29
+ end
30
+
31
+ def each_section(&block)
32
+ @content.each do |key, value|
33
+ yield(Content.from_hash(key, value)) if value.is_a? Hash
34
+ end
35
+ end
36
+
37
+ def method_missing(method, *args, &block)
38
+
39
+ key = method.to_s.downcase.to_sym
40
+
41
+ if @content.has_key?(key)
42
+ if args.count > 0
43
+ raise ArgumentError.new("wrong number of arguments(#{args.count} for 0)")
44
+ elsif block_given?
45
+ raise ArgumentError.new("block syntax not supported")
46
+ elsif @content[key].is_a? Hash
47
+ return Content.from_hash(key, @content[key])
48
+ else
49
+ return @content[key]
50
+ end
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def self.parse(contents)
57
+ content = Content.new
58
+ content.instance_variable_set(:@content, Parser.parse(contents))
59
+ return content
60
+ end
61
+
62
+ private
63
+
64
+ def initialize(name = nil)
65
+ @name = name
66
+ @content = {}
67
+ end
68
+
69
+ def self.from_hash(name, contents)
70
+ content = Content.new(name)
71
+ content.instance_variable_set(:@content, contents)
72
+ return content
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,93 @@
1
+ module IniFile
2
+
3
+ IniFormatError = Class.new(StandardError)
4
+
5
+ module Parser
6
+ extend self
7
+
8
+ def parse(contents)
9
+ raise ArgumentError.new('contents cannot be nil') if contents.nil?
10
+ raise ArgumentError.new('contents must be a string') unless contents.is_a? String
11
+ raise ArgumentError.new('contents cannot be blank') if contents.strip.empty?
12
+
13
+ root = {}
14
+
15
+ section_pattern = /^\s*\[([^\]]+)\]\s*$/
16
+ property_pattern = /^\s*([^;#:=\r\n]++)\s*[:=](.*)$/
17
+ comment_pattern = /^\s*([;#].*)$/
18
+
19
+ pattern = /#{section_pattern}|#{property_pattern}|#{comment_pattern}|^(.*)$/
20
+
21
+ current = root
22
+
23
+ contents.scan(pattern) do |section, key, value, comment, garbage|
24
+
25
+ if section
26
+ if section.scan(/[\.\\\/,]/).uniq.size > 1
27
+ raise IniFormatError.new("Section hierarchy names must use one delimiter: #{section}")
28
+ end
29
+ sections = section.strip.split(/\s*[\.\\\/,\s]{1}\s*/)
30
+ if sections.empty?
31
+ raise IniFormatError.new("Section names cannot be blank: #{section}")
32
+ end
33
+
34
+ current = root
35
+
36
+ sections.each do |section|
37
+ section = $1 || $2 if section =~ /^"([^"]+)"$|^'([^']+)'$/
38
+ section = section.strip.downcase.to_sym
39
+ if section.empty?
40
+ raise IniFormatError.new("Section names cannot be blank: #{section}")
41
+ elsif section =~ /[\W]+/
42
+ raise IniFormatError.new("Section names cannot contain punctuation: #{section}")
43
+ elsif current.has_key?(section) && !current[section].is_a?(Hash)
44
+ raise IniFormatError.new("Section name matches existing property name: #{section}")
45
+ else
46
+ current[section] = {} unless current.has_key?(section)
47
+ current = current[section]
48
+ end
49
+ end
50
+
51
+ elsif key && value
52
+ key = key.strip.downcase.gsub(/-/, '_').to_sym
53
+ value = value.strip
54
+ if key.empty?
55
+ raise IniFormatError.new("Property names cannot be blank: #{key}")
56
+ elsif current[key]
57
+ raise IniFormatError.new("Duplicate keys are not allowed: #{key}")
58
+ elsif key =~ /[\W\s]+/
59
+ raise IniFormatError.new("Property names cannot contain spaces or punctuation: #{key}")
60
+ end
61
+ value = value.gsub(/\s+/, ' ') unless value =~ /^"(.+)"$|^'(.+)'$/
62
+ if value =~ /^\d+$/
63
+ value = value.to_i
64
+ elsif value =~ /^\d*\.\d+$/
65
+ value = value.to_f
66
+ else
67
+ if value =~ /^"(.+)"$|^'(.+)'$/
68
+ value = $1 || $2
69
+ else
70
+ value = value.gsub(/\s*[^\\]{1}[#;].*$/, '')
71
+ value = value.gsub(/\\([#;])/, '\1')
72
+ end
73
+ value = value.gsub(/\\[0abtrn\\]/) {|s| eval('"%s"' % "#{s}") }
74
+ value = value.gsub(/\\[ux](\d{4})/) {|s| eval('"%s"' % "\\u#{$1}") }
75
+ end
76
+ current[key] = value
77
+
78
+ elsif comment
79
+ # do nothing
80
+ elsif garbage
81
+ raise IniFormatError.new("Unrecognized pattern: #{garbage}")
82
+ else
83
+ # possibly throw exceptions?
84
+ end
85
+ end
86
+
87
+ root.freeze
88
+ return root
89
+ end
90
+
91
+ end
92
+
93
+ end
@@ -0,0 +1,3 @@
1
+ module IniFile
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,335 @@
1
+ require 'spec_helper'
2
+
3
+ module IniFile
4
+
5
+ describe Content do
6
+
7
+ let(:contents) do
8
+ <<-DATA
9
+ ; this line is a comment
10
+ KEY1 = value1
11
+ key2 = "The second value"
12
+ the-third-key = 3
13
+
14
+ [section_1]
15
+ key3: value3
16
+
17
+ [section_1.sub_1]
18
+ sub_1_key: 'This is the subsection key'
19
+
20
+ [section_1/sub_1/sub_2]
21
+ key: value
22
+
23
+ [section_1 \ sub_1 \ sub_2 \ sub_3]
24
+ key: value
25
+
26
+ [section_1,sub_1,sub_2,sub_3,sub_4]
27
+ key: value
28
+ DATA
29
+ end
30
+
31
+ context '#new' do
32
+
33
+ it 'calls Parser.parse with the contents' do
34
+ ini_contents = 'key=value'
35
+ Parser.should_receive(:parse).with(ini_contents)
36
+ Content.parse(ini_contents)
37
+ end
38
+
39
+ end
40
+
41
+ context 'accessor methods' do
42
+
43
+ context 'attribute reader' do
44
+
45
+ subject { Content.parse(contents) }
46
+
47
+ it 'throws an ArgumentError if parameters are passed' do
48
+ lambda { subject.key1(nil) }.should raise_error(ArgumentError)
49
+ end
50
+
51
+ it 'throws an ArgumentError if a block is provided' do
52
+ lambda { subject.key1{|x| nil} }.should raise_error(ArgumentError)
53
+ end
54
+
55
+ it 'converts global property keys to attributes at the root' do
56
+ subject.key1.should_not be_nil
57
+ end
58
+
59
+ it 'converts global property values to string values at the root' do
60
+ subject.key1.should eq 'value1'
61
+ end
62
+
63
+ it 'ignores case of property keys' do
64
+ subject.KEY1.should eq 'value1'
65
+ end
66
+
67
+ it 'replaces dashes in the key name with underscores' do
68
+ subject.the_third_key.should eq 3
69
+ end
70
+
71
+ it 'converts section names to attributes at the root' do
72
+ subject.section_1.should_not be_nil
73
+ end
74
+
75
+ it 'converts sections to nested attributes under the root' do
76
+ subject.section_1[:key3].should eq 'value3'
77
+ end
78
+
79
+ it 'converts section property keys to attributes under the section' do
80
+ subject.section_1.key3.should eq 'value3'
81
+ end
82
+
83
+ it 'converts section property values to string values under the section' do
84
+ section = subject.section_1
85
+ value = subject.section_1.key3
86
+ subject.section_1.key3.should eq 'value3'
87
+ end
88
+
89
+ it 'converts section hierarchies to nested section hierarchies' do
90
+ subject.section_1.sub_1.sub_2.sub_3.sub_4.should_not be_nil
91
+ end
92
+
93
+ it 'ignores case of section names' do
94
+ subject.SECTION_1[:key3].should eq 'value3'
95
+ end
96
+
97
+ it 'ignores case of section hierarchies' do
98
+ subject.section_1.SUB_1.should_not be_nil
99
+ end
100
+
101
+ it 'ignores case of property names in section hierarchies' do
102
+ subject.section_1.sub_1.SUB_1_KEY.should eq 'This is the subsection key'
103
+ end
104
+
105
+ it 'throws NoMethodError when accessing a non-existing property' do
106
+ lambda { subject.bogus }.should raise_error(NoMethodError)
107
+ end
108
+
109
+ it 'throws NoMethodError when accessing a non-existing section' do
110
+ lambda { subject.bogus[:key1] }.should raise_error(NoMethodError)
111
+ end
112
+
113
+ it 'throws NoMethodError when accessing a non-existing subsection' do
114
+ lambda { subject.section_1.bogus }.should raise_error(NoMethodError)
115
+ end
116
+
117
+ it 'throws NoMethodError when accessing a non-existing subsection property' do
118
+ lambda { subject.section_1.sub.bogus }.should raise_error(NoMethodError)
119
+ end
120
+
121
+ end
122
+
123
+ context '#[]' do
124
+
125
+ subject { Content.parse(contents) }
126
+
127
+ it 'converts global property keys to symbols at the root' do
128
+ subject[:key1].should_not be_nil
129
+ subject[:key2].should_not be_nil
130
+ end
131
+
132
+ it 'converts global property values to string values at the root' do
133
+ subject[:key1].should eq 'value1'
134
+ subject[:key2].should eq 'The second value'
135
+ end
136
+
137
+ it 'converts section names to symbols at the root' do
138
+ subject[:section_1].should_not be_nil
139
+ end
140
+
141
+ it 'converts sections to nodes' do
142
+ subject[:section_1].should be_kind_of IniFile::Content
143
+ end
144
+
145
+ it 'converts section property keys to symbols in the section hash' do
146
+ subject[:section_1][:key3].should_not be_nil
147
+ end
148
+
149
+ it 'converts section property values to string values in the section hash' do
150
+ subject[:section_1][:key3].should eq 'value3'
151
+ end
152
+
153
+ it 'converts section hierarchies to node hierarchies' do
154
+ subject[:section_1].should be_kind_of IniFile::Content
155
+ subject[:section_1][:sub_1].should be_kind_of IniFile::Content
156
+ subject[:section_1][:sub_1][:sub_2].should be_kind_of IniFile::Content
157
+ subject[:section_1][:sub_1][:sub_2][:sub_3].should be_kind_of IniFile::Content
158
+ subject[:section_1][:sub_1][:sub_2][:sub_3][:sub_4].should be_kind_of IniFile::Content
159
+ end
160
+
161
+ end
162
+
163
+ context '#to_hash' do
164
+
165
+ subject { Content.parse(contents).to_hash }
166
+
167
+ it 'converts global property keys to symbols at the root' do
168
+ subject[:key1].should_not be_nil
169
+ subject[:key2].should_not be_nil
170
+ end
171
+
172
+ it 'converts global property values to string values at the root' do
173
+ subject[:key1].should eq 'value1'
174
+ subject[:key2].should eq 'The second value'
175
+ end
176
+
177
+ it 'converts section names to symbols at the root' do
178
+ subject[:section_1].should_not be_nil
179
+ end
180
+
181
+ it 'converts sections to hash values at the root' do
182
+ subject[:section_1].should be_a Hash
183
+ end
184
+
185
+ it 'converts section property keys to symbols in the section hash' do
186
+ subject[:section_1][:key3].should_not be_nil
187
+ end
188
+
189
+ it 'converts section property values to string values in the section hash' do
190
+ subject[:section_1][:key3].should eq 'value3'
191
+ end
192
+
193
+ it 'converts section hierarchies to hash hierarchies' do
194
+ subject[:section_1][:sub_1][:sub_2][:sub_3][:sub_4].should be_a Hash
195
+ end
196
+
197
+ end
198
+
199
+ end
200
+
201
+ context 'iterators' do
202
+
203
+ let(:contents) do
204
+ <<-DATA
205
+ ; this line is a comment
206
+ KEY1 = value1
207
+ key2 = "The second value"
208
+
209
+ [section_1]
210
+ key1-1: value1
211
+ key1-2: value2
212
+ key1-3: value3
213
+ key1-4: value4
214
+
215
+ [section_2]
216
+ key2-1: value1
217
+ key2-2: value2
218
+ key2-3: value3
219
+ key2-4: value4
220
+
221
+ [section_1.sub_1]
222
+ key1-1-1: value1
223
+ key1-1-2: value2
224
+ key1-1-3: value3
225
+ key1-1-4: value4
226
+
227
+ [section_1.sub_2]
228
+ key1-2-1: value1
229
+ key1-2-2: value2
230
+ key1-2-3: value3
231
+ key1-2-4: value4
232
+ DATA
233
+ end
234
+
235
+ let(:keys) { [ :key1, :key2 ] }
236
+ let(:sections) { [ :section_1, :section_2 ] }
237
+ let(:section_keys) { [ :key1_1, :key1_2, :key1_3, :key1_4 ] }
238
+ let(:subsections) { [ :sub_1, :sub_2 ] }
239
+ let(:subsection_keys) { [ :key2_1, :key2_2, :key2_3, :key2_4 ] }
240
+ let(:subsection_1_keys) { [ :key1_1_1, :key1_1_2, :key1_1_3, :key1_1_4 ] }
241
+ let(:subsection_2_keys) { [ :key1_2_1, :key1_2_2, :key1_2_3, :key1_2_4 ] }
242
+
243
+ subject { Content.parse(contents) }
244
+
245
+ context '#each' do
246
+
247
+ it 'iterates over each key/value pair in the global section' do
248
+ subject.each do |key, value|
249
+ keys.should include key
250
+ end
251
+ end
252
+
253
+ it 'returns the value associated with the current key' do
254
+ subject.each do |key, value|
255
+ subject[key].should eq value
256
+ end
257
+ end
258
+
259
+ it 'does not iterate over sections at the root' do
260
+ subject.each do |key, value|
261
+ sections.should_not include key
262
+ end
263
+ end
264
+
265
+ end
266
+
267
+ context '#each_section' do
268
+
269
+ it 'iterates over each section in the root' do
270
+ subject.each_section do |section|
271
+ sections.should include section.name
272
+ end
273
+ end
274
+
275
+ it 'returns the node associated with the current section' do
276
+ subject.each_section do |section|
277
+ section.should be_kind_of IniFile::Content
278
+ end
279
+ end
280
+
281
+ it 'does not iterate over properties at the root' do
282
+ subject.each_section do |section|
283
+ keys.should_not include section.name
284
+ end
285
+ end
286
+
287
+ end
288
+
289
+ context '#each for sections' do
290
+
291
+ it 'iterates over each key/value pair in the given section' do
292
+ subject.section_1.each do |key, value|
293
+ section_keys.should include key
294
+ end
295
+ end
296
+
297
+ it 'returns the value associated with the current key' do
298
+ subject.section_1.each do |key, value|
299
+ subject.section_1[key].should eq value
300
+ end
301
+ end
302
+
303
+ it 'does not iterate over subsections of the given section' do
304
+ subject[:section_1].each do |key, value|
305
+ subsections.should_not include key
306
+ end
307
+ end
308
+
309
+ end
310
+
311
+ context '#each_section for sections' do
312
+
313
+ it 'iterates over each section in the root' do
314
+ subject.section_1.each_section do |section|
315
+ subsections.should include section.name
316
+ end
317
+ end
318
+
319
+ it 'returns the node associated with the current section' do
320
+ subject.section_1.each_section do |section|
321
+ section.should be_kind_of IniFile::Content
322
+ end
323
+ end
324
+
325
+ it 'does not iterate over properties at the root' do
326
+ subject.section_1.each_section do |section|
327
+ subsection_keys.should_not include section.name
328
+ end
329
+ end
330
+
331
+ end
332
+
333
+ end
334
+ end
335
+ end
@@ -0,0 +1,499 @@
1
+ require 'spec_helper'
2
+
3
+ # http://en.wikipedia.org/wiki/INI_file
4
+
5
+ module IniFile
6
+
7
+ describe Parser do
8
+
9
+ context '#parse' do
10
+
11
+ context 'method call' do
12
+
13
+ let(:contents) { "; this is a comment" }
14
+
15
+ it 'requires the first argument to be a string' do
16
+ lambda { subject.parse(contents) }.should_not raise_error
17
+ end
18
+
19
+ it 'throws an exception if the first argument is nil' do
20
+ lambda { subject.parse(nil) }.should raise_error(ArgumentError)
21
+ end
22
+
23
+ it 'throws an exception if the first argument is an empty string' do
24
+ lambda { subject.parse('') }.should raise_error(ArgumentError)
25
+ end
26
+
27
+ it 'throws an exception if the first argument is nothing but whitespace' do
28
+ lambda { subject.parse(' ') }.should raise_error(ArgumentError)
29
+ end
30
+
31
+ it 'throws an exception if the first argument is not a string' do
32
+ lambda { subject.parse(123) }.should raise_error(ArgumentError)
33
+ end
34
+
35
+ end
36
+
37
+ context 'return value' do
38
+
39
+ it 'is a hash on success' do
40
+ subject.parse('key=value').should be_a Hash
41
+ end
42
+
43
+ it 'is an empty hash when the content is only comments' do
44
+ subject.parse('; this is a comment').should be_empty
45
+ end
46
+
47
+ end
48
+
49
+ context 'content' do
50
+
51
+ context 'properties' do
52
+
53
+ it 'extracts the key from the left of the delimiter' do
54
+ ini = subject.parse('key=value')
55
+ ini[:key].should_not be_nil
56
+ end
57
+
58
+ it 'ignores whitespace before the key' do
59
+ ini = subject.parse(' key=value')
60
+ ini[:key].should eq 'value'
61
+ end
62
+
63
+ it 'throws an exception on a blank key' do
64
+ lambda {
65
+ subject.parse(' =value')
66
+ }.should raise_error(IniFormatError)
67
+ end
68
+
69
+ it 'throws an exception on whitespace within the key' do
70
+ lambda {
71
+ subject.parse('this is the key=value')
72
+ }.should raise_error(IniFormatError)
73
+ end
74
+
75
+ it 'allows underscores in the key' do
76
+ ini = subject.parse('main_window_maximized=value')
77
+ ini[:main_window_maximized].should eq 'value'
78
+ end
79
+
80
+ it 'converts dashes in the key to underscores' do
81
+ ini = subject.parse('main-window-maximized=value')
82
+ ini[:main_window_maximized].should eq 'value'
83
+ end
84
+
85
+ it 'throws an exception on punctuation within the key' do
86
+ lambda {
87
+ subject.parse("the'key=value")
88
+ }.should raise_error(IniFormatError)
89
+ end
90
+
91
+ it 'throws an exception for a duplicate key' do
92
+ lambda {
93
+ subject.parse("key=value1\nkey=value2")
94
+ }.should raise_error(IniFormatError)
95
+ end
96
+
97
+ it 'ignores the case of the key' do
98
+ ini = subject.parse('KEY=value')
99
+ ini[:key].should eq 'value'
100
+ end
101
+
102
+ it 'allows an equal sign as the delimiter' do
103
+ ini = subject.parse('key=value')
104
+ ini[:key].should eq 'value'
105
+ end
106
+
107
+ it 'allows a colon as the delimiter' do
108
+ ini = subject.parse('key:value')
109
+ ini[:key].should eq 'value'
110
+ end
111
+
112
+ it 'allows spaces before the delimiter' do
113
+ ini = subject.parse('key =value')
114
+ ini[:key].should eq 'value'
115
+ end
116
+
117
+ it 'allows spaces after the delimiter' do
118
+ ini = subject.parse('key: value')
119
+ ini[:key].should eq 'value'
120
+ end
121
+
122
+ it 'ignores single quotes around the value' do
123
+ ini = subject.parse("key='value'")
124
+ ini[:key].should eq 'value'
125
+ end
126
+
127
+ it 'ignores double quotes around the value' do
128
+ ini = subject.parse('key="value"')
129
+ ini[:key].should eq 'value'
130
+ end
131
+
132
+ it 'allows a blank value' do
133
+ ini = subject.parse('key=')
134
+ ini[:key].should be_empty
135
+ end
136
+
137
+ it 'allows spaces within the value' do
138
+ ini = subject.parse('key=this is the value')
139
+ ini[:key].should eq 'this is the value'
140
+ end
141
+
142
+ it 'allows punctuation within the value' do
143
+ value = "+refs/heads/*:refs/remotes/origin/*"
144
+ ini = subject.parse("fetch = #{value}")
145
+ ini[:fetch].should eq value
146
+
147
+ value = "url = git@github.com:jdantonio/ini_file.git"
148
+ ini = subject.parse("url = #{value}")
149
+ ini[:url].should eq value
150
+ end
151
+
152
+ it 'collapses concurrent space characters within the value' do
153
+ ini = subject.parse("key=this is\tthe value")
154
+ ini[:key].should eq 'this is the value'
155
+ end
156
+
157
+ it 'does not collapse concurrent space within quoted values' do
158
+ ini = subject.parse("key='this is\tthe value'")
159
+ ini[:key].should eq "this is\tthe value"
160
+ end
161
+
162
+ it 'preserves the case of the value' do
163
+ ini = subject.parse('key=This is the VALUE')
164
+ ini[:key].should eq 'This is the VALUE'
165
+ end
166
+
167
+ it 'allows line continuation when a line ends with a backslash' do
168
+ pending
169
+ ini = subject.parse("key=this\\\nvalue")
170
+ ini[:key].should eq 'this value'
171
+ end
172
+
173
+ end
174
+
175
+ context 'escape sequences' do
176
+
177
+ it "converts \\\\ into \\" do
178
+ ini = subject.parse("key=this\\\\value")
179
+ ini[:key].should eq "this\\value"
180
+ end
181
+
182
+ it "converts \\0 into a null character" do
183
+ ini = subject.parse("key=this\\0value")
184
+ ini[:key].should eq "this\0value"
185
+ end
186
+
187
+ it "converts \\a into a bell/alert" do
188
+ ini = subject.parse("key=this\\avalue")
189
+ ini[:key].should eq "this\avalue"
190
+ end
191
+
192
+ it "converts \\b into a bell/alert" do
193
+ ini = subject.parse("key=this\\bvalue")
194
+ ini[:key].should eq "this\bvalue"
195
+ end
196
+
197
+ it "converts \\t into a tab" do
198
+ ini = subject.parse("key=this\\tvalue")
199
+ ini[:key].should eq "this\tvalue"
200
+ end
201
+
202
+ it "converts \\r into carriage return" do
203
+ ini = subject.parse("key=this\\rvalue")
204
+ ini[:key].should eq "this\rvalue"
205
+ end
206
+
207
+ it "converts \\n into a newline" do
208
+ ini = subject.parse("key=this\\nvalue")
209
+ ini[:key].should eq "this\nvalue"
210
+ end
211
+
212
+ it "converts \\; into a semicolon" do
213
+ ini = subject.parse("key=this\\;value")
214
+ ini[:key].should eq "this;value"
215
+ end
216
+
217
+ it "converts \\# into a number sign" do
218
+ ini = subject.parse("key=this\\#value")
219
+ ini[:key].should eq "this#value"
220
+ end
221
+
222
+ it 'converts \\x???? into a hexidecimal character' do
223
+ ini = subject.parse("key=this\\x1234value")
224
+ ini[:key].should eq "this\u1234value"
225
+ end
226
+
227
+ it 'converts \\u???? into a hexidecimal character' do
228
+ ini = subject.parse("key=this\\u1234value")
229
+ ini[:key].should eq "this\u1234value"
230
+ end
231
+
232
+ end
233
+
234
+ context 'comments' do
235
+
236
+ it 'ignores lines beginning with a semicolon' do
237
+ ini = subject.parse(';this is a comment')
238
+ ini.should be_empty
239
+ end
240
+
241
+ it 'ignores lines beginning with a pound sign' do
242
+ ini = subject.parse('#this is a comment')
243
+ ini.should be_empty
244
+ end
245
+
246
+ it 'ignores inline comments beginning with a semicolon in unquoted values' do
247
+ ini = subject.parse("key = value ; comment")
248
+ ini[:key].should eq "value"
249
+ end
250
+
251
+ it 'ignores inline comments beginning with a pound sign in unquoted values' do
252
+ ini = subject.parse("key = value # comment")
253
+ ini[:key].should eq "value"
254
+ end
255
+
256
+ it 'allows inline comments beginning with a semicolon in quoted values' do
257
+ ini = subject.parse("key = \"value ; comment\"")
258
+ ini[:key].should eq "value ; comment"
259
+ end
260
+
261
+ it 'allows inline comments beginning with a pound sign in quoted values' do
262
+ ini = subject.parse('key = "value # comment"')
263
+ ini[:key].should eq 'value # comment'
264
+ end
265
+
266
+ it 'allows equal signs within comments' do
267
+ ini = subject.parse(' ;editor = mate -w')
268
+ ini.should be_empty
269
+ end
270
+
271
+ it 'allows colons within comments' do
272
+ ini = subject.parse(' #editor = mate -w')
273
+ ini.should be_empty
274
+ end
275
+
276
+ it 'ignores spaces before the comment indicator' do
277
+ ini = subject.parse(' # this is a comment')
278
+ ini.should be_empty
279
+ end
280
+
281
+ it 'ignores blank lines' do
282
+ ini = subject.parse("key1=value1\n \n\t\n \t key2=value2\n#comment")
283
+ ini[:key1].should eq 'value1'
284
+ ini[:key2].should eq 'value2'
285
+ end
286
+
287
+ it 'ignores empty lines' do
288
+ ini = subject.parse("key1=value1\n\n\n\nkey2=value2")
289
+ ini[:key1].should eq 'value1'
290
+ ini[:key2].should eq 'value2'
291
+ end
292
+
293
+ end
294
+
295
+ context 'sections' do
296
+
297
+ it 'assigns properties before any section headers to the global section' do
298
+ ini = subject.parse("key1=value1\n[section]\nkey2=value2")
299
+ ini[:key1].should eq 'value1'
300
+ end
301
+
302
+ it 'assigns properties after a section header to that section' do
303
+ ini = subject.parse("[section]\nkey=value")
304
+ ini[:key].should be_nil
305
+ ini[:section][:key].should eq 'value'
306
+ end
307
+
308
+ it 'allows whitespace before the opening bracket' do
309
+ ini = subject.parse(" [section]\nkey=value")
310
+ ini[:key].should be_nil
311
+ ini[:section][:key].should eq 'value'
312
+ end
313
+
314
+ it 'ignores whitespace between the opening bracket and the section name' do
315
+ ini = subject.parse("[ section]\nkey=value")
316
+ ini[:key].should be_nil
317
+ ini[:section][:key].should eq 'value'
318
+ end
319
+
320
+ it 'ignores whitespace between the closing bracket and the section name' do
321
+ ini = subject.parse("[section ]\nkey=value")
322
+ ini[:key].should be_nil
323
+ ini[:section][:key].should eq 'value'
324
+ end
325
+
326
+ it 'allows whitespace after the closing bracket' do
327
+ ini = subject.parse("[section] \nkey=value")
328
+ ini[:key].should be_nil
329
+ ini[:section][:key].should eq 'value'
330
+ end
331
+
332
+ it 'allows section names to be enclosed in double quotes' do
333
+ ini = subject.parse("[ \"section\" ]\nkey=value")
334
+ ini[:key].should be_nil
335
+ ini[:section][:key].should eq 'value'
336
+ end
337
+
338
+ it 'allows section names to be enclosed in single quotes' do
339
+ ini = subject.parse("[ 'section' ]\nkey=value")
340
+ ini[:key].should be_nil
341
+ ini[:section][:key].should eq 'value'
342
+ end
343
+
344
+ it 'throws an exception on punctuation within the section name' do
345
+ lambda {
346
+ subject.parse("[the'section]\nkey=value")
347
+ }.should raise_error(IniFormatError)
348
+ end
349
+
350
+ it 'throws an exception when a section name is the same as a property key' do
351
+ lambda {
352
+ subject.parse("foo=value1\n[foo]\nkey2=value2")
353
+ }.should raise_error(IniFormatError)
354
+ end
355
+
356
+ it 'throws an exception on duplicate keys within a section' do
357
+ lambda {
358
+ subject.parse("[section]\nfoo=value1\n[section]\nfoo=value2")
359
+ }.should raise_error(IniFormatError)
360
+ end
361
+
362
+ it 'ignores the case of the section name' do
363
+ ini = subject.parse("[SeCtIoN]\nkey=value")
364
+ ini[:key].should be_nil
365
+ ini[:section][:key].should eq 'value'
366
+ end
367
+
368
+ it 'creates a hierarchy when a section name is delimited by a dot' do
369
+ ini = subject.parse("[section.subsection]\nkey=value")
370
+ ini[:key].should be_nil
371
+ ini[:section][:subsection][:key].should eq 'value'
372
+ end
373
+
374
+ it 'creates a hierarchy when a section name is delimited by a backslash' do
375
+ ini = subject.parse("[section\\subsection]\nkey=value")
376
+ ini[:key].should be_nil
377
+ ini[:section][:subsection][:key].should eq 'value'
378
+ end
379
+
380
+ it 'creates a hierarchy when a section name is delimited by a forward slash' do
381
+ ini = subject.parse("[section/subsection]\nkey=value")
382
+ ini[:key].should be_nil
383
+ ini[:section][:subsection][:key].should eq 'value'
384
+ end
385
+
386
+ it 'creates a hierarchy when a section name is delimited by a comma' do
387
+ ini = subject.parse("[section,subsection]\nkey=value")
388
+ ini[:key].should be_nil
389
+ ini[:section][:subsection][:key].should eq 'value'
390
+ end
391
+
392
+ it 'creates a hierarchy when a section name is delimited by whitespace' do
393
+ ini = subject.parse("[section subsection]\nkey=value")
394
+ ini[:key].should be_nil
395
+ ini[:section][:subsection][:key].should eq 'value'
396
+ end
397
+
398
+ it 'allows whitespace before the section delimiter' do
399
+ ini = subject.parse("[section .subsection]\nkey=value")
400
+ ini[:key].should be_nil
401
+ ini[:section][:subsection][:key].should eq 'value'
402
+ end
403
+
404
+ it 'allows whitespace after the section delimiter' do
405
+ ini = subject.parse("[section.\t subsection]\nkey=value")
406
+ ini[:key].should be_nil
407
+ ini[:section][:subsection][:key].should eq 'value'
408
+ end
409
+
410
+ it 'does not collide names when a section has subsections' do
411
+ ini = subject.parse("[section]\nkey1=value1\n[section.subsection]\nkey2=value2")
412
+ ini[:section].should be_a Hash
413
+ ini[:section][:key1].should eq 'value1'
414
+ ini[:section].count.should eq 2
415
+ ini[:section][:subsection].should be_a Hash
416
+ ini[:section][:subsection][:key2].should eq 'value2'
417
+ ini[:section][:subsection].count.should eq 1
418
+ end
419
+
420
+ it 'allows a section to have a subsection with the same name' do
421
+ ini = subject.parse("[section]\nkey1=value1\n[section.section]\nkey2=value2")
422
+ ini[:section].should be_a Hash
423
+ ini[:section][:key1].should eq 'value1'
424
+ ini[:section].count.should eq 2
425
+ ini[:section][:section].should be_a Hash
426
+ ini[:section][:section][:key2].should eq 'value2'
427
+ ini[:section][:section].count.should eq 1
428
+ end
429
+
430
+ it 'throws an exception when a section name is blank' do
431
+ lambda {
432
+ subject.parse("[ ]")
433
+ }.should raise_error(IniFormatError)
434
+ end
435
+
436
+ it 'throws an exception when a subsection name is blank' do
437
+ lambda {
438
+ subject.parse("[head..subsection]")
439
+ }.should raise_error(IniFormatError)
440
+ end
441
+
442
+ it 'throws an exception when a section name mixes hierarchy delimiters' do
443
+ lambda {
444
+ subject.parse("[this.is\\the/section,header]")
445
+ }.should raise_error(IniFormatError)
446
+ end
447
+
448
+ end
449
+
450
+ context 'multiple lines' do
451
+
452
+ it 'supports multiple property lines' do
453
+ ini = subject.parse("key1=value1\nkey2=value2")
454
+ ini[:key1].should eq 'value1'
455
+ ini[:key2].should eq 'value2'
456
+ end
457
+
458
+ it 'supports multiple comment lines' do
459
+ ini = subject.parse(";this is a comment\n#this is also a comment")
460
+ ini.should be_empty
461
+ end
462
+
463
+ it 'supports multiple blank lines' do
464
+ ini = subject.parse("key1=value1\n\n\n\n\n\nkey2=value2")
465
+ ini[:key1].should eq 'value1'
466
+ ini[:key2].should eq 'value2'
467
+ end
468
+
469
+ end
470
+
471
+ context 'garbage' do
472
+
473
+ it 'throws an exception when encountering garbage lines' do
474
+ lambda {
475
+ subject.parse('this line is garbage')
476
+ }.should raise_error(IniFormatError)
477
+ end
478
+
479
+ end
480
+
481
+ context 'property data types' do
482
+
483
+ it 'converts non-quoted integer property values to integers' do
484
+ ini = subject.parse("key1 = 123")
485
+ ini[:key1].should be_a Numeric
486
+ ini[:key1].should eq 123
487
+ end
488
+
489
+ it 'converts non-quoted numeric property values to numbers' do
490
+ ini = subject.parse("key1 = 123.45")
491
+ ini[:key1].should be_a Numeric
492
+ ini[:key1].should be_within(0.1).of(123.45)
493
+ end
494
+ end
495
+
496
+ end
497
+ end
498
+ end
499
+ end
@@ -0,0 +1,75 @@
1
+ require 'fileutils'
2
+ require 'spec_helper'
3
+
4
+ # http://en.wikipedia.org/wiki/INI_file
5
+
6
+ describe IniFile do
7
+
8
+ subject { IniFile }
9
+
10
+ let(:filename) { 'test.ini' }
11
+ let(:contents) { '; this is a comment' }
12
+
13
+ def write_ini_file(path = '.')
14
+ File.open(File.join(path, filename), 'w') {|f| f.write(contents) }
15
+ end
16
+
17
+ context '#parse' do
18
+
19
+ it 'returns a hash on success' do
20
+ subject.parse(contents).should be_a Hash
21
+ end
22
+
23
+ it 'bubbles any exceptions thrown by the Parser' do
24
+ lambda { subject.parse('garbage') }.should raise_error
25
+ end
26
+
27
+ it 'returns nil on error when safe is set to true' do
28
+ subject.parse('garbage', true).should be_nil
29
+ end
30
+
31
+ end
32
+
33
+ context '#load' do
34
+
35
+ it 'throws an exception if the file does not exist' do
36
+ lambda { subject.load(filename) }.should raise_error
37
+ end
38
+
39
+ it 'throws an exception on inadequate file permissions' do
40
+ #pending 'FakeFS is not properly setting file permissions'
41
+ #write_ini_file
42
+ #FileUtils.chmod(0222, filename)
43
+ File.stub(:open).and_raise(Errno::EACCES)
44
+ lambda { subject.load(filename) }.should raise_error
45
+ end
46
+
47
+ it 'passes the file contents to the Parser' do
48
+ write_ini_file
49
+ IniFile::Parser.should_receive(:parse).with(contents)
50
+ subject.load(filename)
51
+ end
52
+
53
+ it 'expands the full file path' do
54
+ write_ini_file(ENV['HOME'])
55
+ lambda { subject.load(File.join("~/#{filename}")) }.should_not raise_error
56
+ end
57
+
58
+ it 'bubbles any exceptions thrown by the Content parser' do
59
+ File.open(filename, 'w') {|f| f.write(contents) }
60
+ IniFile::Content.stub(:parse).with(any_args()).and_raise(StandardError.new('test exception'))
61
+ lambda { subject.load(filename) }.should raise_error(StandardError)
62
+ end
63
+
64
+ it 'returns the new contents object' do
65
+ File.open(filename, 'w') {|f| f.write(contents) }
66
+ IniFile.load(filename).should be_instance_of IniFile::Content
67
+ end
68
+
69
+ it 'returns nil on error when safe is set to true' do
70
+ subject.load('file that does not exist', true).should be_nil
71
+ end
72
+
73
+ end
74
+
75
+ end
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'ini_file'
6
+
7
+ require 'rspec'
8
+ require 'fakefs'
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
data/tasks/rcov.rake ADDED
@@ -0,0 +1,11 @@
1
+ namespace :rcov do
2
+
3
+ RSpec::Core::RakeTask.new(:rspec) do |t|
4
+ rm "coverage.data" if File.exist?("coverage.data")
5
+ t.pattern = 'spec/**/*_spec.rb'
6
+ t.rcov = true
7
+ t.rcov_opts = %w{--exclude osx\/objc,Users\/,gems\/,spec\/,teamcity}
8
+ t.verbose = true
9
+ end
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ini_file
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jerry D'Antonio
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: debugger
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: simplecov
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: fakefs
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ description: ! '''The INI file format is an informal standard for configuration files
111
+ for some platforms or software. INI files are simple text files with a basic structure
112
+ composed of ''sections'' and ''properties''.'' - Wikipedia'
113
+ email:
114
+ - jerry.dantonio@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - Rakefile
120
+ - README.rdoc
121
+ - LICENSE
122
+ - lib/ini_file.rb
123
+ - lib/ini_file/version.rb
124
+ - lib/ini_file/parser.rb
125
+ - lib/ini_file/content.rb
126
+ - spec/ini_file_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/ini_file/content_spec.rb
129
+ - spec/ini_file/parser_spec.rb
130
+ - tasks/rcov.rake
131
+ homepage: https://github.com/jdantonio/ini_file
132
+ licenses: []
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ - lib/ini_file
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ! '>='
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 1.8.24
153
+ signing_key:
154
+ specification_version: 3
155
+ summary: Manage simple INI files.
156
+ test_files:
157
+ - spec/ini_file_spec.rb
158
+ - spec/spec_helper.rb
159
+ - spec/ini_file/content_spec.rb
160
+ - spec/ini_file/parser_spec.rb