sexy_settings 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .idea/*
3
+ .bundle
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in sexy_settings.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Sexy Settings
2
+
3
+ Flexible specifying of application settings
4
+
5
+ ## What is Sexy Settings?
6
+
7
+ It is Ruby library for flexible specifying of application settings different ways:
8
+
9
+ * from YAML file(default and custom settings)
10
+ * from command line
11
+
12
+ ### What's new in 0.0.1
13
+
14
+ Init version with base functionality
15
+
16
+ ## Getting Started
17
+
18
+ ### Prerequisites
19
+
20
+ It was tested with Ruby 1.9.2, but assumes it should work with other Ruby version as well
21
+ ### Installation
22
+
23
+ > gem install sexy_settings
24
+
25
+ ### Configuration
26
+
27
+ Create 2 configuration files, for default and custom settings. For instance,
28
+
29
+ ```
30
+ config\
31
+ default.yaml
32
+ overwritten.yaml
33
+ ```
34
+
35
+ Place next code to boot executable ruby file:
36
+
37
+ ```ruby
38
+ require 'sexy_settings'
39
+
40
+ SexySettings.configure do |config|
41
+ config.path_to_default_settings = File.expand_path("config.yaml", File.join(File.dirname(__FILE__), '..', 'config')) # 'default.yml' by default
42
+ config.path_to_custom_settings = File.expand_path("overwritten.yaml", File.join(File.dirname(__FILE__), '..', 'config')) # 'custom.yml' by default
43
+ config.path_to_project = File.dirname(__FILE__) # '.' by default
44
+ config.env_variable_with_options = 'OPTIONS' # 'OPTS' by default
45
+ cmd_line_option_delimiter = '$$$' # ',' by default
46
+ end
47
+ ```
48
+
49
+ Specify shortcut method for Settings object:
50
+
51
+ ```ruby
52
+ def settings
53
+ SexySettings::Base.instance()
54
+ end
55
+ ```
56
+
57
+ ### Using
58
+
59
+ There are 4 possible values of settings
60
+ The priority ranks with respect to the setting places are as follows:
61
+
62
+ > **command line** < **custom** < **default** < **nil**_(in case setting was not specified anywhere)_
63
+
64
+ Thus, specifying some setting in command line will override the same setting value specified in <_default config file_> or <_custom config file_>
65
+
66
+ Example:
67
+
68
+ _default.yaml_
69
+
70
+ ```yaml
71
+ foo: bar
72
+ foo1: default ${foo}
73
+ foo2: default value
74
+ ```
75
+
76
+ _overwritten.yaml_
77
+
78
+ ```yaml
79
+ foo1: custom ${foo}
80
+ ```
81
+
82
+ Set environment variable:
83
+
84
+ > OPTIONS="foo2=10$$$foo3=:hi$$$foo4=true"
85
+
86
+ ```ruby
87
+ puts settings.foo # returns 'bar'
88
+ puts settings.foo1 # returns 'custom foo'
89
+ puts settings.foo2 # returns 10
90
+ puts settings.foo3 # returns :hi
91
+ puts settings.foo4 # returns true
92
+ ```
93
+
94
+
95
+ ## Hints
96
+
97
+ * Add <_default config file_> under version control system
98
+ * Add <_custom config file_> to ignore list
99
+ * Use command line with using Environment Variable for quick specifying setting in your Continuous Integration System
100
+ * Use next code for output all settings as pretty formatted text:
101
+
102
+ ```ruby
103
+ puts settings.as_formatted_text
104
+ ```
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Spec all functionality of gem'
4
+ task :spec_all do
5
+ system("rspec spec/*/")
6
+ end
@@ -0,0 +1,6 @@
1
+ require 'sexy_settings/version'
2
+ require 'sexy_settings/configuration'
3
+ require 'sexy_settings/base'
4
+ require 'sexy_settings/core'
5
+
6
+
@@ -0,0 +1,113 @@
1
+ require 'singleton'
2
+ require 'yaml'
3
+
4
+ module SexySettings
5
+ class Base
6
+ include Singleton
7
+ attr_reader :default, :custom, :command_line, :all
8
+
9
+ # Priorities: command_line > custom.yml > default.yml > nil
10
+ def initialize
11
+ config = SexySettings.configuration
12
+ @default = load_settings(config.path_to_default_settings)
13
+ @custom = load_settings(config.path_to_custom_settings)
14
+ @all = @default.merge(@custom)
15
+ @command_line = {}
16
+ unless ENV[config.env_variable_with_options].nil?
17
+ ENV[config.env_variable_with_options].split(config.cmd_line_option_delimiter).each{|opt_line| parse_setting(opt_line) if opt_line }
18
+ end
19
+ @all.merge!(@command_line)
20
+ @all.each_pair{|k, v| @all[k] = get_compound_value(v)}
21
+ end
22
+
23
+ def as_formatted_text(which=:all)
24
+ props_list = case which
25
+ when :all then @all
26
+ when :custom then @custom
27
+ when :default then @default
28
+ else ''
29
+ end.to_a
30
+ max_key_size = props_list.map{|el| el.first.to_s.size}.max
31
+ res = []
32
+ title = "##{' '*20}#{which.to_s.capitalize} Settings#{' '*21}#"
33
+ sharp_line = '#'*title.size
34
+ res << sharp_line
35
+ res << title
36
+ res << sharp_line
37
+ res << ''
38
+ res += props_list.map{|el| "#{indent}#{el[0]}#{indent + indent(max_key_size - el[0].to_s.size)}=#{indent}#{el[1]}"}.sort
39
+ res << ''
40
+ res.join("\n")
41
+ end
42
+
43
+ private
44
+
45
+ def load_settings(path)
46
+ File.exists?(path) ? YAML::load_file(path) : {}
47
+ end
48
+
49
+ def indent(space_count=nil)
50
+ " "*(space_count.nil? ? 2 : space_count)
51
+ end
52
+
53
+ # Parse the compound setting.
54
+ # Parts of this config_parser must be defined earlier.
55
+ # You can define an option as option=${another_option_name}/something
56
+ def get_compound_value(value)
57
+ if /\$\{(.*?)\}/.match(value.to_s)
58
+ var = /\$\{(.*?)\}/.match(value.to_s)[1]
59
+ exist_var = @all[var]
60
+ raise ArgumentError, "Did you define this setting '#{var}' before?" if exist_var.nil?
61
+ value["${#{var}}"] = exist_var.to_s if var
62
+ get_compound_value(value)
63
+ end
64
+ value
65
+ end
66
+
67
+ def method_missing(name, *args)
68
+ if name.to_s =~ /=$/
69
+ @all[name.to_s] = args[0] if @all.has_key?(name.to_s)
70
+ else
71
+ @all[name.to_s]
72
+ end
73
+ end
74
+
75
+ # Try to convert a value from String to other types (int, boolean, float)
76
+ # Return the value as String if all tries have failed.
77
+ # If the value is not of String type, return it as is.
78
+ def try_convert_value(value)
79
+ if value.class == String
80
+ if /^[0-9]+$/.match(value) #int
81
+ value.to_i
82
+ elsif /^[0-9]+(\.)[0-9]*$/.match(value) #float
83
+ value.to_f
84
+ elsif (value.downcase == 'true') #boolean
85
+ true
86
+ elsif (value.downcase == 'false') #boolean
87
+ false
88
+ elsif /^:(.+)$/.match(value)
89
+ $1.to_sym
90
+ else
91
+ value # can't parse, return String
92
+ end
93
+ else # value is not String, return it as is
94
+ value
95
+ end
96
+ end
97
+
98
+ # Try to parse the setting's line
99
+ # Delimiter is "=", ignores double quotes in the start and end positions
100
+ def parse_setting(line)
101
+ raise ArgumentError, "Invalid pair: #{line}. Expected: name=value" unless line["="]
102
+ param, value = line.split(/\s*=\s*/, 2)
103
+ var_name = param.chomp.strip
104
+ value = value.chomp.strip if value
105
+ new_value = ''
106
+ if (value)
107
+ new_value = (value =~ /^["](.*)["]$/) ? $1 : value
108
+ end
109
+ new_value = try_convert_value(new_value)
110
+ @command_line[var_name] = new_value
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,21 @@
1
+ module SexySettings
2
+ class Configuration
3
+ DEFAULT_OPTIONS = {
4
+ :path_to_default_settings => "default.yml",
5
+ :path_to_custom_settings => "custom.yml",
6
+ :path_to_project => '.',
7
+ :env_variable_with_options => 'OPTS',
8
+ :cmd_line_option_delimiter => ','
9
+ }
10
+ DEFAULT_OPTIONS.keys.each{|option| attr_writer option}
11
+
12
+ def initialize
13
+ DEFAULT_OPTIONS.keys.each do |method|
14
+ self.class.send(:define_method, method) do
15
+ self.instance_variable_get("@#{method}") || DEFAULT_OPTIONS[method]
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module SexySettings
2
+ # Used internally to ensure examples get reloaded between multiple runs in
3
+ # the same process.
4
+ def self.reset
5
+ self.configuration.class::DEFAULT_OPTIONS.keys.each{|key| self.configuration.send("#{key}=", nil)}
6
+ end
7
+
8
+ # Returns the global configuration object
9
+ def self.configuration
10
+ @configuration ||= SexySettings::Configuration.new
11
+ end
12
+
13
+ # Yields the global configuration object
14
+ #
15
+ # == Examples
16
+ #
17
+ # SexySettings.configure do |config|
18
+ # config.env_variable_with_options = 'OPTIONS'
19
+ # end
20
+ def self.configure
21
+ if block_given?
22
+ yield configuration
23
+ else
24
+ self.configuration
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module SexySettings
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "sexy_settings/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "sexy_settings"
7
+ s.version = SexySettings::VERSION
8
+ s.authors = ["Roman Parashchenko"]
9
+ s.email = ["romikoops1@gmail.com"]
10
+ s.homepage = "https://github.com/romikoops/sexy_settings"
11
+ s.summary = %q{Flexible specifying of application settings}
12
+ s.description = %q{Library for flexible specifying of application settings different ways}
13
+ s.rubyforge_project = "sexy_settings"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+ s.add_development_dependency 'rspec'
20
+ end
@@ -0,0 +1,3 @@
1
+ default_property: default DEFAULT value
2
+ overwritten_property: default OVERWRITTEN value
3
+ console_property: default CONSOLE value
@@ -0,0 +1,2 @@
1
+ overwritten_property: overwritten OVERWRITTEN value
2
+ console_property: overwritten CONSOLE value
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Base' do
4
+ before :all do
5
+ SexySettings.configure do |config|
6
+ config.path_to_default_settings = File.expand_path("config.yaml", File.join(File.dirname(__FILE__), '..', '_config'))
7
+ config.path_to_custom_settings = File.expand_path("overwritten.yaml", File.join(File.dirname(__FILE__), '..', '_config'))
8
+ config.path_to_project = File.dirname(__FILE__)
9
+ config.env_variable_with_options = 'OPTIONS'
10
+ end
11
+ if ENV.has_key?(SexySettings.configuration.env_variable_with_options)
12
+ @original_options = ENV[SexySettings.configuration.env_variable_with_options]
13
+ else
14
+ @original_options = nil
15
+ end
16
+ ENV[SexySettings.configuration.env_variable_with_options] = "console_property=console CONSOLE value"
17
+ @settings ||= settings
18
+ end
19
+
20
+ after :all do
21
+ @original_options = ENV[SexySettings.configuration.env_variable_with_options]
22
+ unless @original_options
23
+ ENV[SexySettings.configuration.env_variable_with_options] = @original_options
24
+ end
25
+ end
26
+
27
+ it "should be singleton object" do
28
+ SexySettings::Base.respond_to?(:instance).should be_true
29
+ SexySettings::Base.instance.is_a?(SexySettings::Base).should be_true
30
+ end
31
+
32
+ it "should have getter for default setting" do
33
+ @settings.respond_to?(:default).should be_true
34
+ expected_default_settings = {
35
+ "default_property" => "default DEFAULT value",
36
+ "overwritten_property" => "default OVERWRITTEN value",
37
+ "console_property" => "default CONSOLE value"
38
+ }
39
+ @settings.default.should == expected_default_settings
40
+ end
41
+
42
+ it "should have getter for custom setting" do
43
+ @settings.respond_to?(:default).should be_true
44
+ expected_custom_settings = {
45
+ "overwritten_property" => "overwritten OVERWRITTEN value",
46
+ "console_property" => "overwritten CONSOLE value"
47
+ }
48
+ @settings.custom.should == expected_custom_settings
49
+ end
50
+
51
+ it "should have getter for all setting" do
52
+ @settings.respond_to?(:default).should be_true
53
+ expected_all_settings = {
54
+ "default_property" => "default DEFAULT value",
55
+ "overwritten_property" => "overwritten OVERWRITTEN value",
56
+ "console_property" => "console CONSOLE value"
57
+ }
58
+ @settings.all.should == expected_all_settings
59
+ end
60
+
61
+ it "should return specified pretty formatted settings for output" do
62
+ expected = <<-eos
63
+ #######################################################
64
+ # All Settings #
65
+ #######################################################
66
+
67
+ console_property = console CONSOLE value
68
+ default_property = default DEFAULT value
69
+ overwritten_property = overwritten OVERWRITTEN value
70
+ eos
71
+ @settings.as_formatted_text.should == expected
72
+ end
73
+
74
+ context "command line" do
75
+ before :all do
76
+ SexySettings.configure.env_variable_with_options = 'OPTS'
77
+ ENV['OPTS'] = "string=Test, int=1, float=1.09, boolean_true=true, boolean_false=false, symbol=:foo, reference = ${string}"
78
+ @clone_settings = settings.class.clone.instance
79
+ end
80
+
81
+ after :all do
82
+ SexySettings.configure.env_variable_with_options = 'OPTIONS'
83
+ end
84
+
85
+ it "should convert command line string value to String type" do
86
+ @clone_settings.string.should == 'Test'
87
+ end
88
+
89
+ it "should convert command line integer value to Fixnum type" do
90
+ @clone_settings.int.should == 1
91
+ @clone_settings.int.class.should == Fixnum
92
+ end
93
+
94
+ it "should convert command line float value to Float type" do
95
+ @clone_settings.float.should == 1.09
96
+ @clone_settings.float.class.should == Float
97
+ end
98
+
99
+ it "should convert command line true value to TrueClass type" do
100
+ @clone_settings.boolean_true.should be_true
101
+ end
102
+
103
+ it "should convert command line false value to FalseClass type" do
104
+ @clone_settings.boolean_false.should be_false
105
+ @clone_settings.boolean_false.class.should == FalseClass
106
+ end
107
+
108
+ it "should convert command line symbol value to Symbol type" do
109
+ @clone_settings.symbol.should == :foo
110
+ end
111
+
112
+ it "should replace command line reference to correct value" do
113
+ @clone_settings.reference == 'Test'
114
+ end
115
+ end
116
+ end
117
+
118
+ def settings
119
+ SexySettings::Base.instance()
120
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Configuration' do
4
+ before :all do
5
+ @expected_opts = {
6
+ :path_to_default_settings => "default.yml",
7
+ :path_to_custom_settings => "custom.yml",
8
+ :path_to_project => '.',
9
+ :env_variable_with_options => 'OPTS',
10
+ :cmd_line_option_delimiter => ','
11
+ }
12
+ @config = SexySettings::Configuration.new
13
+ end
14
+
15
+ it "should have correct default options" do
16
+ SexySettings::Configuration.constants.should include(:DEFAULT_OPTIONS)
17
+ SexySettings::Configuration::DEFAULT_OPTIONS.should == @expected_opts
18
+ end
19
+
20
+ it "should have setters for all options" do
21
+ @expected_opts.keys.each{|key| @config.respond_to?("#{key}=").should be_true}
22
+ end
23
+
24
+ it "should return last value of specified option" do
25
+ new_value = 'fake'
26
+ @expected_opts.keys.each{|key| @config.send("#{key}=", new_value)}
27
+ @expected_opts.keys.each{|key| @config.send(key).should == new_value}
28
+ end
29
+
30
+ it "should return default value of specified option" do
31
+ config = SexySettings::Configuration.new
32
+ @expected_opts.keys.each{|key| config.send(key).should == @expected_opts[key]}
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Core' do
4
+ it "should have ability to reset settings" do
5
+ new_delim = '#@#'
6
+ old_delim = nil
7
+ SexySettings.configure do |config|
8
+ old_delim = config.cmd_line_option_delimiter
9
+ config.cmd_line_option_delimiter = new_delim
10
+ end
11
+ SexySettings.configuration.cmd_line_option_delimiter.should ==(new_delim)
12
+ SexySettings.reset
13
+ SexySettings.configuration.cmd_line_option_delimiter.should ==(old_delim)
14
+ end
15
+
16
+ it "should return the same configuration object each time" do
17
+ config = SexySettings.configuration
18
+ config.is_a?(SexySettings::Configuration).should be_true
19
+ config.object_id.should == SexySettings.configuration.object_id
20
+ end
21
+
22
+ it "should have ability to configure Configuration object with block" do
23
+ SexySettings.configure do |config|
24
+ config.is_a?(SexySettings::Configuration).should be_true
25
+ end
26
+ end
27
+
28
+ it "should have ability to configure Configuration object without block" do
29
+ SexySettings.configure.is_a?(SexySettings::Configuration).should be_true
30
+ end
31
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Version' do
4
+ it "should contains VERSION constant with correct format" do
5
+ SexySettings.constants.should include(:VERSION)
6
+ SexySettings::VERSION.should match(/^\d+\.\d+\.\d+$/)
7
+ end
8
+ end
@@ -0,0 +1 @@
1
+ require 'sexy_settings'
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sexy_settings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Roman Parashchenko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-08 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &26780748 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *26780748
25
+ description: Library for flexible specifying of application settings different ways
26
+ email:
27
+ - romikoops1@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - README.md
35
+ - Rakefile
36
+ - lib/sexy_settings.rb
37
+ - lib/sexy_settings/base.rb
38
+ - lib/sexy_settings/configuration.rb
39
+ - lib/sexy_settings/core.rb
40
+ - lib/sexy_settings/version.rb
41
+ - sexy_settings.gemspec
42
+ - spec/_config/config.yaml
43
+ - spec/_config/overwritten.yaml
44
+ - spec/sexy_settings/base_spec.rb
45
+ - spec/sexy_settings/configuration_spec.rb
46
+ - spec/sexy_settings/core_spec.rb
47
+ - spec/sexy_settings/version_spec.rb
48
+ - spec/spec_helper.rb
49
+ homepage: https://github.com/romikoops/sexy_settings
50
+ licenses: []
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project: sexy_settings
69
+ rubygems_version: 1.8.11
70
+ signing_key:
71
+ specification_version: 3
72
+ summary: Flexible specifying of application settings
73
+ test_files: []