sexy_settings 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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'
|