iniparse 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008-2009 Anthony Williams
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,75 @@
1
+ = IniParse
2
+
3
+ IniParse is a pure Ruby library for parsing
4
+ INI[http://en.wikipedia.org/wiki/INI_file] configuration and data
5
+ files.
6
+
7
+ This rubygem is in ongoing development and - specs aside - hasn't yet been
8
+ properly tested in a production environment. If you think you've found a bug,
9
+ or you feed it an INI file which doesn't work as you'd expected, please mail
10
+ me at +anthony at ninecraft dot com+, ideally with a copy of the INI file in
11
+ question.
12
+
13
+ === Main features
14
+
15
+ * <b>Support for duplicate options.</b> While not common, some INI files
16
+ contain an option more than once. IniParse does not overwrite previous
17
+ options, but allows you to access all of the duplicate values.
18
+
19
+ * <b>Preservation of white space and blank lines.</b> When writing back to
20
+ your INI file, line indents, white space and comments (and their indents)
21
+ are preserved. Only trailing white space (which has no significance in INI
22
+ files) will be removed.
23
+
24
+ * <b>Preservation of section and option ordering.</b> Sections and options
25
+ are kept in the same order they are in the original document ensuring that
26
+ nothing gets mangled when writing back to the file.
27
+
28
+ If you don't need the above mentioned features, you may find the simpler
29
+ IniFile gem does all you need.
30
+
31
+ === Opening an INI file
32
+
33
+ Parsing an INI file is fairly simple:
34
+
35
+ IniParse.parse( File.open('path/to/my/file.ini') ) # => IniParse::Document
36
+
37
+ IniParse.parse returns an IniParse::Document instance which represents the
38
+ passed "INI string". Assuming you know the structure of the document, you can
39
+ access the sections in the INI document with IniParse::Document#[]. For
40
+ example:
41
+
42
+ document = IniParse.parse( File.read('path/to/my/file.ini') )
43
+
44
+ document['a_section']
45
+ # => IniParse::Lines::Section
46
+
47
+ document['a_section']['an_option']
48
+ # => "a value"
49
+ document['a_section']['another_option']
50
+ # => "another value"
51
+
52
+ In the event that duplicate options were found, an array of the values will
53
+ be supplied to you.
54
+
55
+ document = IniParse.parse <<-EOS
56
+ [my_section]
57
+ key = value
58
+ key = another value
59
+ key = a third value
60
+ EOS
61
+
62
+ document['my_section']['key']
63
+ # => ['value', 'another value', 'third value']
64
+
65
+ === Updating an INI file
66
+
67
+ document['a_section']['an_option']
68
+ # => "a value"
69
+ document['a_section']['an_option'] = 'a new value'
70
+ document['a_section']['an_option']
71
+ # => "a new value"
72
+
73
+ === Creating a new INI file
74
+
75
+ See IniParse::Generator.
data/Rakefile ADDED
@@ -0,0 +1,102 @@
1
+ require 'rubygems'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'rubygems/specification'
5
+ require 'date'
6
+ require 'spec/rake/spectask'
7
+
8
+ begin
9
+ require 'hanna/rdoctask'
10
+ rescue LoadError
11
+ require 'rake/rdoctask'
12
+ end
13
+
14
+ require 'lib/iniparse/version'
15
+
16
+ GEM = 'iniparse'
17
+ GEM_VERSION = IniParse::VERSION
18
+
19
+ ##############################################################################
20
+ # Packaging & Installation
21
+ ##############################################################################
22
+
23
+ CLEAN.include ['pkg', '*.gem', 'doc', 'coverage']
24
+
25
+ spec = Gem::Specification.new do |s|
26
+ s.name = GEM
27
+ s.version = GEM_VERSION
28
+ s.platform = Gem::Platform::RUBY
29
+ s.summary = "A pure Ruby library for parsing INI documents."
30
+ s.description = s.summary
31
+ s.author = 'Anthony Williams'
32
+ s.email = 'anthony@ninecraft.com'
33
+ s.homepage = 'http://github.com/anthonyw/iniparse'
34
+
35
+ # rdoc
36
+ s.has_rdoc = true
37
+ s.extra_rdoc_files = %w(README.rdoc LICENSE)
38
+
39
+ # Dependencies
40
+ s.add_dependency 'extlib', '>= 0.9.9'
41
+
42
+ s.require_path = 'lib'
43
+ s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{lib,spec}/**/*")
44
+
45
+ s.rubyforge_project = 'iniparse'
46
+ end
47
+
48
+
49
+ Rake::GemPackageTask.new(spec) do |pkg|
50
+ pkg.gem_spec = spec
51
+ end
52
+
53
+ desc "Run :package and install the resulting .gem"
54
+ task :install => :package do
55
+ sh %(gem install --local pkg/#{GEM}-#{GEM_VERSION}.gem)
56
+ end
57
+
58
+ desc "Run :clean and uninstall the .gem"
59
+ task :uninstall => :clean do
60
+ sh %(gem uninstall #{GEM})
61
+ end
62
+
63
+ desc "Create a gemspec file"
64
+ task :make_spec do
65
+ File.open("#{GEM}.gemspec", "w") do |file|
66
+ file.puts spec.to_ruby
67
+ end
68
+ end
69
+
70
+ ##############################################################################
71
+ # Documentation
72
+ ##############################################################################
73
+ task :doc => ['doc:rdoc']
74
+ namespace :doc do
75
+
76
+ Rake::RDocTask.new do |rdoc|
77
+ rdoc.rdoc_files.add(%w(LICENSE README.rdoc lib/**/*.rb))
78
+ rdoc.main = "README.rdoc"
79
+ rdoc.title = "IniParse API Documentation"
80
+ rdoc.options << "--line-numbers" << "--inline-source"
81
+ rdoc.rdoc_dir = 'doc'
82
+ end
83
+
84
+ end
85
+
86
+ ##############################################################################
87
+ # rSpec & rcov
88
+ ##############################################################################
89
+
90
+ desc "Run all examples"
91
+ Spec::Rake::SpecTask.new('spec') do |t|
92
+ t.spec_files = FileList['spec/**/*.rb']
93
+ t.spec_opts = ['-c -f s']
94
+ end
95
+
96
+ desc "Run all examples with RCov"
97
+ Spec::Rake::SpecTask.new('spec:rcov') do |t|
98
+ t.spec_files = FileList['spec/**/*.rb']
99
+ t.spec_opts = ['-c -f s']
100
+ t.rcov = true
101
+ t.rcov_opts = ['--exclude', 'spec']
102
+ end
data/lib/iniparse.rb ADDED
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'extlib'
3
+
4
+ dir = Pathname(__FILE__).dirname.expand_path / 'iniparse'
5
+
6
+ require dir / 'document'
7
+ require dir / 'generator'
8
+ require dir / 'line_collection'
9
+ require dir / 'lines'
10
+ require dir / 'parser'
11
+ require dir / 'version'
12
+
13
+ module IniParse
14
+ # A base class for IniParse errors.
15
+ class IniParseError < StandardError; end
16
+
17
+ # Raised if an error occurs parsing an INI document.
18
+ class ParseError < IniParseError; end
19
+
20
+ # Raised when an option line is found during parsing before the first
21
+ # section.
22
+ class NoSectionError < ParseError; end
23
+
24
+ # Raised when a line is added to a collection which isn't allowed (e.g.
25
+ # adding a Section line into an OptionCollection).
26
+ class LineNotAllowed < IniParseError; end
27
+
28
+ module_function
29
+
30
+ # Parse given given INI document source +source+.
31
+ #
32
+ # See IniParse::Parser.parse
33
+ #
34
+ # ==== Parameters
35
+ # source<String>:: The source from the INI document.
36
+ #
37
+ # ==== Returns
38
+ # IniParse::Document
39
+ #
40
+ def parse(source)
41
+ IniParse::Parser.new(source).parse
42
+ end
43
+
44
+ # Opens the file at +path+, reads and parses it's contents.
45
+ #
46
+ # ==== Parameters
47
+ # path<String>:: The path to the INI document.
48
+ #
49
+ # ==== Returns
50
+ # IniParse::Document
51
+ #
52
+ def open(path)
53
+ document = IniParse::Parser.new(File.read(path)).parse
54
+ document.path = path
55
+ document
56
+ end
57
+
58
+ # Creates a new IniParse::Document using the specification you provide.
59
+ #
60
+ # See IniParse::Generator.
61
+ #
62
+ # ==== Returns
63
+ # IniParse::Document
64
+ #
65
+ def gen(&blk)
66
+ IniParse::Generator.new.gen(&blk)
67
+ end
68
+ end
@@ -0,0 +1,62 @@
1
+ module IniParse
2
+ # Represents an INI document.
3
+ class Document
4
+ attr_reader :lines
5
+ attr_accessor :path
6
+
7
+ # Creates a new Document instance.
8
+ def initialize(path = nil)
9
+ @path = path
10
+ @lines = IniParse::SectionCollection.new
11
+ end
12
+
13
+ # Enumerates through each Section in this document.
14
+ #
15
+ # Does not yield blank and comment lines by default; if you want _all_
16
+ # lines to be yielded, pass true.
17
+ #
18
+ # ==== Parameters
19
+ # include_blank<Boolean>:: Include blank/comment lines?
20
+ #
21
+ def each(*args, &blk)
22
+ @lines.each(*args, &blk)
23
+ end
24
+
25
+ # Returns the section identified by +key+.
26
+ #
27
+ # Returns nil if there is no Section with the given key.
28
+ #
29
+ def [](key)
30
+ @lines[key.to_s]
31
+ end
32
+
33
+ # Returns this document as a string suitable for saving to a file.
34
+ def to_ini
35
+ @lines.to_a.map { |line| line.to_ini }.join($/)
36
+ end
37
+
38
+ # Returns true if a section with the given +key+ exists in this document.
39
+ def has_section?(key)
40
+ @lines.has_key?(key.to_s)
41
+ end
42
+
43
+ # Saves a copy of this Document to disk.
44
+ #
45
+ # If a path was supplied when the Document was initialized then nothing
46
+ # needs to be given to Document#save. If Document was not given a file
47
+ # path, or you wish to save the document elsewhere, supply a path when
48
+ # calling Document#save.
49
+ #
50
+ # ==== Parameters
51
+ # path<String>:: A path to which this document will be saved.
52
+ #
53
+ # ==== Raises
54
+ # IniParseError:: If your document couldn't be saved.
55
+ #
56
+ def save(path = nil)
57
+ @path = path if path
58
+ raise IniParseError, 'No path given to Document#save' if @path.blank?
59
+ File.open(@path, 'w') { |f| f.write(self.to_ini) }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,200 @@
1
+ module IniParse
2
+ # Generator provides a means for easily creating new INI documents.
3
+ #
4
+ # Rather than trying to hack together new INI documents by manually creating
5
+ # Document, Section and Option instances, it is preferable to use Generator
6
+ # which will handle it all for you.
7
+ #
8
+ # The Generator is exposed through IniParse.gen.
9
+ #
10
+ # IniParse.gen do |doc|
11
+ # doc.section("vehicle") do |vehicle|
12
+ # vehicle.option("road_side", "left")
13
+ # vehicle.option("realistic_acceleration", true)
14
+ # vehicle.option("max_trains", 500)
15
+ # end
16
+ #
17
+ # doc.section("construction") do |construction|
18
+ # construction.option("build_on_slopes", true)
19
+ # construction.option("autoslope", true)
20
+ # end
21
+ # end
22
+ #
23
+ # # => IniParse::Document
24
+ #
25
+ # This can be simplified further if you don't mind the small overhead
26
+ # which comes with +method_missing+:
27
+ #
28
+ # IniParse.gen do |doc|
29
+ # doc.vehicle do |vehicle|
30
+ # vehicle.road_side = "left"
31
+ # vehicle.realistic_acceleration = true
32
+ # vehicle.max_trains = 500
33
+ # end
34
+ #
35
+ # doc.construction do |construction|
36
+ # construction.build_on_slopes = true
37
+ # construction.autoslope = true
38
+ # end
39
+ # end
40
+ #
41
+ # # => IniParse::Document
42
+ #
43
+ # If you want to add slightly more complicated formatting to your document,
44
+ # each line type (except blanks) takes a number of optional parameters:
45
+ #
46
+ # :comment::
47
+ # Adds an inline comment at the end of the line.
48
+ # :comment_offset::
49
+ # Indent the comment. Measured in characters from _beginning_ of the line.
50
+ # See String#ljust.
51
+ # :indent::
52
+ # Adds the supplied text to the beginning of the line.
53
+ #
54
+ # If you supply +:indent+, +:comment_sep+, or +:comment_offset+ options when
55
+ # adding a section, the same options will be inherited by all of the options
56
+ # which belong to it.
57
+ #
58
+ # IniParse.gen do |doc|
59
+ # doc.section("vehicle",
60
+ # :comment => "Options for vehicles", :indent => " "
61
+ # ) do |vehicle|
62
+ # vehicle.option("road_side", "left")
63
+ # vehicle.option("realistic_acceleration", true)
64
+ # vehicle.option("max_trains", 500, :comment => "More = slower")
65
+ # end
66
+ # end.to_ini
67
+ #
68
+ # [vehicle] ; Options for vehicles
69
+ # road_side = left
70
+ # realistic_acceleration = true
71
+ # max_trains = 500 ; More = slower
72
+ #
73
+ class Generator
74
+ attr_reader :context
75
+ attr_reader :document
76
+
77
+ def initialize(opts = {}) # :nodoc:
78
+ @document = IniParse::Document.new
79
+ @context = @document
80
+
81
+ @in_section = false
82
+ @opt_stack = [opts]
83
+ end
84
+
85
+ def gen # :nodoc:
86
+ yield self
87
+ @document
88
+ end
89
+
90
+ # Creates a new IniParse::Document with the given sections and options.
91
+ #
92
+ # ==== Returns
93
+ # IniParse::Document
94
+ #
95
+ def self.gen(opts = {}, &blk)
96
+ new(opts).gen(&blk)
97
+ end
98
+
99
+ # Creates a new section with the given name and adds it to the document.
100
+ #
101
+ # You can optionally supply a block (as detailed in the documentation for
102
+ # Generator#gen) in order to add options to the section.
103
+ #
104
+ # ==== Parameters
105
+ # name<String>:: A name for the given section.
106
+ #
107
+ def section(name, opts = {})
108
+ if @in_section
109
+ # Nesting sections is bad, mmmkay?
110
+ raise LineNotAllowed, "You can't nest sections in INI files."
111
+ end
112
+
113
+ @context = Lines::Section.new(name, line_options(opts))
114
+ @document.lines << @context
115
+
116
+ if block_given?
117
+ begin
118
+ @in_section = true
119
+ with_options(opts) { yield self }
120
+ @context = @document
121
+ blank()
122
+ ensure
123
+ @in_section = false
124
+ end
125
+ end
126
+ end
127
+
128
+ # Adds a new option to the current section.
129
+ #
130
+ # Can only be called as part of a section block, or after at least one
131
+ # section has been added to the document.
132
+ #
133
+ # ==== Parameters
134
+ # key<String>:: The key (name) for this option.
135
+ # value:: The option's value.
136
+ # opts<Hash>:: Extra options for the line (formatting, etc).
137
+ #
138
+ # ==== Raises
139
+ # IniParse::NoSectionError::
140
+ # If no section has been added to the document yet.
141
+ #
142
+ def option(key, value, opts = {})
143
+ @context.lines << Lines::Option.new(
144
+ key, value, line_options(opts)
145
+ )
146
+ rescue LineNotAllowed
147
+ # Tried to add an Option to a Document.
148
+ raise NoSectionError, <<-EOS.compress_lines
149
+ Your INI document contains an option before the first section is
150
+ declared which is not allowed.
151
+ EOS
152
+ end
153
+
154
+ # Adds a new comment line to the document.
155
+ #
156
+ # ==== Parameters
157
+ # comment<String>:: The text for the comment line.
158
+ #
159
+ def comment(comment, opts = {})
160
+ @context.lines << Lines::Comment.new(
161
+ line_options(opts.merge(:comment => comment))
162
+ )
163
+ end
164
+
165
+ # Adds a new blank line to the document.
166
+ def blank
167
+ @context.lines << Lines::Blank.new
168
+ end
169
+
170
+ # Wraps lines, setting default options for each.
171
+ def with_options(opts = {}) # :nodoc:
172
+ opts = opts.dup
173
+ opts.delete(:comment)
174
+ @opt_stack.push( @opt_stack.last.merge(opts))
175
+ yield self
176
+ @opt_stack.pop
177
+ end
178
+
179
+ def method_missing(name, *args, &blk) # :nodoc:
180
+ if m = name.to_s.match(/(.*)=$/)
181
+ option(m[1], *args)
182
+ else
183
+ section(name.to_s, *args, &blk)
184
+ end
185
+ end
186
+
187
+ #######
188
+ private
189
+ #######
190
+
191
+ # Returns options for a line.
192
+ #
193
+ # If the context is a section, we use the section options as a base,
194
+ # rather than the global defaults.
195
+ #
196
+ def line_options(given_opts) # :nodoc:
197
+ @opt_stack.last.empty? ? given_opts : @opt_stack.last.merge(given_opts)
198
+ end
199
+ end
200
+ end