├── .gitignore ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── configparser.gemspec ├── lib ├── configparser.rb └── configparser │ └── version.rb └── test ├── complex.cfg ├── configparser_example.cfg ├── helper.rb ├── simple.cfg ├── smb.cfg └── test_configparser.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in configparser.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 chrislee35 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Configparser 2 | 3 | Configparser parses configuration files compatible with Python's ConfigParser 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | gem 'configparser' 10 | 11 | And then execute: 12 | 13 | $ bundle 14 | 15 | Or install it yourself as: 16 | 17 | $ gem install configparser 18 | 19 | ## Usage 20 | 21 | $ cat test/simple.cfg 22 | test1=hi 23 | test2 = hello 24 | 25 | [first_section] 26 | mytest=55 27 | yourtest = 99 28 | #nothere=notthere 29 | myboolean 30 | 31 | [second section] 32 | myway=or the 33 | highway 34 | 35 | require 'configparser' 36 | cp = ConfigParser.new('test/simple.cfg') 37 | puts cp.to_s 38 | 39 | test1: hi 40 | test2: hello 41 | [first_section] 42 | myboolean 43 | mytest: 55 44 | yourtest: 99 45 | [second section] 46 | myway: or the highway 47 | 48 | $ cat test/complex.cfg 49 | global1=default-$(global3) 50 | global2=strange-$(global1) 51 | global3=whatever 52 | 53 | [section1] 54 | local1=$(global2)-$(local2)-local 55 | local2=yodel 56 | 57 | [section2] 58 | local1=hotel 59 | local2=recent $(local1) 60 | local3=un$(resolvable) 61 | 62 | cp = ConfigParser.new('test/complex.cfg') 63 | puts cp['global2'] 64 | puts cp['section1']['local1'] 65 | puts cp['section2']['local2'] 66 | puts cp['section2']['local3'] 67 | 68 | strange-default-whatever 69 | strange-default-whatever-yodel-local 70 | recent hotel 71 | un$(resolvable) 72 | 73 | 74 | ## Contributing 75 | 76 | 1. Fork it 77 | 2. Create your feature branch (`git checkout -b my-new-feature`) 78 | 3. Commit your changes (`git commit -am 'Add some feature'`) 79 | 4. Push to the branch (`git push origin my-new-feature`) 80 | 5. Create new Pull Request 81 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | 4 | require 'rake/testtask' 5 | 6 | Rake::TestTask.new do |t| 7 | t.libs << 'lib' 8 | t.test_files = FileList['test/test_*.rb'] 9 | t.verbose = true 10 | end 11 | 12 | task :default => :test -------------------------------------------------------------------------------- /configparser.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'configparser/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "configparser" 8 | spec.version = Configparser::VERSION 9 | spec.authors = ["chrislee35"] 10 | spec.email = ["rubygems@chrislee.dhs.org"] 11 | spec.description = %q{parses configuration files compatable with Python's ConfigParser} 12 | spec.summary = %q{parses configuration files compatable with Python's ConfigParser} 13 | spec.homepage = "https://github.com/chrislee35/configparser" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_development_dependency "minitest", "~> 5.5" 22 | spec.add_development_dependency "bundler", "~> 1.3" 23 | spec.add_development_dependency "rake" 24 | 25 | #spec.signing_key = "#{File.dirname(__FILE__)}/../gem-private_key.pem" 26 | #spec.cert_chain = ["#{File.dirname(__FILE__)}/../gem-public_cert.pem"] 27 | 28 | spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 29 | end 30 | -------------------------------------------------------------------------------- /lib/configparser.rb: -------------------------------------------------------------------------------- 1 | require "configparser/version" 2 | 3 | # DESCRIPTION: parses configuration files compatible with Python's ConfigParser 4 | 5 | class ConfigParser < Hash 6 | def initialize(fname = nil) 7 | self.parse(File.open(fname, "r").each_line) if fname 8 | end 9 | 10 | def parse(input_source) 11 | section = nil 12 | key = nil 13 | input_source.each do |line| 14 | next if (line =~ /^\s*(#|;)/) 15 | 16 | # parse out the lines of the config 17 | if line =~ /^\s*(.+?)\s*[=:]\s*(.*)$/ # handle key=value lines 18 | if section 19 | self[section] = {} unless self[section] 20 | key = $1 21 | if self[section][key] 22 | self[section][key] = [self[section][key]] unless self[section][key].is_a?(Array) 23 | self[section][key] << $2 24 | else 25 | self[section][key] = $2 26 | end 27 | else 28 | key = $1 29 | if self[key] 30 | self[key] = [self[key]] unless self[key].is_a?(Array) 31 | self[key] << $2 32 | else 33 | self[key] = $2 34 | end 35 | end 36 | elsif line =~ /^\s*\[(.+?)\]/ # handle new sections 37 | section = $1 38 | self[section] = {} unless self[section] 39 | elsif line =~ /^\s+(.+?)$/ # handle continued lines 40 | if section 41 | if self[section][key].is_a?(Array) 42 | self[section][key].last << " #{$1}"; 43 | else 44 | self[section][key] << " #{$1}"; 45 | end 46 | else 47 | if self[key].is_a?(Array) 48 | self[key].last << " #{$1}" 49 | else 50 | self[key] << " #{$1}" 51 | end 52 | end 53 | elsif line =~ /^([\w\d\_\-]+)$/ 54 | if section 55 | self[section] = {} unless self[section] 56 | key = $1 57 | if self[section][key] 58 | self[section][key] = [self[section][key]] unless self[section][key].is_a?(Array) 59 | self[section][key] << true 60 | else 61 | self[section][key] = true 62 | end 63 | else 64 | key = $1 65 | if self[key] 66 | self[key] = [self[key]] unless self[key].is_a?(Array) 67 | self[key] << true 68 | else 69 | self[key] = true 70 | end 71 | end 72 | end 73 | end 74 | 75 | # handle substitutions (globals first) 76 | changes = true 77 | while changes do 78 | changes = false 79 | self.each_key do |k| 80 | next if self[k].is_a? Hash 81 | next unless self[k].is_a? String 82 | self[k].gsub!(/\$\((.+?)\)/) {|x| 83 | changes = true if self[$1] 84 | self[$1] || "$(#{$1})" 85 | } 86 | end 87 | end 88 | 89 | # handle substitutions within the sections 90 | changes = true 91 | while changes do 92 | changes = false 93 | self.each_key do |k| 94 | next unless self[k].is_a? Hash 95 | self[k].each_key do |j| 96 | next unless self[k][j].is_a? String 97 | self[k][j].gsub!(/\$\((.+?)\)/) {|x| 98 | changes = true if self[k][$1] || self[$1] 99 | self[k][$1] || self[$1] || "$(#{$1})" 100 | } 101 | end 102 | end 103 | end 104 | end 105 | 106 | def to_s(sep=':') 107 | str = "" 108 | # print globals first 109 | self.keys.sort.each do |k| 110 | next if self[k].is_a? Hash 111 | if not self[k].is_a?(Array) 112 | self[k] = [self[k]] 113 | end 114 | self[k].each do |v| 115 | if v === true 116 | str << "#{k}\n" 117 | elsif v == "" 118 | str << "#{k}#{sep}\n" 119 | else 120 | str << "#{k}#{sep} #{v}\n" 121 | end 122 | end 123 | end 124 | 125 | # now print the sections 126 | self.keys.sort.each do |k| 127 | next unless self[k].is_a? Hash 128 | str << "[#{k}]\n" 129 | self[k].sort.each do |j,v| 130 | if not v.is_a?(Array) 131 | v = [v] 132 | end 133 | v.each do |v2| 134 | if v2 === true 135 | str << "#{j}\n" 136 | elsif v2 == "" 137 | str << "#{j}#{sep}\n" 138 | else 139 | str << "#{j}#{sep} #{v2}\n" 140 | end 141 | end 142 | end 143 | end 144 | str 145 | end 146 | end -------------------------------------------------------------------------------- /lib/configparser/version.rb: -------------------------------------------------------------------------------- 1 | class Configparser 2 | VERSION = "0.1.7" 3 | end 4 | -------------------------------------------------------------------------------- /test/complex.cfg: -------------------------------------------------------------------------------- 1 | global1=default-$(global3) 2 | global2=strange-$(global1) 3 | global3=whatever 4 | 5 | [section1] 6 | local1=$(global2)-$(local2)-local 7 | local2=yodel 8 | 9 | [section2] 10 | local1=hotel 11 | local2=recent $(local1) 12 | local3=un$(resolvable) -------------------------------------------------------------------------------- /test/configparser_example.cfg: -------------------------------------------------------------------------------- 1 | [Simple Values] 2 | key=value 3 | spaces in keys=allowed 4 | spaces in values=allowed as well 5 | spaces around the delimiter = obviously 6 | you can also use : to delimit keys from values 7 | 8 | [All Values Are Strings] 9 | values like this: 1000000 10 | or this: 3.14159265359 11 | are they treated as numbers? : no 12 | integers, floats and booleans are held as: strings 13 | can use the API to get converted values directly: true 14 | 15 | [Multiline Values] 16 | chorus: I'm a lumberjack, and I'm okay 17 | I sleep all night and I work all day 18 | 19 | [No Values] 20 | key_without_value 21 | empty string value here = 22 | 23 | [You can use comments] 24 | # like this 25 | ; or this 26 | 27 | # By default only in an empty line. 28 | # Inline comments can be harmful because they prevent users 29 | # from using the delimiting characters as parts of values. 30 | # That being said, this can be customized. 31 | 32 | [Sections Can Be Indented] 33 | can_values_be_as_well = True 34 | does_that_mean_anything_special = False 35 | purpose = formatting for readability 36 | multiline_values = are 37 | handled just fine as 38 | long as they are indented 39 | deeper than the first line 40 | of a value 41 | # Did I mention we can indent comments, too? -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'minitest/autorun' 2 | require 'minitest/test' 3 | require 'minitest/unit' 4 | require 'minitest/pride' 5 | include MiniTest::Assertions 6 | require File.expand_path('../../lib/configparser.rb', __FILE__) -------------------------------------------------------------------------------- /test/simple.cfg: -------------------------------------------------------------------------------- 1 | test1=hi 2 | test2 = hello 3 | 4 | [first_section] 5 | mytest=55 6 | yourtest = 99 7 | #nothere=notthere 8 | myboolean 9 | 10 | [second section] 11 | myway=or the 12 | highway -------------------------------------------------------------------------------- /test/smb.cfg: -------------------------------------------------------------------------------- 1 | [global] 2 | 3 | ## Browsing/Identification ### 4 | 5 | # Change this to the workgroup/NT-domain name your Samba server will part of 6 | workgroup = WORKGROUP 7 | 8 | # server string is the equivalent of the NT Description field 9 | server string = %h server (Samba, Ubuntu) 10 | 11 | # Windows Internet Name Serving Support Section: 12 | # WINS Support - Tells the NMBD component of Samba to enable its WINS Server 13 | # wins support = no 14 | 15 | # WINS Server - Tells the NMBD components of Samba to be a WINS Client 16 | # Note: Samba can be either a WINS Server, or a WINS Client, but NOT both 17 | ; wins server = w.x.y.z 18 | 19 | # This will prevent nmbd to search for NetBIOS names through DNS. 20 | dns proxy = no -------------------------------------------------------------------------------- /test/test_configparser.rb: -------------------------------------------------------------------------------- 1 | unless Kernel.respond_to?(:require_relative) 2 | module Kernel 3 | def require_relative(path) 4 | require File.join(File.dirname(caller[0]), path.to_str) 5 | end 6 | end 7 | end 8 | 9 | require_relative 'helper' 10 | 11 | class TestConfigparser < Minitest::Test 12 | def test_parse_a_simple_config 13 | cp = ConfigParser.new('test/simple.cfg') 14 | refute_nil(cp) 15 | assert_equal('hi',cp['test1']) 16 | assert_equal('hello',cp['test2']) 17 | assert_equal('55',cp['first_section']['mytest']) 18 | assert_equal('99',cp['first_section']['yourtest']) 19 | assert_nil(cp['first_section']['nothere']) 20 | assert_equal('or the highway',cp['second section']['myway']) 21 | end 22 | 23 | def test_convert_a_simple_config_to_a_string 24 | cp = ConfigParser.new('test/simple.cfg') 25 | doc = "test1: hi 26 | test2: hello 27 | [first_section] 28 | myboolean 29 | mytest: 55 30 | yourtest: 99 31 | [second section] 32 | myway: or the highway 33 | " 34 | assert_equal(doc, cp.to_s) 35 | end 36 | 37 | def test_parse_a_config_with_substitutions 38 | cp = ConfigParser.new('test/complex.cfg') 39 | refute_nil(cp) 40 | assert_equal('strange-default-whatever',cp['global2']) 41 | assert_equal('strange-default-whatever-yodel-local',cp['section1']['local1']) 42 | assert_equal('recent hotel',cp['section2']['local2']) 43 | assert_equal('un$(resolvable)',cp['section2']['local3']) 44 | end 45 | 46 | def test_parse_a_config_with_indents 47 | cp = ConfigParser.new('test/smb.cfg') 48 | refute_nil(cp) 49 | assert_equal("WORKGROUP", cp['global']["workgroup"]) 50 | assert_equal("%h server (Samba, Ubuntu)", cp['global']["server string"]) 51 | assert_equal("no", cp['global']["dns proxy"]) 52 | end 53 | 54 | def test_parse_from_non_file 55 | simple_content = < "hi", 74 | "test2" => "hello", 75 | "first_section" => { 76 | "mytest" => "55", 77 | "yourtest" => "99", 78 | "myboolean" => true 79 | }, 80 | "second section" => { 81 | "myway" => "or the highway" 82 | }}, cp) 83 | end 84 | 85 | def test_parse_configparser_example_from_python 86 | cp = ConfigParser.new('test/configparser_example.cfg') 87 | refute_nil(cp) 88 | doc = "[All Values Are Strings] 89 | are they treated as numbers?: no 90 | can use the API to get converted values directly: true 91 | integers, floats and booleans are held as: strings 92 | or this: 3.14159265359 93 | values like this: 1000000 94 | [Multiline Values] 95 | chorus: I'm a lumberjack, and I'm okay I sleep all night and I work all day 96 | [No Values] 97 | empty string value here: 98 | key_without_value 99 | [Sections Can Be Indented] 100 | can_values_be_as_well: True 101 | does_that_mean_anything_special: False 102 | multiline_values: are handled just fine as long as they are indented deeper than the first line of a value 103 | purpose: formatting for readability 104 | [Simple Values] 105 | key: value 106 | spaces around the delimiter: obviously 107 | spaces in keys: allowed 108 | spaces in values: allowed as well 109 | you can also use: to delimit keys from values 110 | [You can use comments] 111 | " 112 | 113 | assert_equal(doc, cp.to_s) 114 | end 115 | 116 | def test_nil_option 117 | nil_content = < { 125 | "foo" => "" 126 | }}, cp) 127 | assert_equal(nil_content, cp.to_s("=")) 128 | end 129 | 130 | def test_from_python_cfgparser 131 | content = <