iniparse 0.2.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,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