├── .bundle
└── config
├── .gitignore
├── .rspec
├── .travis.yml
├── CHANGELOG.md
├── Gemfile
├── LICENSE
├── README.rdoc
├── Rakefile
├── TODO
├── iniparse.gemspec
├── lib
├── iniparse.rb
└── iniparse
│ ├── document.rb
│ ├── generator.rb
│ ├── line_collection.rb
│ ├── lines.rb
│ └── parser.rb
└── spec
├── document_spec.rb
├── fixture_spec.rb
├── fixtures
├── authconfig.ini
├── openttd.ini
├── race07.ini
└── smb.ini
├── generator
├── method_missing_spec.rb
├── with_section_blocks_spec.rb
└── without_section_blocks_spec.rb
├── iniparse_spec.rb
├── line_collection_spec.rb
├── lines_spec.rb
├── parser
├── document_parsing_spec.rb
└── line_parsing_spec.rb
├── spec_fixtures.rb
├── spec_helper.rb
└── spec_helper_spec.rb
/.bundle/config:
--------------------------------------------------------------------------------
1 | ---
2 | BUNDLE_WITHOUT: extras
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | coverage/*
3 | doc/*
4 | pkg/*
5 | *.rbc
6 | *.swp
7 | *.gem
8 | rdoc
9 | Gemfile.lock
10 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | script: "bundle exec rspec"
3 | bundler_args: "--without extras"
4 | before_install:
5 | - gem update --system
6 | - gem update bundler
7 | - gem cleanup bundler
8 | rvm:
9 | - 2.4
10 | - 2.3
11 | - 2.2
12 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ### 1.5.0
2 |
3 | * OptionCollection no longer yields duplicate keys as an array, but instead yields each key in turn.
4 |
5 | For example, given an INI file:
6 |
7 | ```ini
8 | [test]
9 | a = 1
10 | a = 2
11 | b = 3
12 | ```
13 |
14 | IniParse would previously yield a single "a" key: an array containing two `Line`s:
15 |
16 | ```ruby
17 | doc['test'].map { |line| line }
18 | # => [[, ], ]
19 | ```
20 |
21 | Instead, each key/value pair will be yielded in turn:
22 |
23 | ```ruby
24 | doc['test'].map { |line| line }
25 | # => [, , ]
26 | ```
27 |
28 | Directly accessing values via `[]` will still return an array of values as before:
29 |
30 | ```ruby
31 | doc['test']['a']
32 | # => [1, 2]
33 | ```
34 |
35 | * LineCollection#each may be called without a block, returning an Enumerator.
36 |
37 | ```ruby
38 | doc = IniParse.parse(<<~EOF)
39 | [test]
40 | a = x
41 | b = y
42 | EOF
43 |
44 | doc[test].each
45 | # => #
46 | ```
47 |
48 | This allows for chaining as in the standard library:
49 |
50 | ```ruby
51 | doc['test'].map.with_index { |a, i| { index: i, value: a.value } }
52 | # => [{ index: 0, value: 'x' }, { index: 1, value: 'y' }]
53 | ```
54 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | gemspec
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2008-2010 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.
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = IniParse {
}[http://travis-ci.org/antw/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 | === Main features
8 |
9 | * Support for duplicate options. While not common, some INI files
10 | contain an option more than once. IniParse does not overwrite previous
11 | options, but allows you to access all of the duplicate values.
12 |
13 | * Preservation of white space and blank lines. When writing back to
14 | your INI file, line indents, white space and comments (and their indents)
15 | are preserved. Only trailing white space (which has no significance in INI
16 | files) will be removed.
17 |
18 | * Preservation of section and option ordering. Sections and options
19 | are kept in the same order they are in the original document ensuring that
20 | nothing gets mangled when writing back to the file.
21 |
22 | If you don't need the above mentioned features, you may find the simpler
23 | IniFile gem does all you need.
24 |
25 | === Opening an INI file
26 |
27 | Parsing an INI file is fairly simple:
28 |
29 | IniParse.parse( File.read('path/to/my/file.ini') ) # => IniParse::Document
30 |
31 | IniParse.parse returns an IniParse::Document instance which represents the
32 | passed "INI string". Assuming you know the structure of the document, you can
33 | access the sections in the INI document with IniParse::Document#[]. For
34 | example:
35 |
36 | document = IniParse.parse( File.read('path/to/my/file.ini') )
37 |
38 | document['a_section']
39 | # => IniParse::Lines::Section
40 |
41 | document['a_section']['an_option']
42 | # => "a value"
43 | document['a_section']['another_option']
44 | # => "another value"
45 |
46 | In the event that duplicate options were found, an array of the values will
47 | be supplied to you.
48 |
49 | document = IniParse.parse <<-EOS
50 | [my_section]
51 | key = value
52 | key = another value
53 | key = a third value
54 | EOS
55 |
56 | document['my_section']['key']
57 | # => ['value', 'another value', 'third value']
58 |
59 | Options which appear before the first section will be added to a section called
60 | "__anonymous__".
61 |
62 | document = IniParse.parse <<-EOS
63 | driver = true
64 |
65 | [driver]
66 | key = value
67 | EOS
68 |
69 | document['__anonymous__']['driver']
70 | # => true
71 |
72 | document['driver']['key']
73 | # => 'value'
74 |
75 | === Updating an INI file
76 |
77 | document['a_section']['an_option']
78 | # => "a value"
79 | document['a_section']['an_option'] = 'a new value'
80 | document['a_section']['an_option']
81 | # => "a new value"
82 |
83 | Delete entire sections by calling `#delete` with the section name...
84 |
85 | document.delete('a_section')
86 |
87 | ... or a single option with `#delete` on the section object:
88 |
89 | document['a_section'].delete('an_option')
90 |
91 | === Creating a new INI file
92 |
93 | See IniParse::Generator.
94 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake'
3 | require 'date'
4 |
5 | require 'rake/clean'
6 | require 'rdoc/task'
7 | require 'rspec/core/rake_task'
8 |
9 | ##############################################################################
10 | # Helpers
11 | ##############################################################################
12 |
13 | def name
14 | @name ||= Dir['*.gemspec'].first.split('.').first
15 | end
16 |
17 | def version
18 | line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
19 | line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
20 | end
21 |
22 | def date
23 | Date.today.to_s
24 | end
25 |
26 | def gemspec_file
27 | "#{name}.gemspec"
28 | end
29 |
30 | def gem_file
31 | "#{name}-#{version}.gem"
32 | end
33 |
34 | def replace_header(head, header_name)
35 | head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
36 | end
37 |
38 | ##############################################################################
39 | # Packaging & Installation.
40 | ##############################################################################
41 |
42 | CLEAN.include ['pkg', '*.gem', 'doc', 'coverage']
43 |
44 | desc 'Build the gem, and push to Github'
45 | task :release => :build do
46 | unless `git branch` =~ /^\* master$/
47 | puts "You must be on the master branch to release!"
48 | exit!
49 | end
50 | sh "git commit --allow-empty -a -m 'Release #{version}'"
51 | sh "git tag v#{version}"
52 | sh "git push origin master"
53 | sh "git push origin v#{version}"
54 |
55 | puts "Push to Rubygems.org with"
56 | puts " gem push pkg/#{name}-#{version}.gem"
57 | end
58 |
59 | desc 'Builds the IniParse gem'
60 | task :build => :gemspec do
61 | sh "mkdir -p pkg"
62 | sh "gem build #{gemspec_file}"
63 | sh "mv #{gem_file} pkg"
64 | end
65 |
66 | desc 'Creates a fresh gemspec'
67 | task :gemspec => :validate do
68 | # read spec file and split out manifest section
69 | spec = File.read(gemspec_file)
70 | head, manifest, tail = spec.split(" # = MANIFEST =\n")
71 |
72 | # replace name version and date
73 | replace_header(head, :name)
74 | replace_header(head, :version)
75 | replace_header(head, :date)
76 |
77 | # determine file list from git ls-files
78 | files = `git ls-files`.
79 | split("\n").
80 | sort.
81 | reject { |file| file =~ /^\./ }.
82 | reject { |file| file =~ /^(rdoc|pkg|spec)/ }.
83 | map { |file| " #{file}" }.
84 | join("\n")
85 |
86 | # piece file back together and write
87 | manifest = " s.files = %w[\n#{files}\n ]\n"
88 | spec = [head, manifest, tail].join(" # = MANIFEST =\n")
89 | File.open(gemspec_file, 'w') { |io| io.write(spec) }
90 | puts "Updated #{gemspec_file}"
91 | end
92 |
93 | task :validate do
94 | libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
95 | unless libfiles.empty?
96 | puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
97 | exit!
98 | end
99 | unless Dir['VERSION*'].empty?
100 | puts "A `VERSION` file at root level violates Gem best practices."
101 | exit!
102 | end
103 | end
104 |
105 | ##############################################################################
106 | # Documentation
107 | ##############################################################################
108 |
109 | Rake::RDocTask.new do |rdoc|
110 | rdoc.rdoc_dir = 'rdoc'
111 | rdoc.title = "iniparse #{version}"
112 | rdoc.rdoc_files.include('README*')
113 | rdoc.rdoc_files.include('lib/**/*.rb')
114 | end
115 |
116 | ##############################################################################
117 | # Tests & Metrics.
118 | ##############################################################################
119 |
120 | RSpec::Core::RakeTask.new('spec') do |spec|
121 | spec.pattern = 'spec/**/*_spec.rb'
122 | end
123 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | * Support multi-line options with backslashes (presumably with
2 | OptionCollection).
3 |
--------------------------------------------------------------------------------
/iniparse.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4 | # -*- encoding: utf-8 -*-
5 |
6 | Gem::Specification.new do |s|
7 | s.specification_version = 2 if s.respond_to? :specification_version=
8 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9 | s.rubygems_version = '1.3.5'
10 |
11 | ## Leave these as is they will be modified for you by the rake gemspec task.
12 | ## If your rubyforge_project name is different, then edit it and comment out
13 | ## the sub! line in the Rakefile
14 | s.name = 'iniparse'
15 | s.version = '1.5.0'
16 | s.date = '2020-02-27'
17 |
18 | s.summary = 'A pure Ruby library for parsing INI documents.'
19 | s.authors = ['Anthony Williams']
20 | s.email = 'hi@antw.me'
21 | s.homepage = 'http://github.com/antw/iniparse'
22 | s.licenses = ['MIT']
23 |
24 | s.description = 'A pure Ruby library for parsing INI documents. ' \
25 | 'Preserves the structure of the original document, ' \
26 | 'including whitespace and comments'
27 |
28 | s.require_paths = %w(lib)
29 |
30 | s.rdoc_options = ['--charset=UTF-8']
31 | s.extra_rdoc_files = %w(LICENSE README.rdoc)
32 |
33 | # Dependencies.
34 | s.add_development_dependency('rspec', '~> 3.4')
35 |
36 | # = MANIFEST =
37 | s.files = %w[
38 | CHANGELOG.md
39 | Gemfile
40 | LICENSE
41 | README.rdoc
42 | Rakefile
43 | TODO
44 | iniparse.gemspec
45 | lib/iniparse.rb
46 | lib/iniparse/document.rb
47 | lib/iniparse/generator.rb
48 | lib/iniparse/line_collection.rb
49 | lib/iniparse/lines.rb
50 | lib/iniparse/parser.rb
51 | ]
52 | # = MANIFEST =
53 | end
54 |
--------------------------------------------------------------------------------
/lib/iniparse.rb:
--------------------------------------------------------------------------------
1 | dir = File.expand_path('iniparse', File.dirname(__FILE__))
2 |
3 | require File.join(dir, 'document')
4 | require File.join(dir, 'generator')
5 | require File.join(dir, 'line_collection')
6 | require File.join(dir, 'lines')
7 | require File.join(dir, 'parser')
8 |
9 | module IniParse
10 | VERSION = '1.5.0'.freeze
11 |
12 | # A base class for IniParse errors.
13 | class IniParseError < StandardError; end
14 |
15 | # Raised if an error occurs parsing an INI document.
16 | class ParseError < IniParseError; end
17 |
18 | # Raised when an option line is found during parsing before the first
19 | # section.
20 | class NoSectionError < ParseError; end
21 |
22 | # Raised when a line is added to a collection which isn't allowed (e.g.
23 | # adding a Section line into an OptionCollection).
24 | class LineNotAllowed < IniParseError; end
25 |
26 | module_function
27 |
28 | # Parse given given INI document source +source+.
29 | #
30 | # See IniParse::Parser.parse
31 | #
32 | # ==== Parameters
33 | # source:: The source from the INI document.
34 | #
35 | # ==== Returns
36 | # IniParse::Document
37 | #
38 | def parse(source)
39 | IniParse::Parser.new(source.gsub(/(?:: The path to the INI document.
46 | #
47 | # ==== Returns
48 | # IniParse::Document
49 | #
50 | def open(path)
51 | document = parse(File.read(path))
52 | document.path = path
53 | document
54 | end
55 |
56 | # Creates a new IniParse::Document using the specification you provide.
57 | #
58 | # See IniParse::Generator.
59 | #
60 | # ==== Returns
61 | # IniParse::Document
62 | #
63 | def gen(&blk)
64 | IniParse::Generator.new.gen(&blk)
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/iniparse/document.rb:
--------------------------------------------------------------------------------
1 | module IniParse
2 | # Represents an INI document.
3 | class Document
4 | include Enumerable
5 |
6 | attr_reader :lines
7 | attr_accessor :path
8 |
9 | # Creates a new Document instance.
10 | def initialize(path = nil)
11 | @path = path
12 | @lines = IniParse::SectionCollection.new
13 | end
14 |
15 | # Enumerates through each Section in this document.
16 | #
17 | # Does not yield blank and comment lines by default; if you want _all_
18 | # lines to be yielded, pass true.
19 | #
20 | # ==== Parameters
21 | # include_blank:: Include blank/comment lines?
22 | #
23 | def each(*args, &blk)
24 | @lines.each(*args, &blk)
25 | end
26 |
27 | # Returns the section identified by +key+.
28 | #
29 | # Returns nil if there is no Section with the given key.
30 | #
31 | def [](key)
32 | @lines[key.to_s]
33 | end
34 |
35 | # Returns the section identified by +key+.
36 | #
37 | # If there is no Section with the given key it will be created.
38 | #
39 | def section(key)
40 | @lines[key.to_s] ||= Lines::Section.new(key.to_s)
41 | end
42 |
43 | # Deletes the section whose name matches the given +key+.
44 | #
45 | # Returns the document.
46 | #
47 | def delete(*args)
48 | @lines.delete(*args)
49 | self
50 | end
51 |
52 | # Returns this document as a string suitable for saving to a file.
53 | def to_ini
54 | string = @lines.to_a.map { |line| line.to_ini }.join($/)
55 | string = "#{ string }\n" unless string[-1] == "\n"
56 |
57 | string
58 | end
59 |
60 | alias_method :to_s, :to_ini
61 |
62 | # Returns a has representation of the INI with multi-line options
63 | # as an array
64 | def to_hash
65 | result = {}
66 | @lines.entries.each do |section|
67 | result[section.key] ||= {}
68 | section.entries.each do |option|
69 | opts = Array(option)
70 | val = opts.map { |o| o.respond_to?(:value) ? o.value : o }
71 | val = val.size > 1 ? val : val.first
72 | result[section.key][opts.first.key] = val
73 | end
74 | end
75 | result
76 | end
77 |
78 | alias_method :to_h, :to_hash
79 |
80 | # A human-readable version of the document, for debugging.
81 | def inspect
82 | sections = @lines.select { |l| l.is_a?(IniParse::Lines::Section) }
83 | "#"
84 | end
85 |
86 | # Returns true if a section with the given +key+ exists in this document.
87 | def has_section?(key)
88 | @lines.has_key?(key.to_s)
89 | end
90 |
91 | # Saves a copy of this Document to disk.
92 | #
93 | # If a path was supplied when the Document was initialized then nothing
94 | # needs to be given to Document#save. If Document was not given a file
95 | # path, or you wish to save the document elsewhere, supply a path when
96 | # calling Document#save.
97 | #
98 | # ==== Parameters
99 | # path:: A path to which this document will be saved.
100 | #
101 | # ==== Raises
102 | # IniParseError:: If your document couldn't be saved.
103 | #
104 | def save(path = nil)
105 | @path = path if path
106 | raise IniParseError, 'No path given to Document#save' if @path !~ /\S/
107 | File.open(@path, 'w') { |f| f.write(self.to_ini) }
108 | end
109 | end
110 | end
111 |
--------------------------------------------------------------------------------
/lib/iniparse/generator.rb:
--------------------------------------------------------------------------------
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:: 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 | # Add to a section if it already exists
114 | if @document.has_section?(name.to_s())
115 | @context = @document[name.to_s()]
116 | else
117 | @context = Lines::Section.new(name, line_options(opts))
118 | @document.lines << @context
119 | end
120 |
121 | if block_given?
122 | begin
123 | @in_section = true
124 | with_options(opts) { yield self }
125 | @context = @document
126 | blank()
127 | ensure
128 | @in_section = false
129 | end
130 | end
131 | end
132 |
133 | # Adds a new option to the current section.
134 | #
135 | # Can only be called as part of a section block, or after at least one
136 | # section has been added to the document.
137 | #
138 | # ==== Parameters
139 | # key:: The key (name) for this option.
140 | # value:: The option's value.
141 | # opts:: Extra options for the line (formatting, etc).
142 | #
143 | # ==== Raises
144 | # IniParse::NoSectionError::
145 | # If no section has been added to the document yet.
146 | #
147 | def option(key, value, opts = {})
148 | @context.lines << Lines::Option.new(
149 | key, value, line_options(opts)
150 | )
151 | rescue LineNotAllowed
152 | # Tried to add an Option to a Document.
153 | raise NoSectionError,
154 | 'Your INI document contains an option before the first section is ' \
155 | 'declared which is not allowed.'
156 | end
157 |
158 | # Adds a new comment line to the document.
159 | #
160 | # ==== Parameters
161 | # comment:: The text for the comment line.
162 | #
163 | def comment(comment, opts = {})
164 | @context.lines << Lines::Comment.new(
165 | line_options(opts.merge(:comment => comment))
166 | )
167 | end
168 |
169 | # Adds a new blank line to the document.
170 | def blank
171 | @context.lines << Lines::Blank.new
172 | end
173 |
174 | # Wraps lines, setting default options for each.
175 | def with_options(opts = {}) # :nodoc:
176 | opts = opts.dup
177 | opts.delete(:comment)
178 | @opt_stack.push( @opt_stack.last.merge(opts))
179 | yield self
180 | @opt_stack.pop
181 | end
182 |
183 | def method_missing(name, *args, &blk) # :nodoc:
184 | if m = name.to_s.match(/(.*)=$/)
185 | option(m[1], *args)
186 | else
187 | section(name.to_s, *args, &blk)
188 | end
189 | end
190 |
191 | #######
192 | private
193 | #######
194 |
195 | # Returns options for a line.
196 | #
197 | # If the context is a section, we use the section options as a base,
198 | # rather than the global defaults.
199 | #
200 | def line_options(given_opts) # :nodoc:
201 | @opt_stack.last.empty? ? given_opts : @opt_stack.last.merge(given_opts)
202 | end
203 | end
204 | end
205 |
--------------------------------------------------------------------------------
/lib/iniparse/line_collection.rb:
--------------------------------------------------------------------------------
1 | module IniParse
2 | # Represents a collection of lines in an INI document.
3 | #
4 | # LineCollection acts a bit like an Array/Hash hybrid, allowing arbitrary
5 | # lines to be added to the collection, but also indexes the keys of Section
6 | # and Option lines to enable O(1) lookup via LineCollection#[].
7 | #
8 | # The lines instances are stored in an array, +@lines+, while the index of
9 | # each Section/Option is held in a Hash, +@indicies+, keyed with the
10 | # Section/Option#key value (see LineCollection#[]=).
11 | #
12 | module LineCollection
13 | include Enumerable
14 |
15 | def initialize
16 | @lines = []
17 | @indicies = {}
18 | end
19 |
20 | # Retrive a value identified by +key+.
21 | def [](key)
22 | has_key?(key) ? @lines[ @indicies[key] ] : nil
23 | end
24 |
25 | # Set a +value+ identified by +key+.
26 | #
27 | # If a value with the given key already exists, the value will be replaced
28 | # with the new one, with the new value taking the position of the old.
29 | #
30 | def []=(key, value)
31 | key = key.to_s
32 |
33 | if has_key?(key)
34 | @lines[ @indicies[key] ] = value
35 | else
36 | @lines << value
37 | @indicies[key] = @lines.length - 1
38 | end
39 | end
40 |
41 | # Appends a line to the collection.
42 | #
43 | # Note that if you pass a line with a key already represented in the
44 | # collection, the old item will be replaced.
45 | #
46 | def <<(line)
47 | line.blank? ? (@lines << line) : (self[line.key] = line) ; self
48 | end
49 |
50 | alias_method :push, :<<
51 |
52 | # Enumerates through the collection.
53 | #
54 | # By default #each does not yield blank and comment lines.
55 | #
56 | # ==== Parameters
57 | # include_blank:: Include blank/comment lines?
58 | #
59 | def each(include_blank = false)
60 | return enum_for(:each, include_blank) unless block_given?
61 |
62 | @lines.each do |line|
63 | if include_blank || ! (line.is_a?(Array) ? line.empty? : line.blank?)
64 | yield(line)
65 | end
66 | end
67 | end
68 |
69 | # Removes the value identified by +key+.
70 | def delete(key)
71 | key = key.key if key.respond_to?(:key)
72 |
73 | unless (idx = @indicies[key]).nil?
74 | @indicies.delete(key)
75 | @indicies.each { |k,v| @indicies[k] = v -= 1 if v > idx }
76 | @lines.delete_at(idx)
77 | end
78 | end
79 |
80 | # Returns whether +key+ is in the collection.
81 | def has_key?(*args)
82 | @indicies.has_key?(*args)
83 | end
84 |
85 | # Return an array containing the keys for the lines added to this
86 | # collection.
87 | def keys
88 | map { |line| line.key }
89 | end
90 |
91 | # Returns this collection as an array. Includes blank and comment lines.
92 | def to_a
93 | @lines.dup
94 | end
95 |
96 | # Returns this collection as a hash. Does not contain blank and comment
97 | # lines.
98 | def to_hash
99 | Hash[ *(map { |line| [line.key, line] }).flatten ]
100 | end
101 |
102 | alias_method :to_h, :to_hash
103 | end
104 |
105 | # A implementation of LineCollection used for storing (mostly) Option
106 | # instances contained within a Section.
107 | #
108 | # Since it is assumed that an INI document will only represent a section
109 | # once, if SectionCollection encounters a Section key already held in the
110 | # collection, the existing section is merged with the new one (see
111 | # IniParse::Lines::Section#merge!).
112 | class SectionCollection
113 | include LineCollection
114 |
115 | def <<(line)
116 | if line.kind_of?(IniParse::Lines::Option) || (has_key?('__anonymous__') && (line.blank? || line.kind_of?(IniParse::Lines::Comment)))
117 | option = line
118 | line = IniParse::Lines::AnonymousSection.new
119 |
120 | line.lines << option if option
121 | end
122 |
123 | if line.blank? || (! has_key?(line.key))
124 | super # Adding a new section, comment or blank line.
125 | else
126 | self[line.key].merge!(line)
127 | end
128 |
129 | self
130 | end
131 | end
132 |
133 | # A implementation of LineCollection used for storing (mostly) Option
134 | # instances contained within a Section.
135 | #
136 | # Whenever OptionCollection encounters an Option key already held in the
137 | # collection, it treats it as a duplicate. This means that instead of
138 | # overwriting the existing value, the value is changed to an array
139 | # containing the previous _and_ the new Option instances.
140 | class OptionCollection
141 | include LineCollection
142 |
143 | # Appends a line to the collection.
144 | #
145 | # If you push an Option with a key already represented in the collection,
146 | # the previous Option will not be overwritten, but treated as a duplicate.
147 | #
148 | # ==== Parameters
149 | # line:: The line to be added to this section.
150 | #
151 | def <<(line)
152 | if line.kind_of?(IniParse::Lines::Section)
153 | raise IniParse::LineNotAllowed,
154 | "You can't add a Section to an OptionCollection."
155 | end
156 |
157 | if line.blank? || (! has_key?(line.key))
158 | super # Adding a new option, comment or blank line.
159 | else
160 | self[line.key] = [self[line.key], line].flatten
161 | end
162 |
163 | self
164 | end
165 |
166 | def each(*args)
167 | return enum_for(:each, *args) unless block_given?
168 |
169 | super(*args) do |value|
170 | if value.is_a?(Array)
171 | value.each { |item| yield(item) }
172 | else
173 | yield value
174 | end
175 | end
176 | end
177 |
178 | # Return an array containing the keys for the lines added to this
179 | # collection.
180 | def keys
181 | map(&:key).uniq
182 | end
183 | end
184 | end
185 |
--------------------------------------------------------------------------------
/lib/iniparse/lines.rb:
--------------------------------------------------------------------------------
1 | module IniParse
2 | module Lines
3 | # A base class from which other line types should inherit.
4 | module Line
5 | # ==== Parameters
6 | # opts:: Extra options for the line.
7 | #
8 | def initialize(opts = {})
9 | @comment = opts.fetch(:comment, nil)
10 | @comment_sep = opts.fetch(:comment_sep, ';')
11 | @comment_prefix = opts.fetch(:comment_prefix, ' ')
12 | @comment_offset = opts.fetch(:comment_offset, 0)
13 | @indent = opts.fetch(:indent, '')
14 | @option_sep = opts.fetch(:option_sep, nil)
15 | end
16 |
17 | # Returns if this line has an inline comment.
18 | def has_comment?
19 | not @comment.nil?
20 | end
21 |
22 | # Returns this line as a string as it would be represented in an INI
23 | # document.
24 | def to_ini
25 | [*line_contents].map { |ini|
26 | if has_comment?
27 | ini += ' ' if ini =~ /\S/ # not blank
28 | ini = ini.ljust(@comment_offset)
29 | ini += comment
30 | end
31 | @indent + ini
32 | }.join "\n"
33 | end
34 |
35 | # Returns the contents for this line.
36 | def line_contents
37 | ''
38 | end
39 |
40 | # Returns the inline comment for this line. Includes the comment
41 | # separator at the beginning of the string.
42 | def comment
43 | "#{ @comment_sep }#{ @comment_prefix }#{ @comment }"
44 | end
45 |
46 | # Returns whether this is a line which has no data.
47 | def blank?
48 | false
49 | end
50 |
51 | # Returns the options used to create the line
52 | def options
53 | {
54 | comment: @comment,
55 | comment_sep: @comment_sep,
56 | comment_prefix: @comment_prefix,
57 | comment_offset: @comment_offset,
58 | indent: @indent,
59 | option_sep: @option_sep
60 | }
61 | end
62 | end
63 |
64 | # Represents a section header in an INI document. Section headers consist
65 | # of a string of characters wrapped in square brackets.
66 | #
67 | # [section]
68 | # key=value
69 | # etc
70 | # ...
71 | #
72 | class Section
73 | include Line
74 |
75 | @regex = /^\[ # Opening bracket
76 | ([^\]]+) # Section name
77 | \]$ # Closing bracket
78 | /x
79 |
80 | attr_accessor :key
81 | attr_reader :lines
82 |
83 | include Enumerable
84 |
85 | # ==== Parameters
86 | # key:: The section name.
87 | # opts:: Extra options for the line.
88 | #
89 | def initialize(key, opts = {})
90 | super(opts)
91 | @key = key.to_s
92 | @lines = IniParse::OptionCollection.new
93 | end
94 |
95 | def self.parse(line, opts)
96 | if m = @regex.match(line)
97 | [:section, m[1], opts]
98 | end
99 | end
100 |
101 | # Returns this line as a string as it would be represented in an INI
102 | # document. Includes options, comments and blanks.
103 | def to_ini
104 | coll = lines.to_a
105 |
106 | if coll.any?
107 | [*super,coll.to_a.map do |line|
108 | if line.kind_of?(Array)
109 | line.map { |dup_line| dup_line.to_ini }.join($/)
110 | else
111 | line.to_ini
112 | end
113 | end].join($/)
114 | else
115 | super
116 | end
117 | end
118 |
119 | # Enumerates through each Option in this section.
120 | #
121 | # Does not yield blank and comment lines by default; if you want _all_
122 | # lines to be yielded, pass true.
123 | #
124 | # ==== Parameters
125 | # include_blank:: Include blank/comment lines?
126 | #
127 | def each(*args, &blk)
128 | @lines.each(*args, &blk)
129 | end
130 |
131 | # Adds a new option to this section, or updates an existing one.
132 | #
133 | # Note that +[]=+ has no knowledge of duplicate options and will happily
134 | # overwrite duplicate options with your new value.
135 | #
136 | # section['an_option']
137 | # # => ['duplicate one', 'duplicate two', ...]
138 | # section['an_option'] = 'new value'
139 | # section['an_option]
140 | # # => 'new value'
141 | #
142 | # If you do not wish to overwrite duplicates, but wish instead for your
143 | # new option to be considered a duplicate, use +add_option+ instead.
144 | #
145 | def []=(key, value)
146 | line = @lines[key.to_s]
147 | opts = {}
148 | if line.kind_of?(Array)
149 | opts = line.first.options
150 | elsif line.respond_to? :options
151 | opts = line.options
152 | end
153 | @lines[key.to_s] = IniParse::Lines::Option.new(key.to_s, value, opts)
154 | end
155 |
156 | # Returns the value of an option identified by +key+.
157 | #
158 | # Returns nil if there is no corresponding option. If the key provided
159 | # matches a set of duplicate options, an array will be returned containing
160 | # the value of each option.
161 | #
162 | def [](key)
163 | key = key.to_s
164 |
165 | if @lines.has_key?(key)
166 | if (match = @lines[key]).kind_of?(Array)
167 | match.map { |line| line.value }
168 | else
169 | match.value
170 | end
171 | end
172 | end
173 |
174 | # Deletes the option identified by +key+.
175 | #
176 | # Returns the section.
177 | #
178 | def delete(*args)
179 | @lines.delete(*args)
180 | self
181 | end
182 |
183 | # Like [], except instead of returning just the option value, it returns
184 | # the matching line instance.
185 | #
186 | # Will return an array of lines if the key matches a set of duplicates.
187 | #
188 | def option(key)
189 | @lines[key.to_s]
190 | end
191 |
192 | # Returns true if an option with the given +key+ exists in this section.
193 | def has_option?(key)
194 | @lines.has_key?(key.to_s)
195 | end
196 |
197 | # Merges section +other+ into this one. If the section being merged into
198 | # this one contains options with the same key, they will be handled as
199 | # duplicates.
200 | #
201 | # ==== Parameters
202 | # other:: The section to merge into this one.
203 | #
204 | def merge!(other)
205 | other.lines.each(true) do |line|
206 | if line.kind_of?(Array)
207 | line.each { |duplicate| @lines << duplicate }
208 | else
209 | @lines << line
210 | end
211 | end
212 | end
213 |
214 | #######
215 | private
216 | #######
217 |
218 | def line_contents
219 | '[%s]' % key
220 | end
221 | end
222 |
223 | # Stores options which appear at the beginning of a file, without a
224 | # preceding section.
225 | class AnonymousSection < Section
226 | def initialize
227 | super('__anonymous__')
228 | end
229 |
230 | def to_ini
231 | # Remove the leading space which is added by joining the blank line
232 | # content with the options.
233 | super.gsub(/\A\n/, '')
234 | end
235 |
236 | #######
237 | private
238 | #######
239 |
240 | def line_contents
241 | ''
242 | end
243 | end
244 |
245 | # Represents probably the most common type of line in an INI document:
246 | # an option. Consists of a key and value, usually separated with an =.
247 | #
248 | # key = value
249 | #
250 | class Option
251 | include Line
252 |
253 | @regex = /^\s*([^=]+?) # Option, not greedy
254 | (\s*=\s*) # Separator, greedy
255 | (.*?)$ # Value
256 | /x
257 |
258 | attr_accessor :key, :value
259 |
260 | # ==== Parameters
261 | # key:: The option key.
262 | # value:: The value for this option.
263 | # opts:: Extra options for the line.
264 | #
265 | def initialize(key, value, opts = {})
266 | super(opts)
267 | @key, @value = key.to_s, value
268 | @option_sep = opts.fetch(:option_sep, ' = ')
269 | end
270 |
271 | def self.parse(line, opts)
272 | if m = @regex.match(line)
273 | opts[:option_sep] = m[2]
274 | [:option, m[1].strip, typecast(m[3].strip), opts]
275 | end
276 | end
277 |
278 | # Attempts to typecast values.
279 | def self.typecast(value)
280 | case value
281 | when /^\s*$/ then nil
282 | when /^-?(?:\d|[1-9]\d+)$/ then Integer(value)
283 | when /^-?(?:\d|[1-9]\d+)(?:\.\d+)?(?:e[+-]?\d+)?$/i then Float(value)
284 | when /^true$/i then true
285 | when /^false$/i then false
286 | else value
287 | end
288 | end
289 |
290 | #######
291 | private
292 | #######
293 |
294 | # returns an array to support multiple lines or a single one at once
295 | # because of options key duplication
296 | def line_contents
297 | if value.kind_of?(Array)
298 | value.map { |v, i| "#{key}#{@option_sep}#{v}" }
299 | else
300 | "#{key}#{@option_sep}#{value}"
301 | end
302 | end
303 | end
304 |
305 | # Represents a blank line. Used so that we can preserve blank lines when
306 | # writing back to the file.
307 | class Blank
308 | include Line
309 |
310 | def blank?
311 | true
312 | end
313 |
314 | def self.parse(line, opts)
315 | if line !~ /\S/ # blank
316 | if opts[:comment].nil?
317 | [:blank]
318 | else
319 | [:comment, opts[:comment], opts]
320 | end
321 | end
322 | end
323 | end
324 |
325 | # Represents a comment. Comment lines begin with a semi-colon or hash.
326 | #
327 | # ; this is a comment
328 | # # also a comment
329 | #
330 | class Comment < Blank
331 | # Returns if this line has an inline comment.
332 | #
333 | # Being a Comment this will always return true, even if the comment
334 | # is nil. This would be the case if the line starts with a comment
335 | # seperator, but has no comment text. See spec/fixtures/smb.ini for a
336 | # real-world example.
337 | #
338 | def has_comment?
339 | true
340 | end
341 |
342 | # Returns the inline comment for this line. Includes the comment
343 | # separator at the beginning of the string.
344 | #
345 | # In rare cases where a comment seperator appeared in the original file,
346 | # but without a comment, just the seperator will be returned.
347 | #
348 | def comment
349 | @comment !~ /\S/ ? @comment_sep : super
350 | end
351 | end
352 | end # Lines
353 | end # IniParse
354 |
--------------------------------------------------------------------------------
/lib/iniparse/parser.rb:
--------------------------------------------------------------------------------
1 | module IniParse
2 | class Parser
3 |
4 | # Returns the line types.
5 | #
6 | # ==== Returns
7 | # Array
8 | #
9 | def self.parse_types
10 | @@parse_types ||= []
11 | end
12 |
13 | # Sets the line types. Handy if you want to add your own custom Line
14 | # classes.
15 | #
16 | # ==== Parameters
17 | # types:: An array containing Line classes.
18 | #
19 | def self.parse_types=(types)
20 | parse_types.replace(types)
21 | end
22 |
23 | self.parse_types = [ IniParse::Lines::Section,
24 | IniParse::Lines::Option, IniParse::Lines::Blank ]
25 |
26 | # Creates a new Parser instance for parsing string +source+.
27 | #
28 | # ==== Parameters
29 | # source:: The source string.
30 | #
31 | def initialize(source)
32 | @source = source.dup
33 | end
34 |
35 | # Parses the source string and returns the resulting data structure.
36 | #
37 | # ==== Returns
38 | # IniParse::Document
39 | #
40 | def parse
41 | IniParse::Generator.gen do |generator|
42 | @source.split("\n", -1).each do |line|
43 | generator.send(*Parser.parse_line(line))
44 | end
45 | end
46 | end
47 |
48 | class << self
49 | # Takes a raw line from an INI document, striping out any inline
50 | # comment, and indent, then returns the appropriate tuple so that the
51 | # Generator instance can add the line to the Document.
52 | #
53 | # ==== Raises
54 | # IniParse::ParseError: If the line could not be parsed.
55 | #
56 | def parse_line(line)
57 | sanitized, opts = strip_indent(*strip_comment(line, {}))
58 |
59 | parsed = nil
60 | @@parse_types.each do |type|
61 | break if (parsed = type.parse(sanitized, opts))
62 | end
63 |
64 | if parsed.nil?
65 | raise IniParse::ParseError,
66 | "A line of your INI document could not be parsed to a " \
67 | "LineType: #{line.inspect}."
68 | end
69 |
70 | parsed
71 | end
72 |
73 | #######
74 | private
75 | #######
76 |
77 | # Strips in inline comment from a line (or value), removes trailing
78 | # whitespace and sets the comment options as applicable.
79 | def strip_comment(line, opts)
80 | if m = /^(^)(?:(;|\#)(\s*)(.*))$$/.match(line) ||
81 | m = /^(.*?)(?:\s+(;|\#)(\s*)(.*))$/.match(line) # Comment lines.
82 | opts[:comment] = m[4].rstrip
83 | opts[:comment_prefix] = m[3]
84 | opts[:comment_sep] = m[2]
85 | # Remove the line content (since an option value may contain a
86 | # semi-colon) _then_ get the index of the comment separator.
87 | opts[:comment_offset] =
88 | line[(m[1].length..-1)].index(m[2]) + m[1].length
89 |
90 | line = m[1]
91 | else
92 | line = line.chomp
93 | end
94 |
95 | [line, opts]
96 | end
97 |
98 | # Removes any leading whitespace from a line, and adds it to the options
99 | # hash.
100 | def strip_indent(line, opts)
101 | if m = /^(\s+).*$/.match(line)
102 | line.lstrip!
103 | opts[:indent] = m[1]
104 | end
105 |
106 | [line, opts]
107 | end
108 | end
109 | end # Parser
110 | end # IniParse
111 |
--------------------------------------------------------------------------------
/spec/document_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "IniParse::Document" do
4 | it 'should have a +lines+ reader' do
5 | methods = IniParse::Document.instance_methods.map { |m| m.to_sym }
6 | expect(methods).to include(:lines)
7 | end
8 |
9 | it 'should not have a +lines+ writer' do
10 | methods = IniParse::Document.instance_methods.map { |m| m.to_sym }
11 | expect(methods).not_to include(:lines=)
12 | end
13 |
14 | it 'should delegate #[] to +lines+' do
15 | doc = IniParse::Document.new
16 | expect(doc.lines).to receive(:[]).with('key')
17 | doc['key']
18 | end
19 |
20 | it 'should call #each to +lines+' do
21 | doc = IniParse::Document.new
22 | expect(doc.lines).to receive(:each)
23 | doc.each { |l| }
24 | end
25 |
26 | it 'should be enumerable' do
27 | expect(IniParse::Document.included_modules).to include(Enumerable)
28 |
29 | sections = [
30 | IniParse::Lines::Section.new('first section'),
31 | IniParse::Lines::Section.new('second section')
32 | ]
33 |
34 | doc = IniParse::Document.new
35 | doc.lines << sections[0] << sections[1]
36 |
37 | expect(doc.map { |line| line }).to eq(sections)
38 | end
39 |
40 | describe '#has_section?' do
41 | before(:all) do
42 | @doc = IniParse::Document.new
43 | @doc.lines << IniParse::Lines::Section.new('first section')
44 | @doc.section('another section')
45 | end
46 |
47 | it 'should return true if a section with the given key exists' do
48 | expect(@doc).to have_section('first section')
49 | expect(@doc).to have_section('another section')
50 | end
51 |
52 | it 'should return true if no section with the given key exists' do
53 | expect(@doc).not_to have_section('second section')
54 | end
55 | end
56 |
57 | describe '#delete' do
58 | let(:document) do
59 | IniParse::Generator.gen do |doc|
60 | doc.section('first') do |section|
61 | section.alpha = 'bravo'
62 | section.charlie = 'delta'
63 | end
64 |
65 | doc.section('second') do |section|
66 | section.echo = 'foxtrot'
67 | section.golf = 'hotel'
68 | end
69 | end
70 | end
71 |
72 | it 'removes the section given a key' do
73 | expect { document.delete('first') }.
74 | to change { document['first'] }.to(nil)
75 | end
76 |
77 | it 'removes the section given a Section' do
78 | expect { document.delete(document['first']) }.
79 | to change { document['first'] }.to(nil)
80 | end
81 |
82 | it 'removes the lines' do
83 | expect { document.delete('first') }.
84 | to change { document.to_ini.match(/alpha/) }.to(nil)
85 | end
86 |
87 | it 'returns the document' do
88 | expect(document.delete('first')).to eql(document)
89 | end
90 | end
91 |
92 | describe '#to_ini' do
93 | let(:document) do
94 | IniParse.parse(<<-EOF.gsub(/^\s+/, ''))
95 | [one]
96 | alpha = bravo
97 | [two]
98 | chalie = delta
99 | EOF
100 | end
101 |
102 | context 'when the document has a trailing Blank line' do
103 | it 'outputs the newline to the output string' do
104 | expect(document.to_ini).to match(/\n\Z/)
105 | end
106 |
107 | it 'does not add a second newline to the output string' do
108 | expect(document.to_ini).to_not match(/\n\n\Z/)
109 | end
110 | end # when the document has a trailing Blank line
111 |
112 | context 'when the document has no trailing Blank line' do
113 | before { document.delete('two') }
114 |
115 | it 'adds a newline to the output string' do
116 | expect(document.to_ini).to match(/\n\Z/)
117 | end
118 | end # when the document has no trailing Blank line
119 | end # to_ini
120 |
121 | describe '#save' do
122 | describe 'when no path is given to save' do
123 | it 'should save the INI document if a path was given when initialized' do
124 | doc = IniParse::Document.new('/a/path/to/a/file.ini')
125 | expect(File).to receive(:open).with('/a/path/to/a/file.ini', 'w')
126 | doc.save
127 | end
128 |
129 | it 'should raise IniParseError if no path was given when initialized' do
130 | expect { IniParse::Document.new.save }.to \
131 | raise_error(IniParse::IniParseError)
132 | end
133 | end
134 |
135 | describe 'when a path is given to save' do
136 | it "should update the document's +path+" do
137 | allow(File).to receive(:open).and_return(true)
138 | doc = IniParse::Document.new('/a/path/to/a/file.ini')
139 | doc.save('/a/new/path.ini')
140 | expect(doc.path).to eq('/a/new/path.ini')
141 | end
142 |
143 | it 'should save the INI document to the given path' do
144 | expect(File).to receive(:open).with('/a/new/path.ini', 'w')
145 | IniParse::Document.new('/a/path/to/a/file.ini').save('/a/new/path.ini')
146 | end
147 |
148 | it 'should raise IniParseError if no path was given when initialized' do
149 | expect { IniParse::Document.new.save }.to \
150 | raise_error(IniParse::IniParseError)
151 | end
152 | end
153 | end
154 | end
155 |
--------------------------------------------------------------------------------
/spec/fixture_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "IniParse" do
4 | describe 'openttd.ini fixture' do
5 | before(:all) do
6 | @fixture = fixture('openttd.ini')
7 | end
8 |
9 | it 'should parse without any errors' do
10 | expect { IniParse.parse(@fixture) }.not_to raise_error
11 | end
12 |
13 | it 'should have the correct sections' do
14 | expect(IniParse.parse(fixture('openttd.ini')).lines.keys).to eq([
15 | 'misc', 'music', 'difficulty', 'game_creation', 'vehicle',
16 | 'construction', 'station', 'economy', 'pf', 'order', 'gui', 'ai',
17 | 'locale', 'network', 'currency', 'servers', 'bans', 'news_display',
18 | 'version', 'preset-J', 'newgrf', 'newgrf-static'
19 | ])
20 | end
21 |
22 | it 'should have the correct options' do
23 | # Test the keys from one section.
24 | doc = IniParse.parse(@fixture)
25 | section = doc['misc']
26 |
27 | expect(section.lines.keys).to eq([
28 | 'display_opt', 'news_ticker_sound', 'fullscreen', 'language',
29 | 'resolution', 'screenshot_format', 'savegame_format',
30 | 'rightclick_emulate', 'small_font', 'medium_font', 'large_font',
31 | 'small_size', 'medium_size', 'large_size', 'small_aa', 'medium_aa',
32 | 'large_aa', 'sprite_cache_size', 'player_face',
33 | 'transparency_options', 'transparency_locks', 'invisibility_options',
34 | 'keyboard', 'keyboard_caps'
35 | ])
36 |
37 | # Test some of the options.
38 | expect(section['display_opt']).to eq('SHOW_TOWN_NAMES|SHOW_STATION_NAMES|SHOW_SIGNS|FULL_ANIMATION|FULL_DETAIL|WAYPOINTS')
39 | expect(section['news_ticker_sound']).to be_falsey
40 | expect(section['language']).to eq('english_US.lng')
41 | expect(section['resolution']).to eq('1680,936')
42 | expect(section['large_size']).to eq(16)
43 |
44 | # Test some other options.
45 | expect(doc['currency']['suffix']).to eq('" credits"')
46 | expect(doc['news_display']['production_nobody']).to eq('summarized')
47 | expect(doc['version']['version_number']).to eq('070039B0')
48 |
49 | expect(doc['preset-J']['gcf/1_other/BlackCC/mauvetoblackw.grf']).to be_nil
50 | expect(doc['preset-J']['gcf/1_other/OpenGFX/OpenGFX_-_newFaces_v0.1.grf']).to be_nil
51 | end
52 |
53 | it 'should be identical to the original when calling #to_ini' do
54 | expect(IniParse.parse(@fixture).to_ini).to eq(@fixture)
55 | end
56 | end
57 |
58 | describe 'race07.ini fixture' do
59 | before(:all) do
60 | @fixture = fixture('race07.ini')
61 | end
62 |
63 | it 'should parse without any errors' do
64 | expect { IniParse.parse(@fixture) }.not_to raise_error
65 | end
66 |
67 | it 'should have the correct sections' do
68 | expect(IniParse.parse(fixture('race07.ini')).lines.keys).to eq([
69 | 'Header', 'Race', 'Slot010', 'Slot016', 'Slot013', 'Slot018',
70 | 'Slot002', 'END'
71 | ])
72 | end
73 |
74 | it 'should have the correct options' do
75 | # Test the keys from one section.
76 | doc = IniParse.parse(@fixture)
77 | section = doc['Slot010']
78 |
79 | expect(section.lines.keys).to eq([
80 | 'Driver', 'SteamUser', 'SteamId', 'Vehicle', 'Team', 'QualTime',
81 | 'Laps', 'Lap', 'LapDistanceTravelled', 'BestLap', 'RaceTime'
82 | ])
83 |
84 | # Test some of the options.
85 | expect(section['Driver']).to eq('Mark Voss')
86 | expect(section['SteamUser']).to eq('mvoss')
87 | expect(section['SteamId']).to eq(1865369)
88 | expect(section['Vehicle']).to eq('Chevrolet Lacetti 2007')
89 | expect(section['Team']).to eq('TEMPLATE_TEAM')
90 | expect(section['QualTime']).to eq('1:37.839')
91 | expect(section['Laps']).to eq(13)
92 | expect(section['LapDistanceTravelled']).to eq(3857.750244)
93 | expect(section['BestLap']).to eq('1:38.031')
94 | expect(section['RaceTime']).to eq('0:21:38.988')
95 |
96 | expect(section['Lap']).to eq([
97 | '(0, -1.000, 1:48.697)', '(1, 89.397, 1:39.455)',
98 | '(2, 198.095, 1:38.060)', '(3, 297.550, 1:38.632)',
99 | '(4, 395.610, 1:38.031)', '(5, 494.242, 1:39.562)',
100 | '(6, 592.273, 1:39.950)', '(7, 691.835, 1:38.366)',
101 | '(8, 791.785, 1:39.889)', '(9, 890.151, 1:39.420)',
102 | '(10, 990.040, 1:39.401)', '(11, 1089.460, 1:39.506)',
103 | '(12, 1188.862, 1:40.017)'
104 | ])
105 |
106 | expect(doc['Header']['Version']).to eq('1.1.1.14')
107 | expect(doc['Header']['TimeString']).to eq('2008/09/13 23:26:32')
108 | expect(doc['Header']['Aids']).to eq('0,0,0,0,0,1,1,0,0')
109 |
110 | expect(doc['Race']['AIDB']).to eq('GameData\Locations\Anderstorp_2007\2007_ANDERSTORP.AIW')
111 | expect(doc['Race']['Race Length']).to eq(0.1)
112 | end
113 |
114 | it 'should be identical to the original when calling #to_ini' do
115 | pending('awaiting presevation (or lack) of whitespace around =')
116 | expect(IniParse.parse(@fixture).to_ini).to eq(@fixture)
117 | end
118 | end
119 |
120 | describe 'smb.ini fixture' do
121 | before(:all) do
122 | @fixture = fixture('smb.ini')
123 | end
124 |
125 | it 'should parse without any errors' do
126 | expect { IniParse.parse(@fixture) }.not_to raise_error
127 | end
128 |
129 | it 'should have the correct sections' do
130 | expect(IniParse.parse(@fixture).lines.keys).to eq([
131 | 'global', 'printers'
132 | ])
133 | end
134 |
135 | it 'should have the correct options' do
136 | # Test the keys from one section.
137 | doc = IniParse.parse(@fixture)
138 | section = doc['global']
139 |
140 | expect(section.lines.keys).to eq([
141 | 'debug pid', 'log level', 'server string', 'printcap name',
142 | 'printing', 'encrypt passwords', 'use spnego', 'passdb backend',
143 | 'idmap domains', 'idmap config default: default',
144 | 'idmap config default: backend', 'idmap alloc backend',
145 | 'idmap negative cache time', 'map to guest', 'guest account',
146 | 'unix charset', 'display charset', 'dos charset', 'vfs objects',
147 | 'os level', 'domain master', 'max xmit', 'use sendfile',
148 | 'stream support', 'ea support', 'darwin_streams:brlm',
149 | 'enable core files', 'usershare max shares', 'usershare path',
150 | 'usershare owner only', 'usershare allow guests',
151 | 'usershare allow full config', 'com.apple:filter shares by access',
152 | 'obey pam restrictions', 'acl check permissions',
153 | 'name resolve order', 'include'
154 | ])
155 |
156 | expect(section['display charset']).to eq('UTF-8-MAC')
157 | expect(section['vfs objects']).to eq('darwinacl,darwin_streams')
158 | expect(section['usershare path']).to eq('/var/samba/shares')
159 | end
160 |
161 | it 'should be identical to the original when calling #to_ini' do
162 | expect(IniParse.parse(@fixture).to_ini).to eq(@fixture)
163 | end
164 | end
165 |
166 | describe 'authconfig.ini fixture' do
167 | before(:all) do
168 | @fixture = fixture('authconfig.ini')
169 | end
170 |
171 | it 'should be identical to the original when calling #to_ini' do
172 | expect(IniParse.parse(@fixture).to_ini).to eq(@fixture)
173 | end
174 | end
175 |
176 | describe 'option before section fixture' do
177 | before(:all) do
178 | @fixture = fixture(:option_before_section)
179 | end
180 |
181 | it 'should be identical to the original when calling #to_ini' do
182 | expect(IniParse.parse(@fixture).to_ini).to eq(@fixture)
183 | end
184 | end
185 |
186 | describe 'DOS line endings' do
187 | before(:all) do
188 | @fixture = fixture(:dos_endings)
189 | end
190 |
191 | it 'should have the correct sections' do
192 | expect(IniParse.parse(@fixture).lines.keys).to eq(%w[database])
193 | end
194 |
195 | it 'should have the correct options' do
196 | # Test the keys from one section.
197 | doc = IniParse.parse(@fixture)
198 | section = doc['database']
199 |
200 | expect(section.lines.keys).to eq(%w[first second])
201 |
202 | expect(section['first']).to eq(true)
203 | expect(section['second']).to eq(false)
204 | end
205 |
206 | pending 'should be identical to the original when calling #to_ini' do
207 | expect(IniParse.parse(@fixture).to_ini).to eq(@fixture)
208 | end
209 | end
210 |
211 | describe 'anonymous-order.ini fixture' do
212 | # https://github.com/antw/iniparse/issues/17
213 | let(:raw) { fixture(:anon_section_with_comments) }
214 |
215 | it 'should be identical to the original when calling #to_ini' do
216 | expect(IniParse.parse(raw).to_ini).to eq(raw)
217 | end
218 | end
219 | end
220 |
--------------------------------------------------------------------------------
/spec/fixtures/authconfig.ini:
--------------------------------------------------------------------------------
1 | [global]
2 | #--authconfig--start-line--
3 |
4 | # Generated by authconfig on 2014/04/08 15:14:28
5 | # DO NOT EDIT THIS SECTION (delimited by --start-line--/--end-line--)
6 | # Any modification may be deleted or altered by authconfig in future
7 |
8 | template homedir = /home/%U
9 | template shell = /bin/bash
10 | winbind use default domain = true
11 | winbind offline logon = false
12 |
13 | #--authconfig--end-line--
14 |
--------------------------------------------------------------------------------
/spec/fixtures/openttd.ini:
--------------------------------------------------------------------------------
1 |
2 | [misc]
3 | display_opt = SHOW_TOWN_NAMES|SHOW_STATION_NAMES|SHOW_SIGNS|FULL_ANIMATION|FULL_DETAIL|WAYPOINTS
4 | news_ticker_sound = false
5 | fullscreen = false
6 | language = english_US.lng
7 | resolution = 1680,936
8 | screenshot_format =
9 | savegame_format =
10 | rightclick_emulate = false
11 | small_font =
12 | medium_font =
13 | large_font =
14 | small_size = 6
15 | medium_size = 10
16 | large_size = 16
17 | small_aa = false
18 | medium_aa = false
19 | large_aa = false
20 | sprite_cache_size = 4
21 | player_face = 0
22 | transparency_options = 3
23 | transparency_locks = 0
24 | invisibility_options = 30
25 | keyboard =
26 | keyboard_caps =
27 |
28 | [music]
29 | playlist = 0
30 | music_vol = 0
31 | effect_vol = 0
32 | custom_1 = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
33 | custom_2 = 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
34 | playing = false
35 | shuffle = false
36 | extmidi = timidity
37 |
38 | [difficulty]
39 | max_no_competitors = 0
40 | competitor_start_time = 2
41 | number_towns = 1
42 | number_industries = 2
43 | max_loan = 500000
44 | initial_interest = 2
45 | vehicle_costs = 0
46 | competitor_speed = 2
47 | competitor_intelligence = 0
48 | vehicle_breakdowns = 1
49 | subsidy_multiplier = 2
50 | construction_cost = 0
51 | terrain_type = 0
52 | quantity_sea_lakes = 0
53 | economy = 0
54 | line_reverse_mode = 0
55 | disasters = 0
56 | town_council_tolerance = 0
57 | diff_level = 3
58 |
59 | [game_creation]
60 | town_name = english
61 | landscape = temperate
62 | snow_line = 56
63 | snow_line_height = 7
64 | starting_year = 1960
65 | land_generator = 1
66 | oil_refinery_limit = 32
67 | tgen_smoothness = 0
68 | generation_seed = 83252223
69 | tree_placer = 2
70 | heightmap_rotation = 0
71 | se_flat_world_height = 0
72 | map_x = 9
73 | map_y = 9
74 |
75 | [vehicle]
76 | road_side = left
77 | realistic_acceleration = true
78 | mammoth_trains = true
79 | never_expire_vehicles = true
80 | max_trains = 500
81 | max_roadveh = 500
82 | max_aircraft = 200
83 | max_ships = 300
84 | servint_ispercent = false
85 | servint_trains = 150
86 | servint_roadveh = 150
87 | servint_ships = 360
88 | servint_aircraft = 100
89 | wagon_speed_limits = true
90 | disable_elrails = false
91 | freight_trains = 1
92 | plane_speed = 4
93 | dynamic_engines = false
94 | extend_vehicle_life = 0
95 |
96 | [construction]
97 | build_on_slopes = true
98 | autoslope = true
99 | extra_dynamite = false
100 | longbridges = true
101 | signal_side = true
102 | road_stop_on_town_road = true
103 | raw_industry_construction = 1
104 |
105 | [station]
106 | always_small_airport = true
107 | join_stations = true
108 | nonuniform_stations = true
109 | station_spread = 12
110 | modified_catchment = true
111 | adjacent_stations = true
112 |
113 | [economy]
114 | town_layout = 2
115 | station_noise_level = false
116 | inflation = true
117 | multiple_industry_per_town = true
118 | same_industry_close = true
119 | bribe = true
120 | exclusive_rights = true
121 | give_money = true
122 | smooth_economy = true
123 | allow_shares = false
124 | town_growth_rate = 1
125 | larger_towns = 6
126 | initial_city_size = 2
127 | mod_road_rebuild = true
128 | dist_local_authority = 20
129 | town_noise_population = 800,2000,4000
130 |
131 | [pf]
132 | forbid_90_deg = false
133 | roadveh_queue = true
134 | pathfinder_for_trains = 2
135 | pathfinder_for_roadvehs = 2
136 | pathfinder_for_ships = 0
137 | wait_oneway_signal = 15
138 | wait_twoway_signal = 41
139 | wait_for_pbs_path = 30
140 | reserve_paths = false
141 | path_backoff_interval = 20
142 | opf.pf_maxlength = 4096
143 | opf.pf_maxdepth = 48
144 | npf.npf_max_search_nodes = 10000
145 | npf.npf_rail_firstred_penalty = 1000
146 | npf.npf_rail_firstred_exit_penalty = 10000
147 | npf.npf_rail_lastred_penalty = 1000
148 | npf.npf_rail_station_penalty = 100
149 | npf.npf_rail_slope_penalty = 100
150 | npf.npf_rail_curve_penalty = 1
151 | npf.npf_rail_depot_reverse_penalty = 5000
152 | npf.npf_rail_pbs_cross_penalty = 300
153 | npf.npf_rail_pbs_signal_back_penalty = 1500
154 | npf.npf_buoy_penalty = 200
155 | npf.npf_water_curve_penalty = 25
156 | npf.npf_road_curve_penalty = 1
157 | npf.npf_crossing_penalty = 300
158 | npf.npf_road_drive_through_penalty = 800
159 | yapf.disable_node_optimization = false
160 | yapf.max_search_nodes = 10000
161 | yapf.rail_firstred_twoway_eol = true
162 | yapf.rail_firstred_penalty = 1000
163 | yapf.rail_firstred_exit_penalty = 10000
164 | yapf.rail_lastred_penalty = 1000
165 | yapf.rail_lastred_exit_penalty = 10000
166 | yapf.rail_station_penalty = 1000
167 | yapf.rail_slope_penalty = 200
168 | yapf.rail_curve45_penalty = 300
169 | yapf.rail_curve90_penalty = 600
170 | yapf.rail_depot_reverse_penalty = 5000
171 | yapf.rail_crossing_penalty = 300
172 | yapf.rail_look_ahead_max_signals = 10
173 | yapf.rail_look_ahead_signal_p0 = 500
174 | yapf.rail_look_ahead_signal_p1 = -100
175 | yapf.rail_look_ahead_signal_p2 = 5
176 | yapf.rail_pbs_cross_penalty = 300
177 | yapf.rail_pbs_station_penalty = 800
178 | yapf.rail_pbs_signal_back_penalty = 1500
179 | yapf.rail_doubleslip_penalty = 100
180 | yapf.rail_longer_platform_penalty = 800
181 | yapf.rail_longer_platform_per_tile_penalty = 0
182 | yapf.rail_shorter_platform_penalty = 4000
183 | yapf.rail_shorter_platform_per_tile_penalty = 0
184 | yapf.road_slope_penalty = 200
185 | yapf.road_curve_penalty = 100
186 | yapf.road_crossing_penalty = 300
187 | yapf.road_stop_penalty = 800
188 |
189 | [order]
190 | gotodepot = true
191 | no_servicing_if_no_breakdowns = true
192 | timetabling = true
193 | improved_load = true
194 | selectgoods = true
195 | serviceathelipad = true
196 | gradual_loading = true
197 |
198 | [gui]
199 | colored_news_year = 1901
200 | ending_year = 2051
201 | autosave = yearly
202 | vehicle_speed = true
203 | status_long_date = true
204 | show_finances = true
205 | autoscroll = false
206 | reverse_scroll = true
207 | smooth_scroll = false
208 | measure_tooltip = true
209 | errmsg_duration = 5
210 | toolbar_pos = 1
211 | window_snap_radius = 10
212 | population_in_label = true
213 | link_terraform_toolbar = true
214 | liveries = 2
215 | prefer_teamchat = false
216 | scrollwheel_scrolling = 0
217 | scrollwheel_multiplier = 5
218 | pause_on_newgame = false
219 | advanced_vehicle_list = 2
220 | timetable_in_ticks = false
221 | loading_indicators = 1
222 | default_rail_type = 6
223 | enable_signal_gui = true
224 | drag_signals_density = 4
225 | semaphore_build_before = 1975
226 | train_income_warn = true
227 | order_review_system = 2
228 | lost_train_warn = true
229 | autorenew = true
230 | autorenew_months = 12
231 | autorenew_money = 80000
232 | always_build_infrastructure = false
233 | new_nonstop = false
234 | keep_all_autosave = false
235 | autosave_on_exit = false
236 | max_num_autosaves = 16
237 | bridge_pillars = true
238 | auto_euro = true
239 | news_message_timeout = 2
240 | show_track_reservation = false
241 | default_signal_type = 0
242 | cycle_signal_types = 0
243 | console_backlog_timeout = 100
244 | console_backlog_length = 100
245 | network_chat_box_width = 700
246 | network_chat_box_height = 25
247 | right_mouse_btn_emulation = 0
248 |
249 | [ai]
250 | ainew_active = false
251 | ai_in_multiplayer = false
252 | ai_disable_veh_train = false
253 | ai_disable_veh_roadveh = false
254 | ai_disable_veh_aircraft = false
255 | ai_disable_veh_ship = false
256 |
257 | [locale]
258 | currency = GBP
259 | units = imperial
260 |
261 | [network]
262 | max_join_time = 500
263 | pause_on_join = true
264 | server_bind_ip = 0.0.0.0
265 | server_port = 3979
266 | server_advertise = false
267 | lan_internet = 1
268 | client_name = Ant
269 | server_password =
270 | rcon_password =
271 | default_company_pass =
272 | server_name =
273 | connect_to_ip =
274 | network_id =
275 | autoclean_companies = false
276 | autoclean_unprotected = 12
277 | autoclean_protected = 36
278 | max_companies = 8
279 | max_clients = 10
280 | max_spectators = 10
281 | restart_game_year = 0
282 | min_active_clients = 0
283 | server_lang = ANY
284 | reload_cfg = false
285 | last_host =
286 | last_port =
287 |
288 | [currency]
289 | rate = 1
290 | separator = .
291 | to_euro = 0
292 | prefix = ""
293 | suffix = " credits"
294 |
295 | [servers]
296 |
297 | [bans]
298 |
299 | [news_display]
300 | arrival_player = summarized
301 | arrival_other = summarized
302 | accident = full
303 | company_info = full
304 | open = summarized
305 | close = summarized
306 | economy = summarized
307 | production_player = full
308 | production_other = full
309 | production_nobody = summarized
310 | advice = full
311 | new_vehicles = full
312 | acceptance = summarized
313 | subsidies = summarized
314 | general = summarized
315 |
316 | [version]
317 | version_string = r14768
318 | version_number = 070039B0
319 |
320 | [preset-J]
321 | gcf/1_other/BlackCC/mauvetoblackw.grf =
322 | gcf/1_other/OpenGFX/newbuildings.grf =
323 | gcf/1_other/OpenGFX/OpenGFX_NewShips_v0.1.grf =
324 | gcf/1_other/OpenGFX/OpenGFX_NewWaterFeatures.grf =
325 | gcf/1_other/OpenGFX/OpenGFX_-_newEyeCandy_v0.2.grf =
326 | gcf/1_other/OpenGFX/OpenGFX_-_newFaces_v0.1.grf =
327 | gcf/1_other/OpenGFX/OpenGFX_-_newFonts_-_Newspaper_Fonts_v0.1.grf =
328 | gcf/1_other/OpenGFX/OpenGFX_-_newFonts_-_Small_Fonts_v0.1.grf =
329 | gcf/1_other/OpenGFX/OpenGFX_-_newIndustries_v0.8.grf =
330 | gcf/1_other/OpenGFX/OpenGFX_-_newInfrastructure_-_Airports_v0.1.grf =
331 | gcf/1_other/OpenGFX/OpenGFX_-_newInfrastructure_-_newBridges_v0.1.grf =
332 | gcf/1_other/OpenGFX/OpenGFX_-_newInfrastructure_v0.6.grf =
333 | gcf/1_other/OpenGFX/OpenGFX_NewLandscape_v0.3.1.grf =
334 | gcf/1_other/OpenGFX/OpenGFX_-_newTerrain_v0.4.grf =
335 | gcf/4_infrastructure/transrapidtrackset/z/signals.grf =
336 | gcf/8_vehicles/trains_wagons/pb_ukrs/pb_ukrs.grf =
337 | gcf/8_vehicles/trains_wagons/pb_ukrs/ukrsap1w.grf =
338 | gcf/6_town_buildings/ttrs3/ttrs3w.GRF =
339 | gcf/7_stations/basic_platforms/basic_platformsw.grf =
340 | gcf/1_other/townnames/british/brit_names.grf =
341 | gcf/7_stations/indstatr/indstatrw.grf =
342 | gcf/5_industries_cargos/pikkind/pikkindw.grf =
343 | gcf/4_infrastructure/ukroadset/UKRoadsetw.grf =
344 | gcf/7_stations/newstats/newstatsw.grf =
345 | gcf/4_infrastructure/nhfoundations/nhfoundationsw.grf =
346 | gcf/4_infrastructure/bktun/BKTunw.grf =
347 | gcf/7_stations/buffers/buffers.grf =
348 | gcf/8_vehicles/planes/pb_av8/pb_av8w.grf =
349 | gcf/8_vehicles/road_vehicles/egrvts/egrvts.grf =
350 | gcf/4_infrastructure/b_newbridges/newbridgesW.GRF =
351 | gcf/8_vehicles/ships/newships/newshipsw.grf =
352 | gcf/8_vehicles/ships/newships/nshp_ecs.grf =
353 | gcf/4_infrastructure/nhdep/nhdepw.grf =
354 | gcf/4_infrastructure/fence/fencew.grf =
355 | gcf/2_landscape/stolentrees/stolentreesw_162_108.grf =
356 | gcf/5_industries_cargos/pikkind/z/pikbrikw.grf =
357 |
358 | [newgrf]
359 | gcf/2_landscape/rivers/riversw.grf =
360 | gcf/1_other/townnames/british/brit_names.grf =
361 | gcf/8_vehicles/planes/pb_av8/pb_av8w.grf =
362 | gcf/8_vehicles/trains_wagons/empty/empty.grf =
363 | gcf/8_vehicles/road_vehicles/grvts/egrvts.grf =
364 | gcf/7_stations/buffers/buffers.grf =
365 | gcf/8_vehicles/road_vehicles/heqsdt/heqsdt.grf =
366 | gcf/7_stations/indstatr/indstatrw.grf =
367 | gcf/1_other/BlackCC/mauvetoblackw.grf =
368 | gcf/4_infrastructure/b_newtramtracks/NewTramTracksW_v0.4.1.grf =
369 | gcf/4_infrastructure/nhfoundations/nhfoundationsw.grf =
370 | gcf/8_vehicles/ships/newships/newshipsw.grf =
371 | gcf/8_vehicles/ships/newships/nshp_ecs.grf =
372 | gcf/7_stations/newstats/newstatsw.grf =
373 | gcf/4_infrastructure/nhdep/nhdepw.grf =
374 | gcf/1_other/OpenGFX/morebuildings.grf =
375 | gcf/1_other/OpenGFX/newbuildings022.grf =
376 | gcf/1_other/OpenGFX/hq.grf =
377 | gcf/1_other/OpenGFX/OpenGFX_NewWaterFeatures.grf =
378 | gcf/1_other/OpenGFX/OpenGFX_-_newEyeCandy_v0.2.grf =
379 | gcf/1_other/OpenGFX/OpenGFX_NewIndustries_v0.10.1.grf =
380 | gcf/1_other/OpenGFX/OpenGFX_-_newInfrastructure_-_Airports_v0.1.grf =
381 | gcf/1_other/OpenGFX/OpenGFX_newInfrastructure_v0.7.grf =
382 | gcf/1_other/OpenGFX/OpenGFX_NewLandscape_v0.3.1.grf =
383 | gcf/1_other/OpenGFX/OpenGFX_-_newTerrain_v0.4.grf =
384 | gcf/5_industries_cargos/pikkind/pikkindw.grf =
385 | gcf/2_landscape/stolentrees/stolentreesw_162_108.grf =
386 | gcf/4_infrastructure/totalbridges/total_bridges.grf =
387 | gcf/7_stations/ukwaypoints/ukwaypointsw.grf =
388 | gcf/8_vehicles/trains_wagons/pb_ukrs/pb_ukrs.grf = 0 3 0
389 | gcf/8_vehicles/trains_wagons/pb_ukrs/ukrsap1w.grf =
390 | gcf/4_infrastructure/yarrs/yarrs.grf =
391 | gcf/6_town_buildings/w2w/w2w.grf =
392 | gcf/1_other/townnames/welsh/welshnames.grf =
393 | gcf/2_landscape/smoothsnow/smoothsnoww.grf =
394 | gcf/6_town_buildings/pb_ukrh/pb_ukrhw.grf =
395 | gcf/9_last/shiptool/shiptoolv4.grf =
396 |
397 | [newgrf-static]
398 |
--------------------------------------------------------------------------------
/spec/fixtures/race07.ini:
--------------------------------------------------------------------------------
1 | ; All names and Steam IDs changed to fake values.
2 |
3 | [Header]
4 | Game=RACE 07
5 | Version=1.1.1.14
6 | TimeString=2008/09/13 23:26:32
7 | Aids=0,0,0,0,0,1,1,0,0
8 |
9 | [Race]
10 | RaceMode=5
11 | Scene=GameData\Locations\Anderstorp_2007\2007_ANDERSTORP.TRK
12 | AIDB=GameData\Locations\Anderstorp_2007\2007_ANDERSTORP.AIW
13 | Race Length=0.100
14 | Track Length=4018.8376
15 |
16 | [Slot010]
17 | Driver=Mark Voss
18 | SteamUser=mvoss
19 | SteamId=1865369
20 | Vehicle=Chevrolet Lacetti 2007
21 | Team=TEMPLATE_TEAM
22 | QualTime=1:37.839
23 | Laps=13
24 | Lap=(0, -1.000, 1:48.697)
25 | Lap=(1, 89.397, 1:39.455)
26 | Lap=(2, 198.095, 1:38.060)
27 | Lap=(3, 297.550, 1:38.632)
28 | Lap=(4, 395.610, 1:38.031)
29 | Lap=(5, 494.242, 1:39.562)
30 | Lap=(6, 592.273, 1:39.950)
31 | Lap=(7, 691.835, 1:38.366)
32 | Lap=(8, 791.785, 1:39.889)
33 | Lap=(9, 890.151, 1:39.420)
34 | Lap=(10, 990.040, 1:39.401)
35 | Lap=(11, 1089.460, 1:39.506)
36 | Lap=(12, 1188.862, 1:40.017)
37 | LapDistanceTravelled=3857.750244
38 | BestLap=1:38.031
39 | RaceTime=0:21:38.988
40 |
41 | [Slot016]
42 | Driver=Corey Ball
43 | SteamUser=cball
44 | SteamId=889853
45 | Vehicle=SEAT Leon 2007
46 | Team=TEMPLATE_TEAM
47 | QualTime=1:37.904
48 | Laps=9
49 | Lap=(0, -1.000, 1:51.434)
50 | Lap=(1, 89.397, 1:39.427)
51 | Lap=(2, 200.831, 1:38.394)
52 | Lap=(3, 300.258, 1:40.239)
53 | Lap=(4, 398.653, 1:38.172)
54 | Lap=(5, 498.891, 1:38.427)
55 | Lap=(6, 597.063, 1:39.271)
56 | Lap=(7, 695.490, 1:39.922)
57 | Lap=(8, 794.761, 1:40.305)
58 | LapDistanceTravelled=532.898193
59 | BestLap=1:38.172
60 | RaceTime=DNF
61 | Reason=0
62 |
63 | [Slot013]
64 | Driver=Gabriel Lloyd
65 | SteamUser=glloyd
66 | SteamId=635
67 | Vehicle=Chevrolet Lacetti 2007
68 | Team=TEMPLATE_TEAM
69 | QualTime=1:36.607
70 | Laps=13
71 | Lap=(0, -1.000, 1:50.816)
72 | Lap=(1, 89.397, 1:38.835)
73 | Lap=(2, 200.213, 1:38.082)
74 | Lap=(3, 299.048, 1:38.367)
75 | Lap=(4, 397.131, 1:37.825)
76 | Lap=(5, 495.497, 1:38.757)
77 | Lap=(6, 593.322, 1:38.718)
78 | Lap=(7, 692.080, 1:38.478)
79 | Lap=(8, 790.797, 1:39.347)
80 | Lap=(9, 889.275, 1:38.713)
81 | Lap=(10, 988.622, 1:38.952)
82 | Lap=(11, 1087.336, 1:39.285)
83 | Lap=(12, 1186.288, 1:40.555)
84 | LapDistanceTravelled=3898.517578
85 | BestLap=1:37.825
86 | RaceTime=0:21:36.730
87 |
88 | [Slot018]
89 | Driver=Reino Lintula
90 | SteamUser=rlintula
91 | SteamId=1816
92 | Vehicle=SEAT Leon 2007
93 | Team=TEMPLATE_TEAM
94 | QualTime=1:39.602
95 | Laps=7
96 | Lap=(0, -1.000, 1:52.788)
97 | Lap=(1, 112.593, 1:39.346)
98 | Lap=(2, 225.381, 1:40.680)
99 | Lap=(3, 324.727, 1:38.775)
100 | Lap=(4, 425.407, 1:40.149)
101 | Lap=(5, 524.182, 1:41.551)
102 | Lap=(6, 624.331, 1:46.908)
103 | LapDistanceTravelled=1333.551270
104 | BestLap=1:38.775
105 | RaceTime=DNF
106 | Reason=0
107 |
108 | [Slot002]
109 | Driver=Jerry Lalich
110 | SteamUser=jlalich
111 | SteamId=236892
112 | Vehicle=Chevrolet Lacetti 2007
113 | Team=TEMPLATE_TEAM
114 | QualTime=1:38.081
115 | Laps=13
116 | Lap=(0, -1.000, 1:48.530)
117 | Lap=(1, 89.397, 1:39.213)
118 | Lap=(2, 197.927, 1:37.933)
119 | Lap=(3, 297.140, 1:37.717)
120 | Lap=(4, 395.073, 1:38.500)
121 | Lap=(5, 492.790, 1:38.641)
122 | Lap=(6, 591.290, 1:39.810)
123 | Lap=(7, 689.931, 1:39.177)
124 | Lap=(8, 789.742, 1:40.122)
125 | Lap=(9, 888.918, 1:38.928)
126 | Lap=(10, 989.040, 1:39.238)
127 | Lap=(11, 1087.968, 1:39.223)
128 | Lap=(12, 1187.206, 1:39.888)
129 | LapDistanceTravelled=3892.720215
130 | BestLap=1:37.717
131 | RaceTime=0:21:36.920
132 |
133 | [END]
134 |
--------------------------------------------------------------------------------
/spec/fixtures/smb.ini:
--------------------------------------------------------------------------------
1 | ; Configuration file for the Samba software suite.
2 | ; ============================================================================
3 | ;
4 | ; For the format of this file and comprehensive descriptions of all the
5 | ; configuration option, please refer to the man page for smb.conf(5).
6 | ;
7 | ; The following configuration should suit most systems for basic usage and
8 | ; initial testing. It gives all clients access to their home directories and
9 | ; allows access to all printers specified in /etc/printcap.
10 |
11 | ; BEGIN required configuration
12 |
13 | ; Parameters inside the required configuration block should not be altered.
14 | ; They may be changed at any time by upgrades or other automated processes.
15 | ;
16 | ; Site-specific customizations will only be preserved if they are done
17 | ; outside this block. If you choose to make customizations, it is your
18 | ; own responsibility to verify that they work correctly with the supported
19 | ; configuration tools.
20 |
21 | [global]
22 | debug pid = yes
23 | log level = 1
24 | server string = Mac OS X
25 |
26 | printcap name = cups
27 | printing = cups
28 |
29 | encrypt passwords = yes
30 | use spnego = yes
31 |
32 | passdb backend = odsam
33 |
34 | idmap domains = default
35 | idmap config default: default = yes
36 | idmap config default: backend = odsam
37 | idmap alloc backend = odsam
38 | idmap negative cache time = 5
39 |
40 | map to guest = Bad User
41 | guest account = nobody
42 |
43 | unix charset = UTF-8-MAC
44 | display charset = UTF-8-MAC
45 | dos charset = 437
46 |
47 | vfs objects = darwinacl,darwin_streams
48 |
49 | ; Don't become a master browser unless absolutely necessary.
50 | os level = 2
51 | domain master = no
52 |
53 | ; For performance reasons, set the transmit buffer size
54 | ; to the maximum and enable sendfile support.
55 | max xmit = 131072
56 | use sendfile = yes
57 |
58 | ; The darwin_streams module gives us named streams support.
59 | stream support = yes
60 | ea support = yes
61 |
62 | ; Enable locking coherency with AFP.
63 | darwin_streams:brlm = yes
64 |
65 | ; Core files are invariably disabled system-wide, but attempting to
66 | ; dump core will trigger a crash report, so we still want to try.
67 | enable core files = yes
68 |
69 | ; Configure usershares for use by the synchronize-shares tool.
70 | usershare max shares = 1000
71 | usershare path = /var/samba/shares
72 | usershare owner only = no
73 | usershare allow guests = yes
74 | usershare allow full config = yes
75 |
76 | ; Filter inaccessible shares from the browse list.
77 | com.apple:filter shares by access = yes
78 |
79 | ; Check in with PAM to enforce SACL access policy.
80 | obey pam restrictions = yes
81 |
82 | ; Don't be trying to enforce ACLs in userspace.
83 | acl check permissions = no
84 |
85 | ; Make sure that we resolve unqualified names as NetBIOS before DNS.
86 | name resolve order = lmhosts wins bcast host
87 |
88 | ; Pull in system-wide preference settings. These are managed by
89 | ; synchronize-preferences tool.
90 | include = /var/db/smb.conf
91 |
92 | [printers]
93 | comment = All Printers
94 | path = /tmp
95 | printable = yes
96 | guest ok = no
97 | create mode = 0700
98 | writeable = no
99 | browseable = no
100 |
101 | ; Site-specific parameters can be added below this comment.
102 | ; END required configuration.
103 |
--------------------------------------------------------------------------------
/spec/generator/method_missing_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # Tests use of the Generator when used like so:
4 | #
5 | # IniParse::Generator.gen do |doc|
6 | # doc.comment 'My very own comment'
7 | # doc.my_section do |section|
8 | # section.my_option = 'my value'
9 | # end
10 | # end
11 | #
12 |
13 | describe 'When generating a document using Generator with section blocks using method_missing,' do
14 |
15 | # --
16 | # ==========================================================================
17 | # SECTION LINES
18 | # ==========================================================================
19 | # ++
20 |
21 | describe 'adding a section' do
22 | it 'should yield an object with generator methods' do
23 | IniParse::Generator.gen do |doc|
24 | doc.a_section do |section|
25 | %w( option comment blank ).each do |meth|
26 | expect(section).to respond_to(meth)
27 | end
28 | end
29 | end
30 | end
31 |
32 | it 'should add a Section to the document' do
33 | expect(IniParse::Generator.gen do |doc|
34 | doc.a_section { |section| }
35 | end).to have_section("a_section")
36 | end
37 |
38 | it 'should change the Generator context to the section during the section block' do
39 | IniParse::Generator.gen do |doc|
40 | doc.a_section do |section|
41 | expect(section.context).to be_kind_of(IniParse::Lines::Section)
42 | expect(section.context.key).to eq("a_section")
43 | end
44 | end
45 | end
46 |
47 | it 'should reset the Generator context to the document after the section block' do
48 | IniParse::Generator.gen do |doc|
49 | doc.a_section { |section| }
50 | expect(doc.context).to be_kind_of(IniParse::Document)
51 | end
52 | end
53 |
54 | it 'should append a blank line to the document, after the section' do
55 | expect(IniParse::Generator.gen do |doc|
56 | doc.a_section { |section| }
57 | end.lines.to_a.last).to be_kind_of(IniParse::Lines::Blank)
58 | end
59 |
60 | it 'should raise a LineNotAllowed if you attempt to nest a section' do
61 | expect do
62 | IniParse::Generator.gen do |doc|
63 | doc.a_section do |section_one|
64 | section_one.another_section { |section_two| }
65 | end
66 | end
67 | end.to raise_error(IniParse::LineNotAllowed)
68 | end
69 | end
70 |
71 | # --
72 | # ==========================================================================
73 | # OPTION LINES
74 | # ==========================================================================
75 | # ++
76 |
77 | describe 'adding a option' do
78 | describe 'when the context is a Document' do
79 | it "adds the option to an __anonymous__ section" do
80 | doc = IniParse::Generator.gen { |doc| doc.my_option = "a value" }
81 | expect(doc['__anonymous__']['my_option']).to eql('a value')
82 | end
83 | end
84 |
85 | describe 'when the context is a Section' do
86 | it 'should add the option to the section' do
87 | document = IniParse::Generator.gen do |doc|
88 | doc.a_section do |section|
89 | section.my_option = "a value"
90 | end
91 | end
92 |
93 | section = document["a_section"]
94 | expect(section).to have_option("my_option")
95 | expect(section["my_option"]).to eq("a value")
96 | end
97 | end
98 | end
99 |
100 | # Comments and blanks are added in the same way as in the
101 | # 'with_section_block_spec.rb' specification.
102 |
103 | end
104 |
--------------------------------------------------------------------------------
/spec/generator/with_section_blocks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # Tests use of the Generator when used like so:
4 | #
5 | # IniParse::Generator.gen do |doc|
6 | # doc.comment('My very own comment')
7 | # doc.section('my_section') do |section|
8 | # section.option('my_option', 'my value')
9 | # end
10 | # end
11 | #
12 |
13 | describe 'When generating a document using Generator with section blocks,' do
14 |
15 | it 'should be able to compile an empty document' do
16 | expect { IniParse::Generator.gen { |doc| } }.not_to raise_error
17 | end
18 |
19 | it 'should raise LocalJumpError if no block is given' do
20 | expect { IniParse::Generator.gen }.to raise_error(LocalJumpError)
21 | end
22 |
23 | it "should yield an object with generator methods" do
24 | IniParse::Generator.gen do |doc|
25 | %w( section option comment blank ).each do |meth|
26 | expect(doc).to respond_to(meth)
27 | end
28 | end
29 | end
30 |
31 | # --
32 | # ==========================================================================
33 | # SECTION LINES
34 | # ==========================================================================
35 | # ++
36 |
37 | describe 'adding a section' do
38 | it 'should yield an object with generator methods' do
39 | IniParse::Generator.gen do |doc|
40 | doc.section("a section") do |section|
41 | %w( option comment blank ).each do |meth|
42 | expect(section).to respond_to(meth)
43 | end
44 | end
45 | end
46 | end
47 |
48 | it 'should add a Section to the document' do
49 | expect(IniParse::Generator.gen do |doc|
50 | doc.section("a section") { |section| }
51 | end).to have_section("a section")
52 | end
53 |
54 | it 'should change the Generator context to the section during the section block' do
55 | IniParse::Generator.gen do |doc|
56 | doc.section("a section") do |section|
57 | expect(section.context).to be_kind_of(IniParse::Lines::Section)
58 | expect(section.context.key).to eq("a section")
59 | end
60 | end
61 | end
62 |
63 | it 'should reset the Generator context to the document after the section block' do
64 | IniParse::Generator.gen do |doc|
65 | doc.section("a section") { |section| }
66 | expect(doc.context).to be_kind_of(IniParse::Document)
67 | end
68 | end
69 |
70 | it "should use the parent document's options as a base" do
71 | document = IniParse::Generator.gen(:indent => ' ') do |doc|
72 | doc.section("a section") { |section| }
73 | end
74 |
75 | expect(document["a section"].to_ini).to match(/\A /)
76 | end
77 |
78 | it 'should pass extra options to the Section instance' do
79 | document = IniParse::Generator.gen do |doc|
80 | doc.section("a section", :indent => ' ') { |section| }
81 | end
82 |
83 | expect(document["a section"].to_ini).to match(/\A /)
84 | end
85 |
86 | it 'should append a blank line to the document, after the section' do
87 | expect(IniParse::Generator.gen do |doc|
88 | doc.section("a section") { |section| }
89 | end.lines.to_a.last).to be_kind_of(IniParse::Lines::Blank)
90 | end
91 |
92 | it 'should raise a LineNotAllowed if you attempt to nest a section' do
93 | expect do
94 | IniParse::Generator.gen do |doc|
95 | doc.section("a section") do |section_one|
96 | section_one.section("another_section") { |section_two| }
97 | end
98 | end
99 | end.to raise_error(IniParse::LineNotAllowed)
100 | end
101 | end
102 |
103 | # --
104 | # ==========================================================================
105 | # OPTION LINES
106 | # ==========================================================================
107 | # ++
108 |
109 | describe 'adding a option' do
110 |
111 | describe 'when the context is a Document' do
112 | it "should add the option to an __anonymous__ section" do
113 | document = IniParse::Generator.gen do |doc|
114 | doc.option("my option", "a value")
115 | end
116 |
117 | expect(document['__anonymous__']['my option']).to eql('a value')
118 | end
119 | end
120 |
121 | describe 'when the context is a Section' do
122 | it 'should add the option to the section' do
123 | document = IniParse::Generator.gen do |doc|
124 | doc.section("a section") do |section|
125 | section.option("my option", "a value")
126 | end
127 | end
128 |
129 | section = document["a section"]
130 | expect(section).to have_option("my option")
131 | expect(section["my option"]).to eq("a value")
132 | end
133 |
134 | it 'should pass extra options to the Option instance' do
135 | document = IniParse::Generator.gen do |doc|
136 | doc.section("a section") do |section|
137 | section.option("my option", "a value", :indent => " ")
138 | end
139 | end
140 |
141 | expect(document["a section"].option("my option").to_ini).to match(/^ /)
142 | end
143 |
144 | it "should use the parent document's options as a base" do
145 | document = IniParse::Generator.gen(:indent => " ") do |doc|
146 | doc.section("a section") do |section|
147 | section.option("my option", "a value")
148 | end
149 | end
150 |
151 | expect(document["a section"].option("my option").to_ini).to match(/^ /)
152 | end
153 |
154 | it "should use the parent section's options as a base" do
155 | document = IniParse::Generator.gen do |doc|
156 | doc.section("a section", :indent => " ") do |section|
157 | section.option("my option", "a value")
158 | end
159 | end
160 |
161 | expect(document["a section"].option("my option").to_ini).to match(/^ /)
162 | end
163 |
164 | it "should allow customisation of the parent's options" do
165 | document = IniParse::Generator.gen do |doc|
166 | doc.section("a section", :indent => " ") do |section|
167 | section.option("my option", "a value", {
168 | :comment_sep => "#", :comment => 'a comment'
169 | })
170 | end
171 | end
172 |
173 | option_ini = document["a section"].option("my option").to_ini
174 | expect(option_ini).to match(/^ /)
175 | expect(option_ini).to match(/ # a comment/)
176 | end
177 |
178 | it "should not use the parent section's comment when setting line options" do
179 | document = IniParse::Generator.gen do |doc|
180 | doc.section("a section", :comment => "My section") do |section|
181 | section.option("my option", "a value")
182 | end
183 | end
184 |
185 | expect(document["a section"].option("my option").to_ini).not_to match(/My section$/)
186 | end
187 | end
188 | end
189 |
190 | # --
191 | # ==========================================================================
192 | # COMMENT LINES
193 | # ==========================================================================
194 | # ++
195 |
196 | describe 'adding a comment' do
197 | it 'should pass extra options to the Option instance' do
198 | document = IniParse::Generator.gen do |doc|
199 | doc.comment("My comment", :indent => ' ')
200 | end
201 |
202 | expect(document.lines.to_a.first.to_ini).to match(/\A /)
203 | end
204 |
205 | it 'should ignore any extra :comment option' do
206 | document = IniParse::Generator.gen do |doc|
207 | doc.comment("My comment", :comment => 'Ignored')
208 | end
209 |
210 | expect(document.lines.to_a.first.to_ini).to match(/My comment/)
211 | expect(document.lines.to_a.first.to_ini).not_to match(/Ignored/)
212 | end
213 |
214 | describe 'when the context is a Document' do
215 | it 'should add a comment to the document' do
216 | document = IniParse::Generator.gen do |doc|
217 | doc.comment("My comment")
218 | end
219 |
220 | comment = document.lines.to_a.first
221 | expect(comment).to be_kind_of(IniParse::Lines::Comment)
222 | expect(comment.to_ini).to match(/My comment/)
223 | end
224 |
225 | it 'should use the default line options as a base' do
226 | document = IniParse::Generator.gen do |doc|
227 | doc.comment("My comment")
228 | end
229 |
230 | comment_ini = document.lines.to_a.first.to_ini
231 |
232 | # Match separator (;) and offset (0).
233 | expect(comment_ini).to eq('; My comment')
234 | end
235 | end
236 |
237 | describe 'when the context is a Section' do
238 | it 'should add a comment to the section' do
239 | document = IniParse::Generator.gen do |doc|
240 | doc.section("a section") do |section|
241 | section.comment("My comment")
242 | end
243 | end
244 |
245 | comment = document['a section'].lines.to_a.first
246 | expect(comment).to be_kind_of(IniParse::Lines::Comment)
247 | expect(comment.to_ini).to match(/My comment/)
248 | end
249 |
250 | it "should use the parent document's line options as a base" do
251 | document = IniParse::Generator.gen(:comment_offset => 5) do |doc|
252 | doc.section("a section") do |section|
253 | section.comment("My comment")
254 | end
255 | end
256 |
257 | expect(document['a section'].lines.to_a.first.to_ini).to match(/^ ;/)
258 | end
259 |
260 | it "should use the parent section's line options as a base" do
261 | document = IniParse::Generator.gen do |doc|
262 | doc.section("a section", :comment_offset => 5) do |section|
263 | section.comment("My comment")
264 | end
265 | end
266 |
267 | expect(document['a section'].lines.to_a.first.to_ini).to match(/^ ;/)
268 | end
269 |
270 | it "should allow customisation of the parent's options" do
271 | document = IniParse::Generator.gen do |doc|
272 | doc.section("a section", :comment_offset => 5) do |section|
273 | section.comment("My comment", :comment_sep => "#")
274 | end
275 | end
276 |
277 | # Match separator (#) and offset (5)
278 | expect(document['a section'].lines.to_a.first.to_ini).to \
279 | eq(' # My comment')
280 | end
281 |
282 | it "should not use the parent section's comment when setting line options" do
283 | document = IniParse::Generator.gen do |doc|
284 | doc.section("a section", :comment => "My section") do |section|
285 | section.comment("My comment")
286 | end
287 | end
288 |
289 | comment_ini = document['a section'].lines.to_a.first.to_ini
290 | expect(comment_ini).to match(/My comment/)
291 | expect(comment_ini).not_to match(/My section/)
292 | end
293 | end
294 | end
295 |
296 | # --
297 | # ==========================================================================
298 | # BLANK LINES
299 | # ==========================================================================
300 | # ++
301 |
302 | describe 'adding a blank line' do
303 | it 'should add a blank line to the document when it is the context' do
304 | document = IniParse::Generator.gen do |doc|
305 | doc.blank
306 | end
307 |
308 | expect(document.lines.to_a.first).to be_kind_of(IniParse::Lines::Blank)
309 | end
310 |
311 | it 'should add a blank line to the section when it is the context' do
312 | document = IniParse::Generator.gen do |doc|
313 | doc.section("a section") do |section|
314 | section.blank
315 | end
316 | end
317 |
318 | expect(document['a section'].lines.to_a.first).to be_kind_of(IniParse::Lines::Blank)
319 | end
320 | end
321 |
322 | end
323 |
--------------------------------------------------------------------------------
/spec/generator/without_section_blocks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # Tests use of the Generator when used like so:
4 | #
5 | # @gen = IniParse::Generator.new
6 | # @gen.comment('My very own comment')
7 | # @gen.section('my_section')
8 | # @gen.option('my_option', 'my value')
9 | # ...
10 | #
11 | # Or
12 | #
13 | # IniParse::Generator.gen do |doc|
14 | # doc.comment('My very own comment')
15 | # doc.section('my_section')
16 | # doc.option('my_option', 'my value')
17 | # end
18 | #
19 |
20 | describe 'When generating a document using Generator without section blocks,' do
21 | before(:each) { @gen = IniParse::Generator.new }
22 |
23 | # --
24 | # ==========================================================================
25 | # SECTION LINES
26 | # ==========================================================================
27 | # ++
28 |
29 | describe 'adding a section' do
30 | it 'should add a Section to the document' do
31 | @gen.section("a section")
32 | expect(@gen.document).to have_section("a section")
33 | end
34 |
35 | it 'should change the Generator context to the section' do
36 | @gen.section("a section")
37 | expect(@gen.context).to eq(@gen.document['a section'])
38 | end
39 |
40 | it 'should pass extra options to the Section instance' do
41 | @gen.section("a section", :indent => ' ')
42 | expect(@gen.document["a section"].to_ini).to match(/\A /)
43 | end
44 | end
45 |
46 | # --
47 | # ==========================================================================
48 | # OPTION LINES
49 | # ==========================================================================
50 | # ++
51 |
52 | describe 'adding a option' do
53 | it 'should pass extra options to the Option instance' do
54 | @gen.section("a section")
55 | @gen.option("my option", "a value", :indent => ' ')
56 | expect(@gen.document["a section"].option("my option").to_ini).to match(/^ /)
57 | end
58 |
59 | describe 'when the context is a Document' do
60 | it "should add the option to an __anonymous__ section" do
61 | @gen.option("key", "value")
62 | expect(@gen.document['__anonymous__']['key']).to eql('value')
63 | end
64 | end
65 |
66 | describe 'when the context is a Section' do
67 | it 'should add the option to the section' do
68 | @gen.section("a section")
69 | @gen.option("my option", "a value")
70 | expect(@gen.document["a section"]).to have_option("my option")
71 | expect(@gen.document["a section"]["my option"]).to eq("a value")
72 | end
73 | end
74 | end
75 |
76 | # --
77 | # ==========================================================================
78 | # COMMENT LINES
79 | # ==========================================================================
80 | # ++
81 |
82 | describe 'adding a comment' do
83 | it 'should pass extra options to the Option instance' do
84 | @gen.comment("My comment", :indent => ' ')
85 | expect(@gen.document.lines.to_a.first.to_ini).to match(/^ /)
86 | end
87 |
88 | it 'should ignore any extra :comment option' do
89 | @gen.comment("My comment", :comment => 'Ignored')
90 | comment_ini = @gen.document.lines.to_a.first.to_ini
91 | expect(comment_ini).to match(/My comment/)
92 | expect(comment_ini).not_to match(/Ignored/)
93 | end
94 |
95 | describe 'when the context is a Document' do
96 | it 'should add a comment to the document' do
97 | @gen.comment('My comment')
98 | comment = @gen.document.lines.to_a.first
99 | expect(comment).to be_kind_of(IniParse::Lines::Comment)
100 | expect(comment.to_ini).to match(/; My comment/)
101 | end
102 | end
103 |
104 | describe 'when the context is a Section' do
105 | it 'should add a comment to the section' do
106 | @gen.section('a section')
107 | @gen.comment('My comment')
108 | comment = @gen.document['a section'].lines.to_a.first
109 | expect(comment).to be_kind_of(IniParse::Lines::Comment)
110 | expect(comment.to_ini).to match(/My comment/)
111 | end
112 | end
113 | end
114 |
115 | # --
116 | # ==========================================================================
117 | # BLANK LINES
118 | # ==========================================================================
119 | # ++
120 |
121 | describe 'adding a blank line' do
122 | it 'should add a blank line to the document when it is the context' do
123 | @gen.blank
124 | comment = @gen.document.lines.to_a.first
125 | expect(comment).to be_kind_of(IniParse::Lines::Blank)
126 | end
127 |
128 | it 'should add a blank line to the section when it is the context' do
129 | @gen.section('a section')
130 | @gen.blank
131 | comment = @gen.document['a section'].lines.to_a.first
132 | expect(comment).to be_kind_of(IniParse::Lines::Blank)
133 | end
134 | end
135 |
136 | end
137 |
--------------------------------------------------------------------------------
/spec/iniparse_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 |
4 | describe "IniParse" do
5 | describe '.parse' do
6 | context 'with a line ending in a backslash' do
7 | let(:ini) do
8 | <<-'INI'.gsub(/^\s*/, '')
9 | [a]
10 | opt = 1 \
11 | other = 2
12 | INI
13 | end
14 |
15 | let(:doc) { IniParse.parse(ini) }
16 |
17 | it 'recognises the line continuation' do
18 | expect(doc.to_s).to eq("[a]\nopt = 1 other = 2\n")
19 | end
20 |
21 | it 'has one option' do
22 | expect(doc['a'].to_a.length).to eq(1)
23 | end
24 | end
25 |
26 | context 'with a line ending in a double-backslash' do
27 | let(:ini) do
28 | <<-'INI'.gsub(/^\s*/, '')
29 | [a]
30 | opt = 1 \\
31 | other = 2
32 | INI
33 | end
34 |
35 | let(:doc) { IniParse.parse(ini) }
36 |
37 | it 'does not see a line continuation' do
38 | expect(doc.to_s).to eq(ini)
39 | end
40 |
41 | it 'has one option' do
42 | expect(doc['a'].to_a.length).to eq(2)
43 | end
44 | end
45 | end
46 |
47 | describe '.open' do
48 | before(:each) { allow(File).to receive(:read).and_return('[section]') }
49 |
50 | it 'should return an IniParse::Document' do
51 | expect(IniParse.open('/my/path.ini')).to be_kind_of(IniParse::Document)
52 | end
53 |
54 | it 'should set the path on the returned Document' do
55 | expect(IniParse.open('/my/path.ini').path).to eq('/my/path.ini')
56 | end
57 |
58 | it 'should read the file at the given path' do
59 | expect(File).to receive(:read).with('/my/path.ini').and_return('[section]')
60 | IniParse.open('/my/path.ini')
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/spec/line_collection_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # ----------------------------------------------------------------------------
4 | # Shared specs for all Collection types...
5 | # ----------------------------------------------------------------------------
6 |
7 | shared_examples_for "LineCollection" do
8 | before(:each) do
9 | @collection << (@c1 = IniParse::Lines::Comment.new)
10 | @collection << @i1
11 | @collection << @i2
12 | @collection << (@b1 = IniParse::Lines::Blank.new)
13 | @collection << @i3
14 | @collection << (@b2 = IniParse::Lines::Blank.new)
15 | end
16 |
17 | describe '#each' do
18 | it 'should remove blanks and comments by default' do
19 | @collection.each { |l| expect(l).to be_kind_of(@i1.class) }
20 | end
21 |
22 | it 'should not remove blanks and comments if true is given' do
23 | arr = []
24 |
25 | # map(true)->each(true) not possible with Enumerable
26 | @collection.each(true) do |line|
27 | arr << line
28 | end
29 |
30 | expect(arr).to eq([@c1, @i1, @i2, @b1, @i3, @b2])
31 | end
32 | end
33 |
34 | describe '#[]' do
35 | it 'should fetch the correct value' do
36 | expect(@collection['first']).to eq(@i1)
37 | expect(@collection['second']).to eq(@i2)
38 | expect(@collection['third']).to eq(@i3)
39 | end
40 |
41 | it 'should return nil if the given key does not exist' do
42 | expect(@collection['does not exist']).to be_nil
43 | end
44 | end
45 |
46 | describe '#[]=' do
47 | it 'should successfully add a new key' do
48 | @collection['fourth'] = @new
49 | expect(@collection['fourth']).to eq(@new)
50 | end
51 |
52 | it 'should successfully update an existing key' do
53 | @collection['second'] = @new
54 | expect(@collection['second']).to eq(@new)
55 |
56 | # Make sure the old data is gone.
57 | expect(@collection.detect { |s| s.key == 'second' }).to be_nil
58 | end
59 |
60 | it 'should typecast given keys to a string' do
61 | @collection[:a_symbol] = @new
62 | expect(@collection['a_symbol']).to eq(@new)
63 | end
64 | end
65 |
66 | describe '#<<' do
67 | it 'should set the key correctly if given a new item' do
68 | expect(@collection).not_to have_key(@new.key)
69 | @collection << @new
70 | expect(@collection).to have_key(@new.key)
71 | end
72 |
73 | it 'should append Blank lines' do
74 | @collection << IniParse::Lines::Blank.new
75 | expect(@collection.instance_variable_get(:@lines).last).to \
76 | be_kind_of(IniParse::Lines::Blank)
77 | end
78 |
79 | it 'should append Comment lines' do
80 | @collection << IniParse::Lines::Comment.new
81 | expect(@collection.instance_variable_get(:@lines).last).to \
82 | be_kind_of(IniParse::Lines::Comment)
83 | end
84 |
85 | it 'should return self' do
86 | expect(@collection << @new).to eq(@collection)
87 | end
88 | end
89 |
90 | describe '#delete' do
91 | it 'should remove the given value and adjust the indicies' do
92 | expect(@collection['second']).not_to be_nil
93 | @collection.delete('second')
94 | expect(@collection['second']).to be_nil
95 | expect(@collection['first']).to eq(@i1)
96 | expect(@collection['third']).to eq(@i3)
97 | end
98 |
99 | it "should do nothing if the supplied key does not exist" do
100 | @collection.delete('does not exist')
101 | expect(@collection['first']).to eq(@i1)
102 | expect(@collection['third']).to eq(@i3)
103 | end
104 | end
105 |
106 | describe '#to_a' do
107 | it 'should return an array' do
108 | expect(@collection.to_a).to be_kind_of(Array)
109 | end
110 |
111 | it 'should include all lines' do
112 | expect(@collection.to_a).to eq([@c1, @i1, @i2, @b1, @i3, @b2])
113 | end
114 |
115 | it 'should include references to the same line objects as the collection' do
116 | @collection << @new
117 | expect(@collection.to_a.last.object_id).to eq(@new.object_id)
118 | end
119 | end
120 |
121 | describe '#to_hash' do
122 | it 'should return a hash' do
123 | expect(@collection.to_hash).to be_kind_of(Hash)
124 | end
125 |
126 | it 'should have the correct keys' do
127 | hash = @collection.to_hash
128 | expect(hash.keys.length).to eq(3)
129 | expect(hash).to have_key('first')
130 | expect(hash).to have_key('second')
131 | expect(hash).to have_key('third')
132 | end
133 |
134 | it 'should have the correct values' do
135 | hash = @collection.to_hash
136 | expect(hash['first']).to eq(@i1)
137 | expect(hash['second']).to eq(@i2)
138 | expect(hash['third']).to eq(@i3)
139 | end
140 | end
141 |
142 | describe '#keys' do
143 | it 'should return an array of strings' do
144 | expect(@collection.keys).to eq(['first', 'second', 'third'])
145 | end
146 | end
147 | end
148 |
149 | # ----------------------------------------------------------------------------
150 | # On with the collection specs...
151 | # ----------------------------------------------------------------------------
152 |
153 | describe 'IniParse::OptionCollection' do
154 | before(:each) do
155 | @collection = IniParse::OptionCollection.new
156 | @i1 = IniParse::Lines::Option.new('first', 'v1')
157 | @i2 = IniParse::Lines::Option.new('second', 'v2')
158 | @i3 = IniParse::Lines::Option.new('third', 'v3')
159 | @new = IniParse::Lines::Option.new('fourth', 'v4')
160 | end
161 |
162 | it_should_behave_like 'LineCollection'
163 |
164 | describe '#<<' do
165 | it 'should raise a LineNotAllowed exception if a Section is pushed' do
166 | expect { @collection << IniParse::Lines::Section.new('s') }.to \
167 | raise_error(IniParse::LineNotAllowed)
168 | end
169 |
170 | it 'should add the Option as a duplicate if an option with the same key exists' do
171 | option_one = IniParse::Lines::Option.new('k', 'value one')
172 | option_two = IniParse::Lines::Option.new('k', 'value two')
173 |
174 | @collection << option_one
175 | @collection << option_two
176 |
177 | expect(@collection['k']).to eq([option_one, option_two])
178 | end
179 | end
180 |
181 | describe '#keys' do
182 | it 'should handle duplicates' do
183 | @collection << @i1 << @i2 << @i3
184 | @collection << IniParse::Lines::Option.new('first', 'v5')
185 | expect(@collection.keys).to eq(['first', 'second', 'third'])
186 | end
187 | end
188 |
189 | context 'with duplicate lines' do
190 | let(:collection) { IniParse::OptionCollection.new }
191 |
192 | let(:opt_1) { IniParse::Lines::Option.new('my_key', 'value_1') }
193 | let(:opt_2) { IniParse::Lines::Option.new('my_key', 'value_2') }
194 |
195 | before do
196 | collection << opt_1
197 | collection << opt_2
198 | end
199 |
200 | it 'yields each item in turn' do
201 | expect { |b| collection.each(&b) }.to yield_successive_args(opt_1, opt_2)
202 | end
203 | end
204 | end
205 |
206 | describe 'IniParse::SectionCollection' do
207 | before(:each) do
208 | @collection = IniParse::SectionCollection.new
209 | @i1 = IniParse::Lines::Section.new('first')
210 | @i2 = IniParse::Lines::Section.new('second')
211 | @i3 = IniParse::Lines::Section.new('third')
212 | @new = IniParse::Lines::Section.new('fourth')
213 | end
214 |
215 | it_should_behave_like 'LineCollection'
216 |
217 | describe '#<<' do
218 | it 'should add merge Section with the other, if it is a duplicate' do
219 | new_section = IniParse::Lines::Section.new('first')
220 | @collection << @i1
221 | expect(@i1).to receive(:merge!).with(new_section).once
222 | @collection << new_section
223 | end
224 | end
225 | end
226 |
--------------------------------------------------------------------------------
/spec/lines_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module IniParse::Test
4 | class FakeLine
5 | include IniParse::Lines::Line
6 |
7 | def line_contents
8 | 'fake line'
9 | end
10 | end
11 | end
12 |
13 | describe "IniParse::Lines::Line module" do
14 | describe '#to_ini' do
15 | it 'should return +line_contents+' do
16 | expect(IniParse::Test::FakeLine.new.to_ini).to eq('fake line')
17 | end
18 |
19 | it 'should preserve line indents' do
20 | expect(IniParse::Test::FakeLine.new(
21 | :indent => ' '
22 | ).to_ini).to eq(' fake line')
23 | end
24 |
25 | describe 'when a comment is set' do
26 | it 'should correctly include the comment' do
27 | expect(IniParse::Test::FakeLine.new(
28 | :comment => 'comment', :comment_sep => ';', :comment_offset => 10
29 | ).to_ini).to eq('fake line ; comment')
30 | end
31 |
32 | it 'should correctly indent the comment' do
33 | expect(IniParse::Test::FakeLine.new(
34 | :comment => 'comment', :comment_sep => ';', :comment_offset => 15
35 | ).to_ini).to eq('fake line ; comment')
36 | end
37 |
38 | it 'should use ";" as a default comment seperator' do
39 | expect(IniParse::Test::FakeLine.new(
40 | :comment => 'comment'
41 | ).to_ini).to eq('fake line ; comment')
42 | end
43 |
44 | it 'should use the correct seperator' do
45 | expect(IniParse::Test::FakeLine.new(
46 | :comment => 'comment', :comment_sep => '#'
47 | ).to_ini).to eq('fake line # comment')
48 | end
49 |
50 | it 'should use the ensure a space is added before the comment seperator' do
51 | expect(IniParse::Test::FakeLine.new(
52 | :comment => 'comment', :comment_sep => ';', :comment_offset => 0
53 | ).to_ini).to eq('fake line ; comment')
54 | end
55 |
56 | it 'should not add an extra space if the line is blank' do
57 | line = IniParse::Test::FakeLine.new(
58 | :comment => 'comment', :comment_sep => ';', :comment_offset => 0
59 | )
60 |
61 | allow(line).to receive(:line_contents).and_return('')
62 | expect(line.to_ini).to eq('; comment')
63 | end
64 | end
65 |
66 | describe 'when no comment is set' do
67 | it 'should not add trailing space if :comment_offset has a value' do
68 | expect(IniParse::Test::FakeLine.new(:comment_offset => 10).to_ini).to eq('fake line')
69 | end
70 |
71 | it 'should not add a comment seperator :comment_sep has a value' do
72 | expect(IniParse::Test::FakeLine.new(:comment_sep => ';').to_ini).to eq('fake line')
73 | end
74 | end
75 | end
76 |
77 | describe '#has_comment?' do
78 | it 'should return true if :comment has a non-blank value' do
79 | expect(IniParse::Test::FakeLine.new(:comment => 'comment')).to have_comment
80 | end
81 |
82 | it 'should return true if :comment has a blank value' do
83 | expect(IniParse::Test::FakeLine.new(:comment => '')).to have_comment
84 | end
85 |
86 | it 'should return false if :comment has a nil value' do
87 | expect(IniParse::Test::FakeLine.new).not_to have_comment
88 | expect(IniParse::Test::FakeLine.new(:comment => nil)).not_to have_comment
89 | end
90 | end
91 | end
92 |
93 | #
94 | # Section
95 | #
96 |
97 | describe 'IniParse::Lines::Section' do
98 | before(:each) { @section = IniParse::Lines::Section.new('a section') }
99 |
100 | it 'should respond_to +lines+' do
101 | expect(@section).to respond_to(:lines)
102 | end
103 |
104 | it 'should not respond_to +lines=+' do
105 | expect(@section).not_to respond_to(:lines=)
106 | end
107 |
108 | it 'should include Enumerable' do
109 | expect(IniParse::Lines::Section.included_modules).to include(Enumerable)
110 | end
111 |
112 | describe '#initialize' do
113 | it 'should typecast the given key to a string' do
114 | expect(IniParse::Lines::Section.new(:symbol).key).to eq('symbol')
115 | end
116 | end
117 |
118 | describe '#option' do
119 | it 'should retrieve the line identified by the given key' do
120 | option = IniParse::Lines::Option.new('k', 'value one')
121 | @section.lines << option
122 | expect(@section.option('k')).to eq(option)
123 | end
124 |
125 | it 'should return nil if the given key does not exist' do
126 | expect(@section.option('does_not_exist')).to be_nil
127 | end
128 | end
129 |
130 | describe '#each' do
131 | it 'should call #each on +lines+' do
132 | expect(@section.lines).to receive(:each)
133 | @section.each { |l| }
134 | end
135 | end
136 |
137 | describe '#[]' do
138 | it 'should return nil if the given key does not exist' do
139 | expect(@section['k']).to be_nil
140 | end
141 |
142 | it 'should return a value if the given key exists' do
143 | @section.lines << IniParse::Lines::Option.new('k', 'v')
144 | expect(@section['k']).to eq('v')
145 | end
146 |
147 | it 'should return an array of values if the key is a duplicate' do
148 | @section.lines << IniParse::Lines::Option.new('k', 'v1')
149 | @section.lines << IniParse::Lines::Option.new('k', 'v2')
150 | @section.lines << IniParse::Lines::Option.new('k', 'v3')
151 | expect(@section['k']).to eq(['v1', 'v2', 'v3'])
152 | end
153 |
154 | it 'should typecast the key to a string' do
155 | @section.lines << IniParse::Lines::Option.new('k', 'v')
156 | expect(@section[:k]).to eq('v')
157 | end
158 | end
159 |
160 | describe '#[]=' do
161 | it 'should add a new Option with the given key and value' do
162 | @section['k'] = 'a value'
163 | expect(@section.option('k')).to be_kind_of(IniParse::Lines::Option)
164 | expect(@section['k']).to eq('a value')
165 | end
166 |
167 | it 'should update the Option if one already exists' do
168 | @section.lines << IniParse::Lines::Option.new('k', 'orig value')
169 | @section['k'] = 'new value'
170 | expect(@section['k']).to eq('new value')
171 | end
172 |
173 | it 'should replace the existing Option if it is an array' do
174 | @section.lines << IniParse::Lines::Option.new('k', 'v1')
175 | @section.lines << IniParse::Lines::Option.new('k', 'v2')
176 | @section['k'] = 'new value'
177 | expect(@section.option('k')).to be_kind_of(IniParse::Lines::Option)
178 | expect(@section['k']).to eq('new value')
179 | end
180 |
181 | it 'should typecast the key to a string' do
182 | @section[:k] = 'a value'
183 | expect(@section['k']).to eq('a value')
184 | end
185 | end
186 |
187 | describe '#merge!' do
188 | before(:each) do
189 | @section.lines << IniParse::Lines::Option.new('a', 'val1')
190 | @section.lines << IniParse::Lines::Blank.new
191 | @section.lines << IniParse::Lines::Comment.new
192 | @section.lines << IniParse::Lines::Option.new('b', 'val2')
193 |
194 | @new_section = IniParse::Lines::Section.new('new section')
195 | end
196 |
197 | it 'should merge options from the given Section into the receiver' do
198 | @new_section.lines << IniParse::Lines::Option.new('c', 'val3')
199 | @new_section.lines << IniParse::Lines::Option.new('d', 'val4')
200 |
201 | @section.merge!(@new_section)
202 | expect(@section['a']).to eq('val1')
203 | expect(@section['b']).to eq('val2')
204 | expect(@section['c']).to eq('val3')
205 | expect(@section['d']).to eq('val4')
206 | end
207 |
208 | it 'should handle duplicates' do
209 | @new_section.lines << IniParse::Lines::Option.new('a', 'val2')
210 | @section.merge!(@new_section)
211 | expect(@section['a']).to eq(['val1', 'val2'])
212 | end
213 |
214 | it 'should handle duplicates on both sides' do
215 | @section.lines << IniParse::Lines::Option.new('a', 'val2')
216 | @new_section.lines << IniParse::Lines::Option.new('a', 'val3')
217 | @new_section.lines << IniParse::Lines::Option.new('a', 'val4')
218 |
219 | @section.merge!(@new_section)
220 | expect(@section['a']).to eq(['val1', 'val2', 'val3', 'val4'])
221 | end
222 |
223 | it 'should copy blank lines' do
224 | @new_section.lines << IniParse::Lines::Blank.new
225 | @section.merge!(@new_section)
226 | line = nil
227 | @section.each(true) { |l| line = l }
228 | expect(line).to be_kind_of(IniParse::Lines::Blank)
229 | end
230 |
231 | it 'should copy comments' do
232 | @new_section.lines << IniParse::Lines::Comment.new
233 | @section.merge!(@new_section)
234 | line = nil
235 | @section.each(true) { |l| line = l }
236 | expect(line).to be_kind_of(IniParse::Lines::Comment)
237 | end
238 | end
239 |
240 | describe '#delete' do
241 | let(:opt_one) { IniParse::Lines::Option.new('a', 'b') }
242 | let(:opt_two) { IniParse::Lines::Option.new('c', 'd') }
243 |
244 | before(:each) do
245 | @section.lines << opt_one
246 | @section.lines << opt_two
247 | end
248 |
249 | it 'removes the option given a key' do
250 | expect { @section.delete('a') }.
251 | to change { @section['a'] }.to(nil)
252 | end
253 |
254 | it 'removes the option given an Option' do
255 | expect { @section.delete(opt_one) }.
256 | to change { @section['a'] }.to(nil)
257 | end
258 |
259 | it 'should not remove non-matching lines' do
260 | expect { @section.delete('a') }.not_to change { @section['c'] }
261 | end
262 |
263 | it 'returns the section' do
264 | expect(@section.delete('a')).to eql(@section)
265 | end
266 | end
267 |
268 | describe '#to_ini' do
269 | it 'should include the section key' do
270 | expect(IniParse::Lines::Section.new('a section').to_ini).to eq('[a section]')
271 | end
272 |
273 | it 'should include lines belonging to the section' do
274 | @section.lines << IniParse::Lines::Option.new('a', 'val1')
275 | @section.lines << IniParse::Lines::Blank.new
276 | @section.lines << IniParse::Lines::Comment.new(
277 | :comment => 'my comment', :comment_sep => ';', :comment_offset => 0
278 | )
279 | @section.lines << IniParse::Lines::Option.new('b', 'val2')
280 |
281 | expect(@section.to_ini).to eq(
282 | "[a section]\n" \
283 | "a = val1\n" \
284 | "\n" \
285 | "; my comment\n" \
286 | "b = val2"
287 | )
288 | end
289 |
290 | it 'should include duplicate lines' do
291 | @section.lines << IniParse::Lines::Option.new('a', 'val1')
292 | @section.lines << IniParse::Lines::Option.new('a', 'val2')
293 |
294 | expect(@section.to_ini).to eq(
295 | "[a section]\n" \
296 | "a = val1\n" \
297 | "a = val2"
298 | )
299 | end
300 | end
301 |
302 | describe '#has_option?' do
303 | before do
304 | @section['first'] = 'value'
305 | end
306 |
307 | it 'should return true if an option with the given key exists' do
308 | expect(@section).to have_option('first')
309 | end
310 |
311 | it 'should return true if no option with the given key exists' do
312 | expect(@section).not_to have_option('second')
313 | end
314 | end
315 | end
316 |
317 | #
318 | # Option
319 | #
320 |
321 | describe 'Iniparse::Lines::Option' do
322 | describe '#initialize' do
323 | it 'should typecast the given key to a string' do
324 | expect(IniParse::Lines::Option.new(:symbol, '').key).to eq('symbol')
325 | end
326 | end
327 |
328 | describe '#to_ini' do
329 | it 'should include the key and value' do
330 | expect(IniParse::Lines::Option.new('key', 'value').to_ini).to eq('key = value')
331 | end
332 | end
333 |
334 | describe '.parse' do
335 | def parse(line, opts = {})
336 | IniParse::Lines::Option.parse(line, opts)
337 | end
338 |
339 | it 'should typecast empty values to nil' do
340 | expect(parse('key =')).to be_option_tuple('key', nil)
341 | expect(parse('key = ')).to be_option_tuple('key', nil)
342 | expect(parse('key = ')).to be_option_tuple('key', nil)
343 | end
344 |
345 | it 'should not typecast "true" if true is part of a word' do
346 | expect(parse('key = TestTrueTest')).to be_option_tuple('key', 'TestTrueTest')
347 | expect(parse('key = TrueTest')).to be_option_tuple('key', 'TrueTest')
348 | expect(parse('key = TestTrue')).to be_option_tuple('key', 'TestTrue')
349 | end
350 |
351 | it 'should not typecast "false" if false is part of a word' do
352 | expect(parse('key = TestFalseTest')).to be_option_tuple('key', 'TestFalseTest')
353 | expect(parse('key = FalseTest')).to be_option_tuple('key', 'FalseTest')
354 | expect(parse('key = TestFalse')).to be_option_tuple('key', 'TestFalse')
355 | end
356 |
357 | it 'should typecast "true" to TrueClass' do
358 | expect(parse('key = true')).to be_option_tuple('key', true)
359 | expect(parse('key = TRUE')).to be_option_tuple('key', true)
360 | end
361 |
362 | it 'should typecast "false" to FalseClass' do
363 | expect(parse('key = false')).to be_option_tuple('key', false)
364 | expect(parse('key = FALSE')).to be_option_tuple('key', false)
365 | end
366 |
367 | it 'should typecast integer values to Integer' do
368 | expect(parse('key = 1')).to be_option_tuple('key', 1)
369 | expect(parse('key = 10')).to be_option_tuple('key', 10)
370 | end
371 |
372 | it 'should not typecast integers with a leading 0 to Integer' do
373 | expect(parse('key = 0700')).to be_option_tuple('key', '0700')
374 | end
375 |
376 | it 'should typecast negative integer values to Integer' do
377 | expect(parse('key = -1')).to be_option_tuple('key', -1)
378 | end
379 |
380 | it 'should typecast float values to Float' do
381 | expect(parse('key = 3.14159265')).to be_option_tuple('key', 3.14159265)
382 | end
383 |
384 | it 'should typecast negative float values to Float' do
385 | expect(parse('key = -3.14159265')).to be_option_tuple('key', -3.14159265)
386 | end
387 |
388 | it 'should typecast scientific notation numbers to Float' do
389 | expect(parse('key = 10e5')).to be_option_tuple('key', 10e5)
390 | expect(parse('key = 10e+5')).to be_option_tuple('key', 10e5)
391 | expect(parse('key = 10e-5')).to be_option_tuple('key', 10e-5)
392 |
393 | expect(parse('key = -10e5')).to be_option_tuple('key', -10e5)
394 | expect(parse('key = -10e+5')).to be_option_tuple('key', -10e5)
395 | expect(parse('key = -10e-5')).to be_option_tuple('key', -10e-5)
396 |
397 | expect(parse('key = 3.14159265e5')).to be_option_tuple('key', 3.14159265e5)
398 | expect(parse('key = 3.14159265e+5')).to be_option_tuple('key', 3.14159265e5)
399 | expect(parse('key = 3.14159265e-5')).to be_option_tuple('key', 3.14159265e-5)
400 |
401 | expect(parse('key = -3.14159265e5')).to be_option_tuple('key', -3.14159265e5)
402 | expect(parse('key = -3.14159265e+5')).to be_option_tuple('key', -3.14159265e5)
403 | expect(parse('key = -3.14159265e-5')).to be_option_tuple('key', -3.14159265e-5)
404 | end
405 | end
406 | end
407 |
408 | #
409 | # Blank
410 | #
411 |
412 | #
413 | # Comment
414 | #
415 |
416 | describe 'IniParse::Lines::Comment' do
417 | describe '#has_comment?' do
418 | it 'should return true if :comment has a non-blank value' do
419 | expect(IniParse::Lines::Comment.new(:comment => 'comment')).to have_comment
420 | end
421 |
422 | it 'should return true if :comment has a blank value' do
423 | expect(IniParse::Lines::Comment.new(:comment => '')).to have_comment
424 | end
425 |
426 | it 'should return true if :comment has a nil value' do
427 | expect(IniParse::Lines::Comment.new).to have_comment
428 | expect(IniParse::Lines::Comment.new(:comment => nil)).to have_comment
429 | end
430 | end
431 |
432 | describe '#to_ini' do
433 | it 'should return the comment' do
434 | expect(IniParse::Lines::Comment.new(
435 | :comment => 'a comment'
436 | ).to_ini).to eq('; a comment')
437 | end
438 |
439 | it 'should preserve comment offset' do
440 | expect(IniParse::Lines::Comment.new(
441 | :comment => 'a comment', :comment_offset => 10
442 | ).to_ini).to eq(' ; a comment')
443 | end
444 |
445 | it 'should return just the comment_sep if the comment is blank' do
446 | expect(IniParse::Lines::Comment.new.to_ini).to eq(';')
447 | end
448 | end
449 | end
450 |
--------------------------------------------------------------------------------
/spec/parser/document_parsing_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # Tests parsing of multiple lines, in context, using #parse.
4 |
5 | describe 'Parsing a document' do
6 | describe 'when a comment preceeds a single section and option' do
7 | before(:all) do
8 | @doc = IniParse::Parser.new(fixture(:comment_before_section)).parse
9 | end
10 |
11 | it 'should have a comment as the first line' do
12 | expect(@doc.lines.to_a.first).to be_kind_of(IniParse::Lines::Comment)
13 | end
14 |
15 | it 'should have one section' do
16 | expect(@doc.lines.keys).to eq(['first_section'])
17 | end
18 |
19 | it 'should have one option belonging to `first_section`' do
20 | expect(@doc['first_section']['key']).to eq('value')
21 | end
22 | end
23 |
24 | it 'should allow blank lines to preceed the first section' do
25 | expect {
26 | @doc = IniParse::Parser.new(fixture(:blank_before_section)).parse
27 | }.not_to raise_error
28 |
29 | expect(@doc.lines.to_a.first).to be_kind_of(IniParse::Lines::Blank)
30 | end
31 |
32 | it 'should allow a blank line to belong to a section' do
33 | expect {
34 | @doc = IniParse::Parser.new(fixture(:blank_in_section)).parse
35 | }.not_to raise_error
36 |
37 | expect(@doc['first_section'].lines.to_a.first).to be_kind_of(IniParse::Lines::Blank)
38 | end
39 |
40 | it 'should permit comments on their own line' do
41 | expect {
42 | @doc = IniParse::Parser.new(fixture(:comment_line)).parse
43 | }.not_to raise_error
44 |
45 | line = @doc['first_section'].lines.to_a.first
46 | expect(line.comment).to eql('; block comment ;')
47 | end
48 |
49 | it 'should permit options before the first section' do
50 | doc = IniParse::Parser.new(fixture(:option_before_section)).parse
51 |
52 | expect(doc.lines).to have_key('__anonymous__')
53 | expect(doc['__anonymous__']['foo']).to eql('bar')
54 | expect(doc['foo']['another']).to eql('thing')
55 | end
56 |
57 | it 'should raise ParseError if a line could not be parsed' do
58 | expect { IniParse::Parser.new(fixture(:invalid_line)).parse }.to \
59 | raise_error(IniParse::ParseError)
60 | end
61 |
62 | describe 'when a section name contains "="' do
63 | before(:all) do
64 | @doc = IniParse::Parser.new(fixture(:section_with_equals)).parse
65 | end
66 |
67 | it 'should have two sections' do
68 | expect(@doc.lines.to_a.length).to eq(2)
69 | end
70 |
71 | it 'should have one section' do
72 | expect(@doc.lines.keys).to eq(['first_section = name',
73 | 'another_section = a name'])
74 | end
75 |
76 | it 'should have one option belonging to `first_section = name`' do
77 | expect(@doc['first_section = name']['key']).to eq('value')
78 | end
79 |
80 | it 'should have one option belonging to `another_section = a name`' do
81 | expect(@doc['another_section = a name']['another']).to eq('thing')
82 | end
83 | end
84 |
85 | describe 'when a document contains a section multiple times' do
86 | before(:all) do
87 | @doc = IniParse::Parser.new(fixture(:duplicate_section)).parse
88 | end
89 |
90 | it 'should only add the section once' do
91 | # "first_section" and "second_section".
92 | expect(@doc.lines.to_a.length).to eq(2)
93 | end
94 |
95 | it 'should retain values from the first time' do
96 | expect(@doc['first_section']['key']).to eq('value')
97 | end
98 |
99 | it 'should add new keys' do
100 | expect(@doc['first_section']['third']).to eq('fourth')
101 | end
102 |
103 | it 'should merge in duplicate keys' do
104 | expect(@doc['first_section']['another']).to eq(%w( thing again ))
105 | end
106 | end
107 | end
108 |
--------------------------------------------------------------------------------
/spec/parser/line_parsing_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # Tests parsing of individual, out of context, line types using #parse_line.
4 |
5 | describe 'Parsing a line' do
6 | it 'should strip leading whitespace and set the :indent option' do
7 | expect(IniParse::Parser.parse_line(' [section]')).to \
8 | be_section_tuple(:any, {:indent => ' '})
9 | end
10 |
11 | it 'should raise an error if the line could not be matched' do
12 | expect { IniParse::Parser.parse_line('invalid line') }.to \
13 | raise_error(IniParse::ParseError)
14 | end
15 |
16 | it 'should parse using the types set in IniParse::Parser.parse_types' do
17 | begin
18 | # Remove last type.
19 | type = IniParse::Parser.parse_types.pop
20 | expect(type).not_to receive(:parse)
21 | IniParse::Parser.parse_line('[section]')
22 | ensure
23 | IniParse::Parser.parse_types << type
24 | end
25 | end
26 |
27 | # --
28 | # ==========================================================================
29 | # Option lines.
30 | # ==========================================================================
31 | # ++
32 |
33 | describe 'with "k = v"' do
34 | before(:all) do
35 | @tuple = IniParse::Parser.parse_line('k = v')
36 | end
37 |
38 | it 'should return an option tuple' do
39 | expect(@tuple).to be_option_tuple('k', 'v')
40 | end
41 |
42 | it 'should set no indent, comment, offset or separator' do
43 | expect(@tuple.last[:indent]).to be_nil
44 | expect(@tuple.last[:comment]).to be_nil
45 | expect(@tuple.last[:comment_offset]).to be_nil
46 | expect(@tuple.last[:comment_sep]).to be_nil
47 | end
48 | end
49 |
50 | describe 'with "k = a value with spaces"' do
51 | it 'should return an option tuple' do
52 | expect(IniParse::Parser.parse_line('k = a value with spaces')).to \
53 | be_option_tuple('k', 'a value with spaces')
54 | end
55 | end
56 |
57 | describe 'with "k = v ; a comment "' do
58 | before(:all) do
59 | @tuple = IniParse::Parser.parse_line('k = v ; a comment')
60 | end
61 |
62 | it 'should return an option tuple' do
63 | expect(@tuple).to be_option_tuple('k', 'v')
64 | end
65 |
66 | it 'should set the comment to "a comment"' do
67 | expect(@tuple).to be_option_tuple(:any, :any, :comment => 'a comment')
68 | end
69 |
70 | it 'should set the comment separator to ";"' do
71 | expect(@tuple).to be_option_tuple(:any, :any, :comment_sep => ';')
72 | end
73 |
74 | it 'should set the comment offset to 6' do
75 | expect(@tuple).to be_option_tuple(:any, :any, :comment_offset => 6)
76 | end
77 | end
78 |
79 | describe 'with "k = v;w;x y;z"' do
80 | before(:all) do
81 | @tuple = IniParse::Parser.parse_line('k = v;w;x y;z')
82 | end
83 |
84 | it 'should return an option tuple with the correct value' do
85 | expect(@tuple).to be_option_tuple(:any, 'v;w;x y;z')
86 | end
87 |
88 | it 'should not set a comment' do
89 | expect(@tuple.last[:comment]).to be_nil
90 | expect(@tuple.last[:comment_offset]).to be_nil
91 | expect(@tuple.last[:comment_sep]).to be_nil
92 | end
93 | end
94 |
95 | describe 'with "k = v;w ; a comment"' do
96 | before(:all) do
97 | @tuple = IniParse::Parser.parse_line('k = v;w ; a comment')
98 | end
99 |
100 | it 'should return an option tuple with the correct value' do
101 | expect(@tuple).to be_option_tuple(:any, 'v;w')
102 | end
103 |
104 | it 'should set the comment to "a comment"' do
105 | expect(@tuple).to be_option_tuple(:any, :any, :comment => 'a comment')
106 | end
107 |
108 | it 'should set the comment separator to ";"' do
109 | expect(@tuple).to be_option_tuple(:any, :any, :comment_sep => ';')
110 | end
111 |
112 | it 'should set the comment offset to 8' do
113 | expect(@tuple).to be_option_tuple(:any, :any, :comment_offset => 8)
114 | end
115 | end
116 |
117 | describe 'with "key=value"' do
118 | it 'should return an option tuple with the correct key and value' do
119 | expect(IniParse::Parser.parse_line('key=value')).to \
120 | be_option_tuple('key', 'value')
121 | end
122 | end
123 |
124 | describe 'with "key= value"' do
125 | it 'should return an option tuple with the correct key and value' do
126 | expect(IniParse::Parser.parse_line('key= value')).to \
127 | be_option_tuple('key', 'value')
128 | end
129 | end
130 |
131 | describe 'with "key =value"' do
132 | it 'should return an option tuple with the correct key and value' do
133 | expect(IniParse::Parser.parse_line('key =value')).to \
134 | be_option_tuple('key', 'value')
135 | end
136 | end
137 |
138 | describe 'with "key = value"' do
139 | it 'should return an option tuple with the correct key and value' do
140 | expect(IniParse::Parser.parse_line('key = value')).to \
141 | be_option_tuple('key', 'value')
142 | end
143 | end
144 |
145 | describe 'with "key ="' do
146 | it 'should return an option tuple with the correct key' do
147 | expect(IniParse::Parser.parse_line('key =')).to \
148 | be_option_tuple('key')
149 | end
150 |
151 | it 'should set the option value to nil' do
152 | expect(IniParse::Parser.parse_line('key =')).to \
153 | be_option_tuple(:any, nil)
154 | end
155 | end
156 |
157 |
158 | describe 'with "key = EEjDDJJjDJDJD233232=="' do
159 | it 'should include the "equals" in the option value' do
160 | expect(IniParse::Parser.parse_line('key = EEjDDJJjDJDJD233232==')).to \
161 | be_option_tuple('key', 'EEjDDJJjDJDJD233232==')
162 | end
163 | end
164 |
165 | describe 'with "key = ==EEjDDJJjDJDJD233232"' do
166 | it 'should include the "equals" in the option value' do
167 | expect(IniParse::Parser.parse_line('key = ==EEjDDJJjDJDJD233232')).to \
168 | be_option_tuple('key', '==EEjDDJJjDJDJD233232')
169 | end
170 | end
171 |
172 | describe 'with "key.two = value"' do
173 | it 'should return an option tuple with the correct key' do
174 | expect(IniParse::Parser.parse_line('key.two = value')).to \
175 | be_option_tuple('key.two')
176 | end
177 | end
178 |
179 | describe 'with "key/with/slashes = value"' do
180 | it 'should return an option tuple with the correct key' do
181 | expect(IniParse::Parser.parse_line('key/with/slashes = value')).to \
182 | be_option_tuple('key/with/slashes', 'value')
183 | end
184 | end
185 |
186 | describe 'with "key_with_underscores = value"' do
187 | it 'should return an option tuple with the correct key' do
188 | expect(IniParse::Parser.parse_line('key_with_underscores = value')).to \
189 | be_option_tuple('key_with_underscores', 'value')
190 | end
191 | end
192 |
193 | describe 'with "key-with-dashes = value"' do
194 | it 'should return an option tuple with the correct key' do
195 | expect(IniParse::Parser.parse_line('key-with-dashes = value')).to \
196 | be_option_tuple('key-with-dashes', 'value')
197 | end
198 | end
199 |
200 | describe 'with "key with spaces = value"' do
201 | it 'should return an option tuple with the correct key' do
202 | expect(IniParse::Parser.parse_line('key with spaces = value')).to \
203 | be_option_tuple('key with spaces', 'value')
204 | end
205 | end
206 |
207 | # --
208 | # ==========================================================================
209 | # Section lines.
210 | # ==========================================================================
211 | # ++
212 |
213 | describe 'with "[section]"' do
214 | before(:all) do
215 | @tuple = IniParse::Parser.parse_line('[section]')
216 | end
217 |
218 | it 'should return a section tuple' do
219 | expect(@tuple).to be_section_tuple('section')
220 | end
221 |
222 | it 'should set no indent, comment, offset or separator' do
223 | expect(@tuple.last[:indent]).to be_nil
224 | expect(@tuple.last[:comment]).to be_nil
225 | expect(@tuple.last[:comment_offset]).to be_nil
226 | expect(@tuple.last[:comment_sep]).to be_nil
227 | end
228 | end
229 |
230 | describe 'with "[section with whitespace]"' do
231 | it 'should return a section tuple with the correct key' do
232 | expect(IniParse::Parser.parse_line('[section with whitespace]')).to \
233 | be_section_tuple('section with whitespace')
234 | end
235 | end
236 |
237 | describe 'with "[ section with surounding whitespace ]"' do
238 | it 'should return a section tuple with the correct key' do
239 | expect(IniParse::Parser.parse_line('[ section with surounding whitespace ]')).to \
240 | be_section_tuple(' section with surounding whitespace ')
241 | end
242 | end
243 |
244 | describe 'with "[section] ; a comment"' do
245 | before(:all) do
246 | @tuple = IniParse::Parser.parse_line('[section] ; a comment')
247 | end
248 |
249 | it 'should return a section tuple' do
250 | expect(@tuple).to be_section_tuple('section')
251 | end
252 |
253 | it 'should set the comment to "a comment"' do
254 | expect(@tuple).to be_section_tuple(:any, :comment => 'a comment')
255 | end
256 |
257 | it 'should set the comment separator to ";"' do
258 | expect(@tuple).to be_section_tuple(:any, :comment_sep => ';')
259 | end
260 |
261 | it 'should set the comment offset to 10' do
262 | expect(@tuple).to be_section_tuple(:any, :comment_offset => 10)
263 | end
264 | end
265 |
266 | describe 'with "[section;with#comment;chars]"' do
267 | before(:all) do
268 | @tuple = IniParse::Parser.parse_line('[section;with#comment;chars]')
269 | end
270 |
271 | it 'should return a section tuple with the correct key' do
272 | expect(@tuple).to be_section_tuple('section;with#comment;chars')
273 | end
274 |
275 | it 'should not set a comment' do
276 | expect(@tuple.last[:indent]).to be_nil
277 | expect(@tuple.last[:comment]).to be_nil
278 | expect(@tuple.last[:comment_offset]).to be_nil
279 | expect(@tuple.last[:comment_sep]).to be_nil
280 | end
281 | end
282 |
283 | describe 'with "[section;with#comment;chars] ; a comment"' do
284 | before(:all) do
285 | @tuple = IniParse::Parser.parse_line('[section;with#comment;chars] ; a comment')
286 | end
287 |
288 | it 'should return a section tuple with the correct key' do
289 | expect(@tuple).to be_section_tuple('section;with#comment;chars')
290 | end
291 |
292 | it 'should set the comment to "a comment"' do
293 | expect(@tuple).to be_section_tuple(:any, :comment => 'a comment')
294 | end
295 |
296 | it 'should set the comment separator to ";"' do
297 | expect(@tuple).to be_section_tuple(:any, :comment_sep => ';')
298 | end
299 |
300 | it 'should set the comment offset to 29' do
301 | expect(@tuple).to be_section_tuple(:any, :comment_offset => 29)
302 | end
303 | end
304 |
305 | # --
306 | # ==========================================================================
307 | # Comment lines.
308 | # ==========================================================================
309 | # ++
310 |
311 | describe 'with "; a comment"' do
312 | before(:all) do
313 | @tuple = IniParse::Parser.parse_line('; a comment')
314 | end
315 |
316 | it 'should return a comment tuple with the correct comment' do
317 | expect(@tuple).to be_comment_tuple('a comment')
318 | end
319 |
320 | it 'should set the comment separator to ";"' do
321 | expect(@tuple).to be_comment_tuple(:any, :comment_sep => ';')
322 | end
323 |
324 | it 'should set the comment offset to 0' do
325 | expect(@tuple).to be_comment_tuple(:any, :comment_offset => 0)
326 | end
327 | end
328 |
329 | describe 'with " ; a comment"' do
330 | before(:all) do
331 | @tuple = IniParse::Parser.parse_line(' ; a comment')
332 | end
333 |
334 | it 'should return a comment tuple with the correct comment' do
335 | expect(@tuple).to be_comment_tuple('a comment')
336 | end
337 |
338 | it 'should set the comment separator to ";"' do
339 | expect(@tuple).to be_comment_tuple(:any, :comment_sep => ';')
340 | end
341 |
342 | it 'should set the comment offset to 1' do
343 | expect(@tuple).to be_comment_tuple(:any, :comment_offset => 1)
344 | end
345 | end
346 |
347 | describe 'with ";"' do
348 | before(:all) do
349 | @tuple = IniParse::Parser.parse_line(';')
350 | end
351 |
352 | it 'should return a comment tuple with an empty value' do
353 | expect(@tuple).to be_comment_tuple('')
354 | end
355 |
356 | it 'should set the comment separator to ";"' do
357 | expect(@tuple).to be_comment_tuple(:any, :comment_sep => ';')
358 | end
359 |
360 | it 'should set the comment offset to 0' do
361 | expect(@tuple).to be_comment_tuple(:any, :comment_offset => 0)
362 | end
363 | end
364 |
365 | # --
366 | # ==========================================================================
367 | # Blank lines.
368 | # ==========================================================================
369 | # ++
370 |
371 | describe 'with ""' do
372 | it 'should return a blank tuple' do
373 | expect(IniParse::Parser.parse_line('')).to be_blank_tuple
374 | end
375 | end
376 |
377 | describe 'with " "' do
378 | it 'should return a blank tuple' do
379 | expect(IniParse::Parser.parse_line(' ')).to be_blank_tuple
380 | end
381 | end
382 | end
383 |
--------------------------------------------------------------------------------
/spec/spec_fixtures.rb:
--------------------------------------------------------------------------------
1 | module IniParse
2 | module Test
3 | class Fixtures
4 | @@fixtures = {}
5 |
6 | def self.[](fix)
7 | if @@fixtures.has_key?(fix)
8 | @@fixtures[fix]
9 | else
10 | @@fixtures[fix] = File.read(
11 | File.join(File.expand_path('fixtures', File.dirname(__FILE__)), fix)
12 | )
13 | end
14 | end
15 |
16 | def self.[]=(fix, val)
17 | @@fixtures[fix] = val
18 | end
19 | end
20 | end
21 | end
22 |
23 | IniParse::Test::Fixtures[:comment_before_section] = <<-FIX.gsub(/^ /, '')
24 | ; This is a comment
25 | [first_section]
26 | key = value
27 | FIX
28 |
29 | IniParse::Test::Fixtures[:blank_before_section] = <<-FIX.gsub(/^ /, '')
30 |
31 | [first_section]
32 | key = value
33 | FIX
34 |
35 | IniParse::Test::Fixtures[:blank_in_section] = <<-FIX.gsub(/^ /, '')
36 | [first_section]
37 |
38 | key = value
39 | FIX
40 |
41 | IniParse::Test::Fixtures[:option_before_section] = <<-FIX.gsub(/^ /, '')
42 | foo = bar
43 | [foo]
44 | another = thing
45 | FIX
46 |
47 | IniParse::Test::Fixtures[:invalid_line] = <<-FIX.gsub(/^ /, '')
48 | this line is not valid
49 | FIX
50 |
51 | IniParse::Test::Fixtures[:section_with_equals] = <<-FIX.gsub(/^ /, '')
52 | [first_section = name]
53 | key = value
54 | [another_section = a name]
55 | another = thing
56 | FIX
57 |
58 | IniParse::Test::Fixtures[:comment_line] = <<-FIX.gsub(/^ /, '')
59 | [first_section]
60 | ; block comment ;
61 | ; with more lines ;
62 | key = value
63 | FIX
64 |
65 | IniParse::Test::Fixtures[:duplicate_section] = <<-FIX.gsub(/^ /, '')
66 | [first_section]
67 | key = value
68 | another = thing
69 |
70 | [second_section]
71 | okay = yes
72 |
73 | [first_section]
74 | third = fourth
75 | another = again
76 | FIX
77 |
78 | IniParse::Test::Fixtures[:dos_endings] =
79 | "[database]\r\n" \
80 | "first = true\r\n" \
81 | "second = false\r\n"
82 |
83 | # https://github.com/antw/iniparse/issues/17
84 | IniParse::Test::Fixtures[:anon_section_with_comments] = <<-FIX.gsub(/^ /, '')
85 | #####################
86 | # A lot of comments #
87 | #####################
88 |
89 | # optiona comment
90 | optiona = A
91 |
92 | # optionb comment
93 | optionb = B
94 |
95 | # optionc comment
96 | optionc = C
97 | FIX
98 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | $:.push File.join(File.dirname(__FILE__), '..', 'lib')
2 |
3 | require 'rubygems'
4 | require 'rspec'
5 |
6 | require 'iniparse'
7 | require File.join(File.dirname(__FILE__), 'spec_fixtures')
8 |
9 | module IniParse
10 | module Test
11 | module Helpers
12 | # Taken from Merb Core's spec helper.
13 | # Merb is licenced using the MIT License and is copyright
14 | # Engine Yard Inc.
15 | class BeKindOf
16 | def initialize(expected) # + args
17 | @expected = expected
18 | end
19 |
20 | def matches?(target)
21 | @target = target
22 | @target.kind_of?(@expected)
23 | end
24 |
25 | def failure_message
26 | "expected #{@expected} but got #{@target.class}"
27 | end
28 |
29 | def failure_message_when_negated
30 | "expected #{@expected} to not be #{@target.class}"
31 | end
32 |
33 | def description
34 | "be_kind_of #{@target}"
35 | end
36 | end
37 |
38 | # Used to match line tuples returned by Parser.parse_line.
39 | class BeLineTuple
40 | def initialize(type, value_keys = [], *expected)
41 | @expected_type = type
42 | @value_keys = value_keys
43 |
44 | if expected.nil?
45 | @expected_opts = {}
46 | @expected_values = []
47 | else
48 | @expected_opts = expected.pop
49 | @expected_values = expected
50 | end
51 | end
52 |
53 | def matches?(tuple)
54 | @tuple = tuple
55 |
56 | @failure_message = catch(:fail) do
57 | tuple?
58 | correct_type?
59 | correct_values?
60 | correct_opts?
61 | true
62 | end
63 |
64 | @failure_message == true
65 | end
66 |
67 | def failure_message
68 | "expected #{@expected_type} tuple #{@failure_message}"
69 | end
70 |
71 | def failure_message_when_negated
72 | "expected #{@tuple.inspect} to not be #{@expected_type} tuple"
73 | end
74 |
75 | def description
76 | "be_#{@expected_type}_tuple #{@tuple}"
77 | end
78 |
79 | #######
80 | private
81 | #######
82 |
83 | # Matchers.
84 |
85 | def tuple?
86 | throw :fail, "but was #{@tuple.class}" unless @tuple.kind_of?(Array)
87 | end
88 |
89 | def correct_type?
90 | throw :fail, "but was #{type} tuple" unless type == @expected_type
91 | end
92 |
93 | def correct_values?
94 | # Make sure the values match.
95 | @value_keys.each_with_index do |key, i|
96 | if @expected_values[i] != :any && values[i] != @expected_values[i]
97 | throw :fail, 'with %s value of "%s" but was "%s"' % [
98 | key, values[i], @expected_values[i]
99 | ]
100 | end
101 | end
102 | end
103 |
104 | def correct_opts?
105 | if(! @expected_opts.nil?)
106 | if (! @expected_opts.empty?) && opts.empty?
107 | throw :fail, 'with options but there were none'
108 | end
109 |
110 | @expected_opts.each do |key, value|
111 | unless opts.has_key?(key)
112 | throw :fail, 'with option "%s", but key was not present' % key
113 | end
114 |
115 | unless opts[key] == value
116 | throw :fail, 'with option "%s" => "%s" but was "%s"' % [
117 | key, value, opts[key]
118 | ]
119 | end
120 | end
121 | end
122 | end
123 |
124 | # Tuple values, etc.
125 |
126 | def type
127 | @type ||= @tuple.first
128 | end
129 |
130 | def values
131 | @values ||= @tuple.length < 3 ? [] : @tuple[1..-2]
132 | end
133 |
134 | def opts
135 | @opts ||= @tuple.last
136 | end
137 | end
138 |
139 | def be_kind_of(expected) # + args
140 | BeKindOf.new(expected)
141 | end
142 |
143 | def be_section_tuple(key = :any, opts = {})
144 | BeLineTuple.new(:section, [:key], key, opts)
145 | end
146 |
147 | def be_option_tuple(key = :any, value = :any, opts = {})
148 | BeLineTuple.new(:option, [:key, :value], key, value, opts)
149 | end
150 |
151 | def be_blank_tuple
152 | BeLineTuple.new(:blank)
153 | end
154 |
155 | def be_comment_tuple(comment = :any, opts = {})
156 | BeLineTuple.new(:comment, [:comment], comment, opts)
157 | end
158 |
159 | def fixture(fix)
160 | IniParse::Test::Fixtures[fix]
161 | end
162 | end
163 | end
164 | end
165 |
166 | RSpec.configure do |config|
167 | config.include(IniParse::Test::Helpers)
168 | end
169 |
--------------------------------------------------------------------------------
/spec/spec_helper_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | # --
4 | # ============================================================================
5 | # Empty array.
6 | # ============================================================================
7 | # ++
8 |
9 | describe 'An empty array' do
10 | it 'should not pass be_section_tuple' do
11 | expect([]).not_to be_section_tuple
12 | end
13 |
14 | it 'should not pass be_option_tuple' do
15 | expect([]).not_to be_option_tuple
16 | end
17 |
18 | it 'should not pass be_blank_tuple' do
19 | expect([]).not_to be_blank_tuple
20 | end
21 |
22 | it 'should not pass be_comment_tuple' do
23 | expect([]).not_to be_comment_tuple
24 | end
25 | end
26 |
27 | # --
28 | # ============================================================================
29 | # Section tuple.
30 | # ============================================================================
31 | # ++
32 |
33 | describe 'Line tuple [:section, "key", {:opt => "val"}]' do
34 | before(:all) { @tuple = [:section, "key", {:opt => "val"}] }
35 |
36 | it 'should pass be_section_tuple' do
37 | expect(@tuple).to be_section_tuple
38 | end
39 |
40 | it 'should pass be_section_tuple("key")' do
41 | expect(@tuple).to be_section_tuple("key")
42 | end
43 |
44 | it 'should fail be_section_tuple("invalid")' do
45 | expect(@tuple).not_to be_section_tuple("invalid")
46 | end
47 |
48 | it 'should pass be_section_tuple("key", {:opt => "val"})' do
49 | expect(@tuple).to be_section_tuple("key", {:opt => "val"})
50 | end
51 |
52 | it 'should not pass be_section_tuple("key", {:invalid => "val"})' do
53 | expect(@tuple).not_to be_section_tuple("key", {:invalid => "val"})
54 | end
55 |
56 | it 'should not pass be_section_tuple("key", {:opt => "invalid"})' do
57 | expect(@tuple).not_to be_section_tuple("key", {:opt => "invalid"})
58 | end
59 |
60 | it 'should fail be_option_tuple' do
61 | expect(@tuple).not_to be_option_tuple
62 | end
63 |
64 | it 'should fail be_blank_tuple' do
65 | expect(@tuple).not_to be_blank_tuple
66 | end
67 |
68 | it 'should fail be_comment_tuple' do
69 | expect(@tuple).not_to be_comment_tuple
70 | end
71 | end
72 |
73 | # --
74 | # ============================================================================
75 | # Option tuple.
76 | # ============================================================================
77 | # ++
78 |
79 | describe 'Line tuple [:option, "key", "val", {:opt => "val"}]' do
80 | before(:all) { @tuple = [:option, "key", "val", {:opt => "val"}] }
81 |
82 | it 'should fail be_section_tuple' do
83 | expect(@tuple).not_to be_section_tuple
84 | end
85 |
86 | it 'should pass be_option_tuple' do
87 | expect(@tuple).to be_option_tuple
88 | end
89 |
90 | it 'should pass be_option_tuple("key")' do
91 | expect(@tuple).to be_option_tuple("key")
92 | end
93 |
94 | it 'should fail be_option_tuple("invalid")' do
95 | expect(@tuple).not_to be_option_tuple("invalid")
96 | end
97 |
98 | it 'should pass be_option_tuple("key", "val")' do
99 | expect(@tuple).to be_option_tuple("key", "val")
100 | end
101 |
102 | it 'should pass be_option_tuple(:any, "val")' do
103 | expect(@tuple).to be_option_tuple(:any, "val")
104 | end
105 |
106 | it 'should fail be_option_tuple("key", "invalid")' do
107 | expect(@tuple).not_to be_option_tuple("key", "invalid")
108 | end
109 |
110 | it 'should pass be_option_tuple("key", "val", { :opt => "val" })' do
111 | expect(@tuple).to be_option_tuple("key", "val", { :opt => "val" })
112 | end
113 |
114 | it 'should fail be_option_tuple("key", "val", { :invalid => "val" })' do
115 | expect(@tuple).not_to be_option_tuple("key", "val", { :invalid => "val" })
116 | end
117 |
118 | it 'should fail be_option_tuple("key", "val", { :opt => "invalid" })' do
119 | expect(@tuple).not_to be_option_tuple("key", "val", { :opt => "invalid" })
120 | end
121 |
122 | it 'should fail be_blank_tuple' do
123 | expect(@tuple).not_to be_blank_tuple
124 | end
125 |
126 | it 'should fail be_comment_tuple' do
127 | expect(@tuple).not_to be_comment_tuple
128 | end
129 | end
130 |
131 | # --
132 | # ============================================================================
133 | # Blank tuple.
134 | # ============================================================================
135 | # ++
136 |
137 | describe 'Line tuple [:blank]' do
138 | before(:all) { @tuple = [:blank] }
139 |
140 | it 'should fail be_section_tuple' do
141 | expect(@tuple).not_to be_section_tuple
142 | end
143 |
144 | it 'should fail be_option_tuple' do
145 | expect(@tuple).not_to be_option_tuple
146 | end
147 |
148 | it 'should pass be_blank_tuple' do
149 | expect(@tuple).to be_blank_tuple
150 | end
151 |
152 | it 'should fail be_comment_tuple' do
153 | expect(@tuple).not_to be_comment_tuple
154 | end
155 | end
156 |
157 | # --
158 | # ============================================================================
159 | # Coment tuple.
160 | # ============================================================================
161 | # ++
162 |
163 | describe 'Line tuple [:comment, "A comment", {:opt => "val"}]' do
164 | before(:all) { @tuple = [:comment, "A comment", {:opt => "val"}] }
165 |
166 | it 'should fail be_section_tuple' do
167 | expect(@tuple).not_to be_section_tuple
168 | end
169 |
170 | it 'should fail be_option_tuple' do
171 | expect(@tuple).not_to be_option_tuple
172 | end
173 |
174 | it 'should fail be_blank_tuple' do
175 | expect(@tuple).not_to be_blank_tuple
176 | end
177 |
178 | it 'should pass be_comment_tuple' do
179 | expect(@tuple).to be_comment_tuple
180 | end
181 |
182 | it 'should pass be_comment_tuple("A comment")' do
183 | expect(@tuple).to be_comment_tuple("A comment")
184 | end
185 |
186 | it 'should fail be_comment_tuple("Invalid")' do
187 | expect(@tuple).not_to be_comment_tuple("Invalid")
188 | end
189 |
190 | it 'should pass be_comment_tuple("A comment", {:opt => "val"})' do
191 | expect(@tuple).to be_comment_tuple("A comment", {:opt => "val"})
192 | end
193 |
194 | it 'should fail be_comment_tuple("A comment", {:invalid => "val"})' do
195 | expect(@tuple).not_to be_comment_tuple("A comment", {:invalid => "val"})
196 | end
197 |
198 | it 'should fail be_comment_tuple("A comment", {:opt => "invalid"})' do
199 | expect(@tuple).not_to be_comment_tuple("A comment", {:opt => "invalid"})
200 | end
201 | end
202 |
--------------------------------------------------------------------------------