diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..f869cc0 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,23 @@ +require: + - rubocop-performance + - rubocop-rake +AllCops: + NewCops: enable +Layout/LineLength: + Max: 120 +Metrics/AbcSize: + Enabled: false +Metrics/BlockLength: + inherit_mode: + merge: + - Exclude + Exclude: + - lib/tasks/**/* + - spec/**/* +Metrics/MethodLength: + inherit_mode: + merge: + - Exclude + Max: 30 +Metrics/ModuleLength: + Enabled: false diff --git a/Gemfile.lock b/Gemfile.lock index 3eba580..69c5b32 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,13 +6,20 @@ PATH GEM remote: https://rubygems.org/ specs: + ast (2.4.2) coderay (1.1.3) diff-lcs (1.4.4) method_source (1.0.0) + parallel (1.20.1) + parser (3.0.1.1) + ast (~> 2.4.1) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) + rainbow (3.0.0) rake (12.3.3) + regexp_parser (2.1.1) + rexml (3.2.5) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) @@ -26,15 +33,36 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) rspec-support (3.9.3) + rubocop (1.15.0) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.5.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.7.0) + parser (>= 3.0.1.1) + rubocop-performance (1.11.3) + rubocop (>= 1.7.0, < 2.0) + rubocop-ast (>= 0.4.0) + rubocop-rake (0.5.1) + rubocop + ruby-progressbar (1.11.0) + unicode-display_width (2.0.0) PLATFORMS ruby DEPENDENCIES dotini! - pry (~> 0.14.1) + pry (~> 0.14) rake (~> 12.3) rspec (~> 3.0) + rubocop (~> 1.15) + rubocop-performance (~> 1.11) + rubocop-rake (~> 0.5) BUNDLED WITH 2.1.4 diff --git a/README.md b/README.md index d5c082d..d511b09 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,68 @@ Or install it yourself as: ## Usage -TODO +### Reading an INI file + +Given this INI file: + +```ini +[main] +username = foo +; this is a color +; the color is nice +color = red + +[personal] +color = cyan +path = /tmp ; TODO: change later +``` + +It can be read with: + +```ruby + ini_file = Dotini::IniFile.load(filename, options) # options are not required + + ini_file['main']['color'].value # => 'red' + ini_file['main']['color'].prepended_comments # => ['; this is a color', '; the color is nice'] + ini_file['personal']['path'].value # => '/tmp' + ini_file['personal']['path'].inline_comment # => '; TODO: change later' +``` + +These are the available options: + +- `comment_character` (default: `';'`) +- `key_pattern` (default: `Dotini::IniFile::DEFAULT_KEY_PATTERN`) +- `value_pattern` (default: `Dotini::IniFile::DEFAULT_VALUE_PATTERN`) + +### Creating a new INI file + +```ruby + ini_file = Dotini::IniFile.new + ini_file['profile default']['color'] = 'blue' + ini_file['preferences']['width'] = 42 +``` + +### Saving the INI file + +Given the INI file above, it can be turned into a string: + +```ruby + ini_file.to_s # => "[profile default]\ncolor = blue\n[preferences]\nwidth = 42\n" +``` + +...or it can be written to a IO stream: + +```ruby + File.open('new-file.ini', 'wb') do |file| + ini_file.write(file) + end +``` + +### Converting the INI file to a hash + +```ruby + ini_file.to_h # => { 'profile default' => { 'color' => 'blue' }, 'preferences' => { 'width' => '42' } } +``` ## Development @@ -35,7 +96,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To Bug reports and pull requests are welcome on GitHub at https://github.com/wendelscardua/dotini. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/wendelscardua/dotini/blob/master/CODE_OF_CONDUCT.md). - ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/dotini.gemspec b/dotini.gemspec index 11c88d4..d8f6c7f 100644 --- a/dotini.gemspec +++ b/dotini.gemspec @@ -18,7 +18,10 @@ Gem::Specification.new do |spec| spec.metadata['source_code_uri'] = spec.homepage spec.metadata['changelog_uri'] = spec.homepage - spec.add_development_dependency 'pry', '~> 0.14.1' + spec.add_development_dependency 'pry', '~> 0.14' + spec.add_development_dependency 'rubocop', '~> 1.15' + spec.add_development_dependency 'rubocop-performance', '~> 1.11' + spec.add_development_dependency 'rubocop-rake', '~> 0.5' # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. diff --git a/lib/dotini.rb b/lib/dotini.rb index 093efe7..d4b3d73 100644 --- a/lib/dotini.rb +++ b/lib/dotini.rb @@ -4,6 +4,7 @@ require 'dotini/key_value_pair' require 'dotini/section' require 'dotini/ini_file' +require 'stringio' # Root module for Dotini module Dotini; end diff --git a/lib/dotini/ini_file.rb b/lib/dotini/ini_file.rb index a805a29..c42415a 100644 --- a/lib/dotini/ini_file.rb +++ b/lib/dotini/ini_file.rb @@ -8,15 +8,18 @@ class IniFile attr_accessor :sections + # Creates a new, empty INI file def initialize @sections = [] end + # Retrieves an existing section, or creates a new one def [](name) sections.find { |section| section.name == name } || Section.new(name).tap { |section| sections << section } end + # Represents the current INI file as a hash def to_h {}.tap do |hash| sections.each do |section| @@ -29,6 +32,7 @@ def to_h end end + # Represents the current INI file as a string def to_s buffer = StringIO.new sections.each do |section| @@ -42,11 +46,19 @@ def write(io_stream) end class << self + # Loads an INI file by name + # The options are: + # - comment_character: which character is used for comments + # - key_pattern: a regexp that matches the property keys + # - value_pattern: a regexp that matches the property values def load(filename, - comment_character = ';', - key_pattern = DEFAULT_KEY_PATTERN, - value_pattern = DEFAULT_VALUE_PATTERN) - line_pattern = /\A(?#{key_pattern})\s*=\s*(?#{value_pattern})(?:\s*(?#{comment_character}.*))?\z/ + comment_character: ';', + key_pattern: DEFAULT_KEY_PATTERN, + value_pattern: DEFAULT_VALUE_PATTERN) + line_pattern = /\A(?#{key_pattern}) + \s*=\s* + (?#{value_pattern}) + (?:\s*(?#{comment_character}.*))?\z/x ini_file = IniFile.new current_section = Section.new(nil) current_key_value_pair = KeyValuePair.new diff --git a/lib/dotini/key_value_pair.rb b/lib/dotini/key_value_pair.rb index f70ce83..f5d8e12 100644 --- a/lib/dotini/key_value_pair.rb +++ b/lib/dotini/key_value_pair.rb @@ -5,6 +5,7 @@ module Dotini class KeyValuePair attr_accessor :key, :value, :prepended_comments, :inline_comment + # Creates a new, undefined key/value pair with no comments def initialize @key = nil @value = nil @@ -12,6 +13,7 @@ def initialize @inline_comment = nil end + # Represents the key/value pair as a string def to_s buffer = StringIO.new prepended_comments.each do |line| diff --git a/lib/dotini/section.rb b/lib/dotini/section.rb index 1f1a8b8..1613f55 100644 --- a/lib/dotini/section.rb +++ b/lib/dotini/section.rb @@ -5,11 +5,13 @@ module Dotini class Section attr_accessor :name, :key_value_pairs + # Creates a new, empty section def initialize(name) @name = name @key_value_pairs = [] end + # Retrieves a KeyValuePair from the section, or creates one def [](key) key_value_pairs.find { |key_pair| key_pair.key == key } || KeyValuePair.new.tap do |pair| @@ -18,19 +20,23 @@ def [](key) end end + # Sets a value for a key in this section def []=(key, value) self[key].value = value end + # Represents the section as a hash def to_h {}.tap do |hash| key_value_pairs.each do |pair| next if pair.key.nil? + hash[pair.key] = pair.value end end end + # Represents the section as a string def to_s buffer = StringIO.new buffer << "[#{name}]\n" unless name.nil?