sexy_settings 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/.rubocop.yml +23 -0
- data/.travis.yml +6 -0
- data/CONTRIBUTING.md +14 -0
- data/Gemfile +6 -1
- data/LICENSE_MIT +20 -0
- data/README.md +136 -104
- data/Rakefile +17 -5
- data/lib/sexy_settings/base.rb +66 -60
- data/lib/sexy_settings/configuration.rb +37 -21
- data/lib/sexy_settings/core.rb +30 -27
- data/lib/sexy_settings/printable.rb +53 -0
- data/lib/sexy_settings/sensitive_data_protector.rb +37 -0
- data/lib/sexy_settings/version.rb +2 -1
- data/lib/sexy_settings.rb +1 -2
- data/sexy_settings.gemspec +15 -12
- data/spec/_config/config.yaml +16 -3
- data/spec/_config/overwritten.yaml +1 -1
- data/spec/sexy_settings/base_spec.rb +140 -120
- data/spec/sexy_settings/configuration_spec.rb +46 -34
- data/spec/sexy_settings/core_spec.rb +32 -31
- data/spec/sexy_settings/version_spec.rb +9 -8
- data/spec/spec_helper.rb +2 -1
- metadata +58 -15
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'sensitive_data_protector'
|
3
|
+
module SexySettings
|
4
|
+
# This module holds print methods
|
5
|
+
module Printable
|
6
|
+
def as_formatted_text(which = :all)
|
7
|
+
props_list = property_list(which)
|
8
|
+
max_key_size = props_list.map { |el| el.first.to_s.size }.max
|
9
|
+
[
|
10
|
+
sharp_line(which),
|
11
|
+
title(which),
|
12
|
+
sharp_line(which),
|
13
|
+
'',
|
14
|
+
formatted_properties(props_list, max_key_size),
|
15
|
+
''
|
16
|
+
].join("\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def sharp_line(which)
|
22
|
+
'#' * title(which).size
|
23
|
+
end
|
24
|
+
|
25
|
+
def title(which)
|
26
|
+
"##{' ' * 20}#{which.to_s.capitalize} Settings#{' ' * 21}#"
|
27
|
+
end
|
28
|
+
|
29
|
+
def indent(space_count = nil)
|
30
|
+
' ' * (space_count.nil? ? 2 : space_count)
|
31
|
+
end
|
32
|
+
|
33
|
+
def property_list(which)
|
34
|
+
case which
|
35
|
+
when :all then @all
|
36
|
+
when :custom then @custom
|
37
|
+
when :default then @default
|
38
|
+
else ''
|
39
|
+
end.to_a
|
40
|
+
end
|
41
|
+
|
42
|
+
def formatted_properties(data, max_key_size)
|
43
|
+
data.sort_by(&:first).map do |(prop, value)|
|
44
|
+
value = protect_sensitive_data(prop, value)
|
45
|
+
"#{indent}#{prop}#{indent + indent(max_key_size - prop.to_s.size)}=#{indent}#{value}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def protect_sensitive_data(prop, value)
|
50
|
+
SensitiveDataProtector.new(prop, value).protected_value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module SexySettings
|
3
|
+
# This class holds logic sensitive data hiding
|
4
|
+
class SensitiveDataProtector
|
5
|
+
PROTECTED_PROPERTIES = [/pass(\z|word)/i, /_key\z/i, /secret/i, /token/i].freeze
|
6
|
+
URL_REGEXP = %r{\A(?:https?|ftp):\/\/(?:(?<userpass>.+)@)?.*:?(?:[^\/]*)}i
|
7
|
+
attr_reader :prop, :value
|
8
|
+
|
9
|
+
def initialize(prop, value)
|
10
|
+
@prop = prop
|
11
|
+
@value = value.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def protected_value
|
15
|
+
return hide_protected_data_in_url(value) if /_url\z/ =~ prop
|
16
|
+
return value unless PROTECTED_PROPERTIES.any? { |el| el =~ prop }
|
17
|
+
hide_protected_data(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def hide_protected_data(value)
|
21
|
+
return value if value.nil?
|
22
|
+
return '********' if value.to_s.size <= 4
|
23
|
+
"********#{value.to_s[-4..-1]}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def hide_protected_data_in_url(value)
|
27
|
+
return value if value.nil? || !(URL_REGEXP =~ value)
|
28
|
+
userpass = URL_REGEXP.match(value)[:userpass]
|
29
|
+
return value if userpass.nil? || userpass.empty?
|
30
|
+
value.sub(userpass, protected_userpass(userpass))
|
31
|
+
end
|
32
|
+
|
33
|
+
def protected_userpass(value)
|
34
|
+
value.split(':', 2).compact.map(&method(:hide_protected_data)).join(':')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/sexy_settings.rb
CHANGED
data/sexy_settings.gemspec
CHANGED
@@ -1,20 +1,23 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
4
|
+
require 'sexy_settings/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
7
|
+
s.name = 'sexy_settings'
|
7
8
|
s.version = SexySettings::VERSION
|
8
|
-
s.authors = [
|
9
|
-
s.email = [
|
10
|
-
s.homepage =
|
11
|
-
s.summary =
|
12
|
-
s.description =
|
13
|
-
s.rubyforge_project =
|
9
|
+
s.authors = ['Roman Parashchenko']
|
10
|
+
s.email = ['romikoops1@gmail.com']
|
11
|
+
s.homepage = 'https://github.com/romikoops/sexy_settings'
|
12
|
+
s.summary = 'Flexible specifying of application settings'
|
13
|
+
s.description = 'Library for flexible specifying of application settings different ways'
|
14
|
+
s.rubyforge_project = 'sexy_settings'
|
14
15
|
|
15
16
|
s.files = `git ls-files`.split("\n")
|
16
17
|
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 = [
|
19
|
-
s.add_development_dependency 'rspec'
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
|
+
s.require_paths = ['lib']
|
20
|
+
s.add_development_dependency 'rspec', '~>3.5'
|
21
|
+
s.add_development_dependency('rake')
|
22
|
+
s.add_development_dependency('yard')
|
20
23
|
end
|
data/spec/_config/config.yaml
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
-
default_property: default DEFAULT value
|
2
|
-
overwritten_property: default OVERWRITTEN value
|
3
|
-
console_property: default CONSOLE value
|
1
|
+
default_property: default DEFAULT value
|
2
|
+
overwritten_property: default OVERWRITTEN value
|
3
|
+
console_property: default CONSOLE value
|
4
|
+
pass: 123
|
5
|
+
user_pass: hello:world
|
6
|
+
api_key: '12341234123222333'
|
7
|
+
password_confirmation: 'mysuperpass'
|
8
|
+
passenger_name: 'Ivan Petrov'
|
9
|
+
api_token: wer221wfqw23ef
|
10
|
+
my_secret: ssdf3fvqww
|
11
|
+
email: user@example.com
|
12
|
+
test1_url: http://${email}:${user_pass}@host:80/wd/hub
|
13
|
+
test2_url: http://${email}@host/wd/hub
|
14
|
+
test3_url: http://:${user_pass}@host:80/wd/hub
|
15
|
+
test4_url:
|
16
|
+
test5_url: http://host/wd/hub
|
@@ -1,2 +1,2 @@
|
|
1
|
-
overwritten_property: overwritten OVERWRITTEN value
|
1
|
+
overwritten_property: overwritten OVERWRITTEN value
|
2
2
|
console_property: overwritten CONSOLE value
|
@@ -1,120 +1,140 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
config.
|
8
|
-
|
9
|
-
config.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
RSpec.describe 'Base' do
|
5
|
+
before :all do
|
6
|
+
SexySettings.configure do |config|
|
7
|
+
config.path_to_default_settings =
|
8
|
+
File.expand_path('config.yaml', File.join(File.dirname(__FILE__), '..', '_config'))
|
9
|
+
config.path_to_custom_settings =
|
10
|
+
File.expand_path('overwritten.yaml', File.join(File.dirname(__FILE__), '..', '_config'))
|
11
|
+
config.path_to_project = File.dirname(__FILE__)
|
12
|
+
config.env_variable_with_options = 'OPTIONS'
|
13
|
+
end
|
14
|
+
@original_options = if ENV.key?(SexySettings.configuration.env_variable_with_options)
|
15
|
+
ENV[SexySettings.configuration.env_variable_with_options]
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
ENV[SexySettings.configuration.env_variable_with_options] = 'console_property=console CONSOLE value'
|
20
|
+
@settings ||= settings
|
21
|
+
end
|
22
|
+
|
23
|
+
after :all do
|
24
|
+
@original_options = ENV[SexySettings.configuration.env_variable_with_options]
|
25
|
+
unless @original_options
|
26
|
+
ENV[SexySettings.configuration.env_variable_with_options] = @original_options
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should be singleton object' do
|
31
|
+
expect(SexySettings::Base.respond_to?(:instance)).to be_truthy
|
32
|
+
expect(SexySettings::Base.instance).to be_a(SexySettings::Base)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should have getter for default setting' do
|
36
|
+
expect(@settings).to be_respond_to(:default)
|
37
|
+
expected_default_settings = {
|
38
|
+
'default_property' => 'default DEFAULT value',
|
39
|
+
'overwritten_property' => 'default OVERWRITTEN value',
|
40
|
+
'console_property' => 'default CONSOLE value'
|
41
|
+
}
|
42
|
+
expect(@settings.default).to include(expected_default_settings)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should have getter for custom setting' do
|
46
|
+
expect(@settings).to be_respond_to(:default)
|
47
|
+
expected_custom_settings = {
|
48
|
+
'overwritten_property' => 'overwritten OVERWRITTEN value',
|
49
|
+
'console_property' => 'overwritten CONSOLE value'
|
50
|
+
}
|
51
|
+
expect(@settings.custom).to eq(expected_custom_settings)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should have getter for all setting' do
|
55
|
+
expect(@settings).to be_respond_to(:default)
|
56
|
+
expected_all_settings = {
|
57
|
+
'default_property' => 'default DEFAULT value',
|
58
|
+
'overwritten_property' => 'overwritten OVERWRITTEN value',
|
59
|
+
'console_property' => 'console CONSOLE value'
|
60
|
+
}
|
61
|
+
|
62
|
+
expect(@settings.all).to include(expected_all_settings)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should return specified pretty formatted settings for output' do
|
66
|
+
# rubocop:disable Lint/EmptyInterpolation
|
67
|
+
expected = <<-eos
|
68
|
+
#######################################################
|
69
|
+
# All Settings #
|
70
|
+
#######################################################
|
71
|
+
|
72
|
+
api_key = ********2333
|
73
|
+
api_token = ********23ef
|
74
|
+
console_property = console CONSOLE value
|
75
|
+
default_property = default DEFAULT value
|
76
|
+
email = user@example.com
|
77
|
+
my_secret = ********vqww
|
78
|
+
overwritten_property = overwritten OVERWRITTEN value
|
79
|
+
pass = ********
|
80
|
+
passenger_name = Ivan Petrov
|
81
|
+
password_confirmation = ********pass
|
82
|
+
test1_url = http://********.com:********orld@host:80/wd/hub
|
83
|
+
test2_url = http://********.com@host/wd/hub
|
84
|
+
test3_url = http://********:********orld@host:80/wd/hub
|
85
|
+
test4_url = #{}
|
86
|
+
test5_url = http://host/wd/hub
|
87
|
+
user_pass = ********orld
|
88
|
+
eos
|
89
|
+
# rubocop:enable Lint/EmptyInterpolation
|
90
|
+
expect(@settings.as_formatted_text).to eq(expected)
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'command line' do
|
94
|
+
let(:clone_settings) { settings.class.clone.instance }
|
95
|
+
before do
|
96
|
+
SexySettings.configure.env_variable_with_options = 'SEXY_SETTINGS'
|
97
|
+
ENV['SEXY_SETTINGS'] = 'string=Test, int=1, float=1.09, boolean_true=true,' \
|
98
|
+
' boolean_false=false, symbol=:foo, reference = ${string}'
|
99
|
+
end
|
100
|
+
|
101
|
+
after do
|
102
|
+
SexySettings.configure.env_variable_with_options = 'OPTIONS'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should convert command line string value to String type' do
|
106
|
+
expect(clone_settings.string).to eq('Test')
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should convert command line integer value to Fixnum type' do
|
110
|
+
expect(clone_settings.int).to eq(1)
|
111
|
+
expect(clone_settings.int.class).to eq(Fixnum)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should convert command line float value to Float type' do
|
115
|
+
expect(clone_settings.float).to eq(1.09)
|
116
|
+
expect(clone_settings.float.class).to eq(Float)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should convert command line true value to TrueClass type' do
|
120
|
+
expect(clone_settings.boolean_true).to be_truthy
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should convert command line false value to FalseClass type' do
|
124
|
+
expect(clone_settings.boolean_false).to be_falsy
|
125
|
+
expect(clone_settings.boolean_false.class).to eq(FalseClass)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should convert command line symbol value to Symbol type' do
|
129
|
+
expect(clone_settings.symbol).to eq(:foo)
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'should replace command line reference to correct value' do
|
133
|
+
expect(clone_settings.reference).to eq('Test')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def settings
|
139
|
+
SexySettings::Base.instance
|
140
|
+
end
|
@@ -1,34 +1,46 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
SexySettings::Configuration
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Configuration' do
|
5
|
+
let(:expected_opts) do
|
6
|
+
{
|
7
|
+
path_to_default_settings: 'default.yml',
|
8
|
+
path_to_custom_settings: 'custom.yml',
|
9
|
+
path_to_project: '.',
|
10
|
+
env_variable_with_options: 'SEXY_SETTINGS',
|
11
|
+
cmd_line_option_delimiter: ','
|
12
|
+
}
|
13
|
+
end
|
14
|
+
let(:config) { SexySettings::Configuration.new }
|
15
|
+
|
16
|
+
it 'should have correct default options' do
|
17
|
+
expect(SexySettings::Configuration.constants).to include(:DEFAULT_OPTIONS)
|
18
|
+
expect(SexySettings::Configuration::DEFAULT_OPTIONS).to eq(expected_opts)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should have setters for all options' do
|
22
|
+
expected_opts.keys.each { |key| expect(config).to be_respond_to("#{key}=") }
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should return last value of specified option' do
|
26
|
+
new_value = 'fake'
|
27
|
+
expected_opts.keys.each do |key|
|
28
|
+
config.send("#{key}=", new_value)
|
29
|
+
expect(config.send(key)).to eq(new_value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should return default value of specified option' do
|
34
|
+
expected_opts.keys.each { |key| expect(config.send(key)).to eq(expected_opts[key]) }
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when SEXY_SETTINGS_DELIMITER env variable specified' do
|
38
|
+
before { ENV['SEXY_SETTINGS_DELIMITER'] = '$' }
|
39
|
+
after { ENV['SEXY_SETTINGS_DELIMITER'] = nil }
|
40
|
+
|
41
|
+
it 'should override specified delimiter' do
|
42
|
+
config.cmd_line_option_delimiter = ';'
|
43
|
+
expect(config.cmd_line_option_delimiter).to eq('$')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,31 +1,32 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
config.cmd_line_option_delimiter
|
10
|
-
|
11
|
-
|
12
|
-
SexySettings.
|
13
|
-
SexySettings.
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
config
|
19
|
-
config.
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Core' do
|
5
|
+
it 'should have ability to reset settings' do
|
6
|
+
new_delim = '#@#'
|
7
|
+
old_delim = nil
|
8
|
+
SexySettings.configure do |config|
|
9
|
+
old_delim = config.cmd_line_option_delimiter
|
10
|
+
config.cmd_line_option_delimiter = new_delim
|
11
|
+
end
|
12
|
+
expect(SexySettings.configuration.cmd_line_option_delimiter).to eq(new_delim)
|
13
|
+
SexySettings.reset
|
14
|
+
expect(SexySettings.configuration.cmd_line_option_delimiter).to eq(old_delim)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should return the same configuration object each time' do
|
18
|
+
config = SexySettings.configuration
|
19
|
+
expect(config).to be_a(SexySettings::Configuration)
|
20
|
+
expect(config.object_id).to eq(SexySettings.configuration.object_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should have ability to configure Configuration object with block' do
|
24
|
+
SexySettings.configure do |config|
|
25
|
+
expect(config).to be_a(SexySettings::Configuration)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should have ability to configure Configuration object without block' do
|
30
|
+
expect(SexySettings.configure).to be_a(SexySettings::Configuration)
|
31
|
+
end
|
32
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
SexySettings
|
7
|
-
|
8
|
-
end
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Version' do
|
5
|
+
it 'should contains VERSION constant with correct format' do
|
6
|
+
expect(SexySettings.constants).to include(:VERSION)
|
7
|
+
expect(SexySettings::VERSION).to match(/^\d+\.\d+\.\d+$/)
|
8
|
+
end
|
9
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'sexy_settings'
|