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 +20 -0
- data/README.rdoc +1 -0
- data/Rakefile +16 -0
- data/lib/ini_file.rb +32 -0
- data/lib/ini_file/content.rb +76 -0
- data/lib/ini_file/parser.rb +93 -0
- data/lib/ini_file/version.rb +3 -0
- data/spec/ini_file/content_spec.rb +335 -0
- data/spec/ini_file/parser_spec.rb +499 -0
- data/spec/ini_file_spec.rb +75 -0
- data/spec/spec_helper.rb +12 -0
- data/tasks/rcov.rake +11 -0
- metadata +160 -0
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,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
|
data/spec/spec_helper.rb
ADDED
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
|