configliere 0.4.13 → 0.4.14
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.
- data/.travis.yml +11 -0
- data/.yardopts +7 -0
- data/Gemfile +18 -15
- data/README.textile +2 -0
- data/Rakefile +35 -54
- data/VERSION +1 -1
- data/configliere.gemspec +21 -14
- data/lib/configliere.rb +2 -0
- data/lib/configliere/commands.rb +1 -1
- data/lib/configliere/config_file.rb +6 -6
- data/lib/configliere/crypter.rb +7 -4
- data/lib/configliere/vayacondios.rb +1 -0
- data/spec/configliere/config_file_spec.rb +41 -40
- data/spec/configliere/crypter_spec.rb +1 -1
- data/spec/configliere/deep_hash_spec.rb +49 -48
- data/spec/spec_helper.rb +10 -7
- metadata +92 -30
- data/.watchr +0 -20
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -1,39 +1,42 @@
|
|
1
|
-
source
|
1
|
+
source 'http://rubygems.org'
|
2
2
|
|
3
3
|
gem 'multi_json', ">= 1.1"
|
4
|
-
gem 'oj', ">= 1.2"
|
5
|
-
gem 'json', :platform => :jruby
|
6
4
|
|
7
5
|
# Only necessary if you want to use Configliere::Prompt
|
8
6
|
gem 'highline', ">= 1.5.2"
|
7
|
+
gem 'jruby-openssl', :platform => :jruby # if RUBY_PLATFORM =~ /java/
|
8
|
+
|
9
|
+
puts [RUBY_PLATFORM, RUBY_ENGINE, RUBY_DESCRIPTION]
|
9
10
|
|
10
11
|
# Only gems that you want listed as development dependencies in the gemspec
|
11
12
|
group :development do
|
12
13
|
gem 'bundler', "~> 1.1"
|
13
14
|
gem 'rake'
|
14
|
-
end
|
15
|
-
|
16
|
-
# Gems you would use if hacking on this gem (rather than with it)
|
17
|
-
group :support do
|
18
|
-
gem 'jeweler', ">= 1.6"
|
19
|
-
gem 'pry'
|
20
15
|
gem 'yard', ">= 0.7"
|
21
|
-
gem 'rspec', "
|
16
|
+
gem 'rspec', ">= 2.8"
|
17
|
+
gem 'jeweler', ">= 1.6"
|
22
18
|
end
|
23
19
|
|
24
|
-
group :
|
25
|
-
gem 'RedCloth', ">= 4.2",
|
26
|
-
gem 'redcarpet', ">= 2.1"
|
20
|
+
group :docs do
|
21
|
+
gem 'RedCloth', ">= 4.2", :require => "redcloth"
|
22
|
+
gem 'redcarpet', ">= 2.1", :platform => [:mri, :rbx]
|
23
|
+
gem 'kramdown', :platform => :jruby
|
27
24
|
end
|
28
25
|
|
29
26
|
# Gems for testing and coverage
|
30
27
|
group :test do
|
31
|
-
gem 'simplecov', ">= 0.5",
|
28
|
+
gem 'simplecov', ">= 0.5", :platform => :ruby_19
|
32
29
|
#
|
30
|
+
gem 'oj', ">= 1.2", :platform => [:mri, :rbx]
|
31
|
+
gem 'json', :platform => :jruby
|
32
|
+
end
|
33
|
+
|
34
|
+
# Gems you would use if hacking on this gem (rather than with it)
|
35
|
+
group :support do
|
36
|
+
gem 'pry'
|
33
37
|
gem 'guard', ">= 1.0"
|
34
38
|
gem 'guard-rspec', ">= 0.6"
|
35
39
|
gem 'guard-yard'
|
36
|
-
#
|
37
40
|
if RUBY_PLATFORM.include?('darwin')
|
38
41
|
gem 'rb-fsevent', ">= 0.9"
|
39
42
|
end
|
data/README.textile
CHANGED
@@ -8,6 +8,8 @@ You've got a script. It's got some settings. Some settings are for this module,
|
|
8
8
|
|
9
9
|
Configliere manages settings from many sources: static constants, simple config files, environment variables, commandline options, straight ruby. You don't have to predefine anything, but you can ask configliere to type-convert, require, document or password-obscure any of its fields. Basically: *Settings go in, the right thing happens*.
|
10
10
|
|
11
|
+
"!https://secure.travis-ci.org/infochimps-labs/configliere.png!":http://travis-ci.org/infochimps-labs/configliere
|
12
|
+
|
11
13
|
h2. Example
|
12
14
|
|
13
15
|
Here's a simple example, using params from a config file and the command line. In the script:
|
data/Rakefile
CHANGED
@@ -1,64 +1,45 @@
|
|
1
1
|
require 'rubygems' unless defined?(Gem)
|
2
|
-
require 'bundler'
|
3
|
-
|
4
|
-
Bundler.setup(:default, :development, :support)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.setup(:default, :development)
|
10
4
|
require 'rake'
|
11
5
|
|
12
|
-
|
13
|
-
require 'jeweler'
|
14
|
-
Jeweler::Tasks.new do |gem|
|
15
|
-
gem.name = "configliere"
|
16
|
-
gem.homepage = "http://infochimps.com/tools"
|
17
|
-
gem.license = "Apache"
|
18
|
-
gem.summary = %Q{Wise, discreet configuration management}
|
19
|
-
gem.email = "coders@infochimps.org"
|
20
|
-
gem.authors = ["infochimps", "mrflip"]
|
21
|
-
gem.executables = []
|
22
|
-
gem.description = %Q{ You've got a script. It's got some settings. Some settings are for this module, some are for that module. Most of them don't change. Except on your laptop, where the paths are different. Or when you're in production mode. Or when you're testing from the command line.
|
23
|
-
|
24
|
-
"" So, Consigliere of mine, I think you should tell your Don what everyone knows. "" -- Don Corleone
|
6
|
+
task :default => :rspec
|
25
7
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
rescue LoadError
|
31
|
-
puts "Jeweler (or a dependency) not available. Install it with: bundle install"
|
8
|
+
require 'rspec/core/rake_task'
|
9
|
+
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
10
|
+
Bundler.setup(:default, :development, :test)
|
11
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
12
|
end
|
33
13
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
spec.pattern = FileList['spec/**/*_spec.rb']
|
39
|
-
end
|
40
|
-
|
41
|
-
# if rcov shits the bed with ruby 1.9, see
|
42
|
-
# https://github.com/relevance/rcov/issues/31
|
43
|
-
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
44
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
45
|
-
spec.rcov = true
|
46
|
-
spec.rcov_opts = %w[ --exclude .rvm --no-comments --text-summary]
|
47
|
-
end
|
48
|
-
rescue LoadError
|
49
|
-
task(:rspec){ abort "Rspec is not available. In order to run spec, you must: bundle install" }
|
50
|
-
task(:rcov ){ abort "Rspec is not available. In order to run coverage, you must: bundle install" }
|
14
|
+
desc "Run RSpec with code coverage"
|
15
|
+
task :cov do
|
16
|
+
ENV['CONFIGLIERE_COV'] = "yep"
|
17
|
+
Rake::Task[:rspec].execute
|
51
18
|
end
|
52
19
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
Bundler.setup(:default, :development, :doc)
|
57
|
-
end
|
58
|
-
rescue LoadError
|
59
|
-
task :yardoc do
|
60
|
-
abort "YARD is not available. In order to run yardoc, you must: bundle install"
|
61
|
-
end
|
20
|
+
require 'yard'
|
21
|
+
YARD::Rake::YardocTask.new do
|
22
|
+
Bundler.setup(:default, :development, :docs)
|
62
23
|
end
|
63
24
|
|
64
|
-
|
25
|
+
require 'jeweler'
|
26
|
+
Jeweler::Tasks.new do |gem|
|
27
|
+
Bundler.setup(:default, :development, :test)
|
28
|
+
gem.name = 'configliere'
|
29
|
+
gem.homepage = 'https://github.com/infochimps-labs/configliere'
|
30
|
+
gem.license = 'Apache 2.0'
|
31
|
+
gem.email = 'coders@infochimps.org'
|
32
|
+
gem.authors = ['Infochimps']
|
33
|
+
|
34
|
+
gem.summary = %Q{Wise, discreet configuration management}
|
35
|
+
gem.description = <<-EOF
|
36
|
+
You\'ve got a script. It\'s got some settings. Some settings are for this module, some are for that module. Most of them don\'t change. Except on your laptop, where the paths are different. Or when you're in production mode. Or when you\'re testing from the command line.
|
37
|
+
|
38
|
+
"" So, Consigliere of mine, I think you should tell your Don what everyone knows. "" -- Don Corleone
|
39
|
+
|
40
|
+
Configliere manage settings from many sources: static constants, simple config files, environment variables, commandline options, straight ruby. You don't have to predefine anything, but you can ask configliere to type-convert, require, document or password-obscure any of its fields. Modules can define config settings independently of each other and the main program.
|
41
|
+
EOF
|
42
|
+
|
43
|
+
gem.executables = []
|
44
|
+
end
|
45
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.14
|
data/configliere.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "configliere"
|
8
|
-
s.version = "0.4.
|
8
|
+
s.version = "0.4.14"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
-
s.authors = ["
|
12
|
-
s.date = "2012-08-
|
13
|
-
s.description = "
|
11
|
+
s.authors = ["Infochimps"]
|
12
|
+
s.date = "2012-08-13"
|
13
|
+
s.description = "You've got a script. It's got some settings. Some settings are for this module, some are for that module. Most of them don't change. Except on your laptop, where the paths are different. Or when you're in production mode. Or when you're testing from the command line.\n\n \"\" So, Consigliere of mine, I think you should tell your Don what everyone knows. \"\" -- Don Corleone\n\nConfigliere manage settings from many sources: static constants, simple config files, environment variables, commandline options, straight ruby. You don't have to predefine anything, but you can ask configliere to type-convert, require, document or password-obscure any of its fields. Modules can define config settings independently of each other and the main program.\n"
|
14
14
|
s.email = "coders@infochimps.org"
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.md",
|
@@ -18,7 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
]
|
19
19
|
s.files = [
|
20
20
|
".rspec",
|
21
|
-
".
|
21
|
+
".travis.yml",
|
22
|
+
".yardopts",
|
22
23
|
"CHANGELOG.textile",
|
23
24
|
"FEATURES.txt",
|
24
25
|
"Gemfile",
|
@@ -72,10 +73,10 @@ Gem::Specification.new do |s|
|
|
72
73
|
"spec/spec.opts",
|
73
74
|
"spec/spec_helper.rb"
|
74
75
|
]
|
75
|
-
s.homepage = "
|
76
|
-
s.licenses = ["Apache"]
|
76
|
+
s.homepage = "https://github.com/infochimps-labs/configliere"
|
77
|
+
s.licenses = ["Apache 2.0"]
|
77
78
|
s.require_paths = ["lib"]
|
78
|
-
s.rubygems_version = "1.8.
|
79
|
+
s.rubygems_version = "1.8.24"
|
79
80
|
s.summary = "Wise, discreet configuration management"
|
80
81
|
|
81
82
|
if s.respond_to? :specification_version then
|
@@ -83,26 +84,32 @@ Gem::Specification.new do |s|
|
|
83
84
|
|
84
85
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
85
86
|
s.add_runtime_dependency(%q<multi_json>, [">= 1.1"])
|
86
|
-
s.add_runtime_dependency(%q<oj>, [">= 1.2"])
|
87
|
-
s.add_runtime_dependency(%q<json>, [">= 0"])
|
88
87
|
s.add_runtime_dependency(%q<highline>, [">= 1.5.2"])
|
88
|
+
s.add_runtime_dependency(%q<jruby-openssl>, [">= 0"])
|
89
89
|
s.add_development_dependency(%q<bundler>, ["~> 1.1"])
|
90
90
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
91
|
+
s.add_development_dependency(%q<yard>, [">= 0.7"])
|
92
|
+
s.add_development_dependency(%q<rspec>, [">= 2.8"])
|
93
|
+
s.add_development_dependency(%q<jeweler>, [">= 1.6"])
|
91
94
|
else
|
92
95
|
s.add_dependency(%q<multi_json>, [">= 1.1"])
|
93
|
-
s.add_dependency(%q<oj>, [">= 1.2"])
|
94
|
-
s.add_dependency(%q<json>, [">= 0"])
|
95
96
|
s.add_dependency(%q<highline>, [">= 1.5.2"])
|
97
|
+
s.add_dependency(%q<jruby-openssl>, [">= 0"])
|
96
98
|
s.add_dependency(%q<bundler>, ["~> 1.1"])
|
97
99
|
s.add_dependency(%q<rake>, [">= 0"])
|
100
|
+
s.add_dependency(%q<yard>, [">= 0.7"])
|
101
|
+
s.add_dependency(%q<rspec>, [">= 2.8"])
|
102
|
+
s.add_dependency(%q<jeweler>, [">= 1.6"])
|
98
103
|
end
|
99
104
|
else
|
100
105
|
s.add_dependency(%q<multi_json>, [">= 1.1"])
|
101
|
-
s.add_dependency(%q<oj>, [">= 1.2"])
|
102
|
-
s.add_dependency(%q<json>, [">= 0"])
|
103
106
|
s.add_dependency(%q<highline>, [">= 1.5.2"])
|
107
|
+
s.add_dependency(%q<jruby-openssl>, [">= 0"])
|
104
108
|
s.add_dependency(%q<bundler>, ["~> 1.1"])
|
105
109
|
s.add_dependency(%q<rake>, [">= 0"])
|
110
|
+
s.add_dependency(%q<yard>, [">= 0.7"])
|
111
|
+
s.add_dependency(%q<rspec>, [">= 2.8"])
|
112
|
+
s.add_dependency(%q<jeweler>, [">= 1.6"])
|
106
113
|
end
|
107
114
|
end
|
108
115
|
|
data/lib/configliere.rb
CHANGED
@@ -11,6 +11,8 @@ require 'configliere/config_file' # read / save! files
|
|
11
11
|
# running the specs requires rspec and spork
|
12
12
|
|
13
13
|
module Configliere
|
14
|
+
RUBY_ENGINE = 'ruby' if not defined?(::RUBY_ENGINE)
|
15
|
+
|
14
16
|
ALL_MIXINS = [:define, :config_file, :commandline, :encrypted, :env_var, :config_block, :commands, :prompt]
|
15
17
|
def self.use *mixins
|
16
18
|
mixins = ALL_MIXINS if mixins.include?(:all) || mixins.empty?
|
data/lib/configliere/commands.rb
CHANGED
@@ -39,7 +39,7 @@ module Configliere
|
|
39
39
|
commands.each do |cmd, cmd_info|
|
40
40
|
cmd_info[:config].resolve!
|
41
41
|
end
|
42
|
-
if command_name
|
42
|
+
if command_name && commands[command_name]
|
43
43
|
sub_config = commands[command_name][:config]
|
44
44
|
adoptable = sub_config.send(:definitions).keys
|
45
45
|
merge!(sub_config.select{|k,v| adoptable.include?(k) } )
|
@@ -79,29 +79,29 @@ module Configliere
|
|
79
79
|
filename = expand_filename(filename)
|
80
80
|
hsh = self.export.to_hash
|
81
81
|
FileUtils.mkdir_p(File.dirname(filename))
|
82
|
-
File.open(filename, 'w'){|
|
82
|
+
File.open(filename, 'w'){|file| file << YAML.dump(hsh) }
|
83
83
|
end
|
84
84
|
|
85
85
|
def determine_conf_location(level, scope)
|
86
86
|
lookup_conf_dir(level, scope).join("#{scope}.yaml").to_s
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
def default_conf_dir
|
90
90
|
lookup_conf_dir(:user, 'configliere')
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
def lookup_conf_dir(level, scope)
|
94
94
|
Configliere::DEFAULT_CONFIG_LOCATION[level].call(scope)
|
95
95
|
end
|
96
96
|
|
97
97
|
def load_configuration_in_order!(scope = 'configliere')
|
98
|
-
[ :machine, :user, :app ].each do |level|
|
98
|
+
[ :machine, :user, :app ].each do |level|
|
99
99
|
conf = determine_conf_location(level, scope)
|
100
|
-
read(conf) if Pathname(conf).exist?
|
100
|
+
read(conf) if Pathname(conf).exist?
|
101
101
|
end
|
102
102
|
resolve!
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
protected
|
106
106
|
|
107
107
|
def filetype filename
|
data/lib/configliere/crypter.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
require 'openssl' # for encryption
|
2
|
-
require 'digest/sha2' # for encryption
|
3
|
-
require "base64" # base64-encode the binary encrypted string
|
4
1
|
module Configliere
|
2
|
+
# for encryption
|
3
|
+
require 'openssl'
|
4
|
+
require 'digest/sha2'
|
5
|
+
# base64-encode the binary encrypted string
|
6
|
+
require "base64"
|
7
|
+
|
5
8
|
#
|
6
9
|
# Encrypt and decrypt values in configliere stores
|
7
10
|
#
|
@@ -56,7 +59,7 @@ module Configliere
|
|
56
59
|
|
57
60
|
# prepend the initialization vector to the encoded message
|
58
61
|
def self.combine_iv_and_ciphertext iv, message
|
59
|
-
iv + message
|
62
|
+
iv.force_encoding("BINARY") + message.force_encoding("BINARY")
|
60
63
|
end
|
61
64
|
# pull the initialization vector from the front of the encoded message
|
62
65
|
def self.separate_iv_and_ciphertext cipher, iv_and_ciphertext
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Configliere::ConfigFile do
|
4
4
|
let(:default_params) { { my_param: 'default_val', also_a_param: true } }
|
5
|
-
|
5
|
+
|
6
6
|
subject{ Configliere::Param.new default_params }
|
7
7
|
|
8
8
|
it 'is included by default' do
|
@@ -13,7 +13,7 @@ describe Configliere::ConfigFile do
|
|
13
13
|
let(:file_params) { { my_param: 'val_from_file' } }
|
14
14
|
let(:file_string) { file_params.to_yaml }
|
15
15
|
let(:file_path) { '/absolute/path.yaml' }
|
16
|
-
|
16
|
+
|
17
17
|
before{ File.stub(:open).and_return(file_string) }
|
18
18
|
|
19
19
|
it 'returns the config object for chaining' do
|
@@ -22,31 +22,31 @@ describe Configliere::ConfigFile do
|
|
22
22
|
|
23
23
|
context 'a yaml file' do
|
24
24
|
let(:file_path) { '/absolute/path.yaml' }
|
25
|
-
|
25
|
+
|
26
26
|
it 'reads successfully' do
|
27
27
|
subject.should_receive(:read_yaml).with(file_string, {})
|
28
28
|
subject.read file_path
|
29
|
-
end
|
30
|
-
|
29
|
+
end
|
30
|
+
|
31
31
|
it 'merges the data' do
|
32
32
|
subject.read(file_path).should == default_params.merge(file_params)
|
33
|
-
end
|
33
|
+
end
|
34
34
|
end
|
35
35
|
|
36
36
|
context 'a json file' do
|
37
37
|
let(:file_path) { '/absolute/path.json' }
|
38
|
-
let(:file_string) {
|
39
|
-
|
38
|
+
let(:file_string) { '{"my_param":"val_from_file"}' }
|
39
|
+
|
40
40
|
it 'reads successfully' do
|
41
41
|
subject.should_receive(:read_json).with(file_string, {})
|
42
42
|
subject.read file_path
|
43
|
-
end
|
43
|
+
end
|
44
44
|
|
45
45
|
it 'merges the data' do
|
46
|
-
subject.read(file_path).should == default_params.merge(file_params)
|
46
|
+
subject.read(file_path).should == default_params.merge(file_params)
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
context 'given a symbol' do
|
51
51
|
let(:file_path) { :my_settings }
|
52
52
|
|
@@ -55,10 +55,10 @@ describe Configliere::ConfigFile do
|
|
55
55
|
defined?(Configliere::DEFAULT_CONFIG_FILE).should_not be_true
|
56
56
|
end
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
context 'given a nonexistent file' do
|
60
|
-
let(:file_path) { 'nonexistent.conf' }
|
61
|
-
|
60
|
+
let(:file_path) { 'nonexistent.conf' }
|
61
|
+
|
62
62
|
it 'warns but does not fail if the file is missing' do
|
63
63
|
File.stub(:open).and_raise(Errno::ENOENT)
|
64
64
|
subject.should_receive(:warn).with("Loading empty configliere settings file #{subject.default_conf_dir}/#{file_path}")
|
@@ -68,22 +68,22 @@ describe Configliere::ConfigFile do
|
|
68
68
|
|
69
69
|
context 'given an absolute path' do
|
70
70
|
let(:file_path) { '/absolute/path.yaml' }
|
71
|
-
|
71
|
+
|
72
72
|
it 'uses it directly' do
|
73
73
|
File.should_receive(:open).with(file_path).and_return(file_string)
|
74
74
|
subject.read file_path
|
75
|
-
end
|
75
|
+
end
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
context 'given a simple filename' do
|
79
79
|
let(:file_path) { 'simple_path.yaml' }
|
80
|
-
|
80
|
+
|
81
81
|
it 'references it to the default config dir' do
|
82
82
|
File.should_receive(:open).with(File.join(subject.default_conf_dir, file_path)).and_return(file_string)
|
83
83
|
subject.read file_path
|
84
|
-
end
|
84
|
+
end
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
context 'with options' do
|
88
88
|
let(:file_params) { { development: { reload: true }, production: { reload: false } } }
|
89
89
|
|
@@ -92,7 +92,7 @@ describe Configliere::ConfigFile do
|
|
92
92
|
context ':env key' do
|
93
93
|
context 'valid :env' do
|
94
94
|
let(:opts) { { env: :development } }
|
95
|
-
|
95
|
+
|
96
96
|
it 'slices out a subhash given by :env' do
|
97
97
|
subject.read(file_path, opts)
|
98
98
|
subject.should == default_params.merge(reload: true)
|
@@ -101,23 +101,23 @@ describe Configliere::ConfigFile do
|
|
101
101
|
|
102
102
|
context 'invalid :env' do
|
103
103
|
let(:opts) { { env: :not_there } }
|
104
|
-
|
104
|
+
|
105
105
|
it 'has no effect if the key given by :env option is absent' do
|
106
106
|
subject.read(file_path, opts)
|
107
107
|
subject.should == default_params.merge(reload: 'whatever')
|
108
|
-
end
|
109
|
-
end
|
108
|
+
end
|
109
|
+
end
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
context 'no :env key' do
|
113
113
|
let(:opts) { Hash.new }
|
114
|
-
|
114
|
+
|
115
115
|
it 'does no slicing without the :env option' do
|
116
116
|
subject.read(file_path, opts)
|
117
|
-
subject.should == default_params.merge(reload: 'whatever').merge(file_params)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
117
|
+
subject.should == default_params.merge(reload: 'whatever').merge(file_params)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
121
|
end
|
122
122
|
|
123
123
|
context '#save!' do
|
@@ -125,14 +125,15 @@ describe Configliere::ConfigFile do
|
|
125
125
|
|
126
126
|
context 'given an absolute pathname' do
|
127
127
|
let(:file_path) { '/absolute/path.yaml' }
|
128
|
-
|
128
|
+
|
129
129
|
it 'saves the filename as given' do
|
130
130
|
File.should_receive(:open).with(file_path, 'w').and_yield(fake_file)
|
131
|
+
FileUtils.stub(:mkdir_p)
|
131
132
|
fake_file.should_receive(:<<).with(default_params.to_yaml)
|
132
133
|
subject.save! file_path
|
133
134
|
end
|
134
|
-
end
|
135
|
-
|
135
|
+
end
|
136
|
+
|
136
137
|
context 'given a simple pathname' do
|
137
138
|
let(:file_path) { 'simple_path.yaml' }
|
138
139
|
|
@@ -141,7 +142,7 @@ describe Configliere::ConfigFile do
|
|
141
142
|
fake_file.should_receive(:<<).with(default_params.to_yaml)
|
142
143
|
subject.save! file_path
|
143
144
|
end
|
144
|
-
|
145
|
+
|
145
146
|
it 'ensures the directory exists' do
|
146
147
|
File.should_receive(:open).with(File.join(subject.default_conf_dir, file_path), 'w').and_yield(fake_file)
|
147
148
|
FileUtils.should_receive(:mkdir_p).with(subject.default_conf_dir.to_s)
|
@@ -149,27 +150,27 @@ describe Configliere::ConfigFile do
|
|
149
150
|
end
|
150
151
|
end
|
151
152
|
end
|
152
|
-
|
153
|
+
|
153
154
|
context '#resolve!' do
|
154
155
|
around do |example|
|
155
156
|
Configliere::ParamParent.class_eval{ def resolve!() parent_method ; end }
|
156
157
|
example.run
|
157
158
|
Configliere::ParamParent.class_eval{ def resolve!() self ; end }
|
158
159
|
end
|
159
|
-
|
160
|
+
|
160
161
|
it 'calls super and returns self' do
|
161
162
|
subject.should_receive(:parent_method)
|
162
163
|
subject.resolve!.should equal(subject)
|
163
164
|
end
|
164
165
|
end
|
165
|
-
|
166
|
+
|
166
167
|
describe '#validate!' do
|
167
168
|
around do |example|
|
168
169
|
Configliere::ParamParent.class_eval{ def validate!() parent_method ; end }
|
169
170
|
example.run
|
170
171
|
Configliere::ParamParent.class_eval{ def validate!() self ; end }
|
171
172
|
end
|
172
|
-
|
173
|
+
|
173
174
|
it 'calls super and returns self' do
|
174
175
|
subject.should_receive(:parent_method)
|
175
176
|
subject.validate!.should equal(subject)
|
@@ -178,9 +179,9 @@ describe Configliere::ConfigFile do
|
|
178
179
|
|
179
180
|
context '#load_configuration_in_order!' do
|
180
181
|
let(:scope) { 'test' }
|
181
|
-
|
182
|
+
|
182
183
|
before{ subject.stub(:determine_conf_location).and_return('conf_dir') }
|
183
|
-
|
184
|
+
|
184
185
|
it 'resolves configuration in order' do
|
185
186
|
subject.should_receive(:determine_conf_location).with(:machine, scope).ordered
|
186
187
|
subject.should_receive(:determine_conf_location).with(:user, scope).ordered
|
@@ -3,7 +3,7 @@ require 'configliere/crypter'
|
|
3
3
|
include Configliere
|
4
4
|
|
5
5
|
describe "Crypter" do
|
6
|
-
ENCRYPTED_FOO_VAL = "cc+Bp5jMUBHFCvPNZIfleeatB4IGaaXjVINl12HOpcs=\n"
|
6
|
+
ENCRYPTED_FOO_VAL = "cc+Bp5jMUBHFCvPNZIfleeatB4IGaaXjVINl12HOpcs=\n".force_encoding("BINARY")
|
7
7
|
FOO_VAL_IV = Base64.decode64(ENCRYPTED_FOO_VAL)[0..15]
|
8
8
|
it "encrypts" do
|
9
9
|
# Force the same initialization vector as used to prepare the test value
|
@@ -83,7 +83,6 @@ describe DeepHash do
|
|
83
83
|
end
|
84
84
|
|
85
85
|
it "only accepts #to_sym'bolizable things as keys" do
|
86
|
-
lambda{ @deep_hash[1] = 'hi' }.should raise_error(NoMethodError, /undefined method `to_sym'/)
|
87
86
|
lambda{ @deep_hash[{ :a => :b }] = 'hi' }.should raise_error(NoMethodError, /undefined method `to_sym'/)
|
88
87
|
lambda{ @deep_hash[Object.new] = 'hi' }.should raise_error(NoMethodError, /undefined method `to_sym'/)
|
89
88
|
lambda{ @deep_hash[:a] = 'hi' }.should_not raise_error
|
@@ -91,23 +90,32 @@ describe DeepHash do
|
|
91
90
|
end
|
92
91
|
|
93
92
|
describe '#[]' do
|
93
|
+
let(:orig_hash){ { :hat => :cat, :basket => :lotion, :moon => { :man => :smiling, :cheese => {:type => :tilsit} } } }
|
94
|
+
let(:subject ){ Configliere::Param.new({ :hat => :cat, :basket => :lotion, :moon => { :man => :smiling, :cheese => {:type => :tilsit} } }) }
|
95
|
+
|
94
96
|
it 'deep-gets dotted vals' do
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
97
|
+
subject['moon.man'].should == :smiling
|
98
|
+
subject['moon.cheese.type'].should == :tilsit
|
99
|
+
subject['moon.cheese.smell'].should be_nil
|
100
|
+
subject['moon.non.existent.interim.values'].should be_nil
|
101
|
+
subject['moon.non'].should be_nil
|
102
|
+
subject.should == orig_hash # shouldn't change from reading (specifically, shouldn't autovivify)
|
103
|
+
end
|
104
|
+
it 'indexing through a non-hash will raise an error' do
|
105
|
+
begin ; p subject['hat'] ; rescue StandardError => err ; p [err, err.class] ; end
|
106
|
+
begin ; p subject['hat.dog'] ; rescue StandardError => err ; p [err, err.class] ; end
|
107
|
+
begin ; p subject['hat']['dog'] ; rescue StandardError => err ; p [err, err.class] ; end
|
108
|
+
begin ; p :happy_sym['dog'] ; rescue StandardError => err ; p [err, err.class] ; end
|
109
|
+
end
|
110
|
+
it 'indexing through a non-hash will raise an error' do
|
111
|
+
err_klass = (RUBY_VERSION >= "1.9.0") ? TypeError : NoMethodError
|
112
|
+
expect{ subject['hat.dog'] }.to raise_error(err_klass, /Symbol/)
|
113
|
+
subject.should == orig_hash # shouldn't change from reading (specifically, shouldn't autovivify)
|
105
114
|
end
|
106
115
|
|
107
116
|
it "only accepts #to_sym'bolizable things as keys" do
|
108
|
-
lambda{
|
109
|
-
lambda{
|
110
|
-
lambda{ @deep_hash[Object.new] }.should raise_error(NoMethodError, /undefined method `to_sym'/)
|
117
|
+
lambda{ subject[{ :a => :b }] }.should raise_error(NoMethodError, /undefined method `to_sym'/)
|
118
|
+
lambda{ subject[Object.new] }.should raise_error(NoMethodError, /undefined method `to_sym'/)
|
111
119
|
end
|
112
120
|
end
|
113
121
|
|
@@ -160,59 +168,55 @@ describe DeepHash do
|
|
160
168
|
end
|
161
169
|
|
162
170
|
describe '#slice' do
|
163
|
-
|
164
|
-
@deep_hash = DeepHash.new({ :a => 'x', :b => 'y', :c => 10 })
|
165
|
-
end
|
171
|
+
let(:subject ){ Configliere::Param.new({ :a => 'x', :b => 'y', :c => 10 }) }
|
166
172
|
|
167
173
|
it 'returns a new hash with only the given keys' do
|
168
|
-
|
169
|
-
|
174
|
+
subject.slice(:a, :b).should == { :a => 'x', :b => 'y' }
|
175
|
+
subject.should == { :a => 'x', :b => 'y', :c => 10 }
|
170
176
|
end
|
171
177
|
|
172
178
|
it 'with bang replaces the hash with only the given keys' do
|
173
|
-
|
174
|
-
|
179
|
+
subject.slice!(:a, :b).should == { :c => 10 }
|
180
|
+
subject.should == { :a => 'x', :b => 'y' }
|
175
181
|
end
|
176
182
|
|
177
183
|
it 'ignores an array key' do
|
178
|
-
|
179
|
-
|
184
|
+
subject.slice([:a, :b], :c).should == { :c => 10 }
|
185
|
+
subject.should == { :a => 'x', :b => 'y', :c => 10 }
|
180
186
|
end
|
181
187
|
|
182
188
|
it 'with bang ignores an array key' do
|
183
|
-
|
184
|
-
|
189
|
+
subject.slice!([:a, :b], :c).should == { :a => 'x', :b => 'y' }
|
190
|
+
subject.should == { :c => 10 }
|
185
191
|
end
|
186
192
|
|
187
193
|
it 'uses splatted keys individually' do
|
188
|
-
|
189
|
-
|
194
|
+
subject.slice(*[:a, :b]).should == { :a => 'x', :b => 'y' }
|
195
|
+
subject.should == { :a => 'x', :b => 'y', :c => 10 }
|
190
196
|
end
|
191
197
|
|
192
198
|
it 'with bank uses splatted keys individually' do
|
193
|
-
|
194
|
-
|
199
|
+
subject.slice!(*[:a, :b]).should == { :c => 10 }
|
200
|
+
subject.should == { :a => 'x', :b => 'y' }
|
195
201
|
end
|
196
202
|
end
|
197
203
|
|
198
204
|
describe '#extract' do
|
199
|
-
|
200
|
-
@deep_hash = DeepHash.new({ :a => 'x', :b => 'y', :c => 10 })
|
201
|
-
end
|
205
|
+
let(:subject ){ Configliere::Param.new({ :a => 'x', :b => 'y', :c => 10 }) }
|
202
206
|
|
203
207
|
it 'replaces the hash with only the given keys' do
|
204
|
-
|
205
|
-
|
208
|
+
subject.extract!(:a, :b).should == { :a => 'x', :b => 'y' }
|
209
|
+
subject.should == { :c => 10 }
|
206
210
|
end
|
207
211
|
|
208
212
|
it 'leaves the hash empty if all keys are gone' do
|
209
|
-
|
210
|
-
|
213
|
+
subject.extract!(:a, :b, :c).should == { :a => 'x', :b => 'y', :c => 10 }
|
214
|
+
subject.should == {}
|
211
215
|
end
|
212
216
|
|
213
217
|
it 'gets values for all given keys even if missing' do
|
214
|
-
|
215
|
-
|
218
|
+
subject.extract!(:bob, :c).should == { :bob => nil, :c => 10 }
|
219
|
+
subject.should == { :a => 'x', :b => 'y' }
|
216
220
|
end
|
217
221
|
|
218
222
|
it 'is OK when empty' do
|
@@ -220,7 +224,7 @@ describe DeepHash do
|
|
220
224
|
end
|
221
225
|
|
222
226
|
it 'returns an instance of the same class' do
|
223
|
-
|
227
|
+
subject.slice(:a).should be_a(DeepHash)
|
224
228
|
end
|
225
229
|
end
|
226
230
|
|
@@ -241,26 +245,23 @@ describe DeepHash do
|
|
241
245
|
end
|
242
246
|
|
243
247
|
describe "#fetch" do
|
244
|
-
|
245
|
-
@deep_hash = DeepHash.new(:no => "in between")
|
246
|
-
end
|
248
|
+
let(:subject ){ Configliere::Param.new({ :no => :in_between }) }
|
247
249
|
|
248
250
|
it 'converts key before fetching' do
|
249
|
-
|
251
|
+
subject.fetch("no").should == :in_between
|
250
252
|
end
|
251
253
|
|
252
254
|
it 'returns alternative value if key lookup fails' do
|
253
|
-
|
255
|
+
subject.fetch("flying", "screwdriver").should == "screwdriver"
|
254
256
|
end
|
255
257
|
end
|
256
258
|
|
257
259
|
describe "#values_at" do
|
258
|
-
|
259
|
-
|
260
|
-
end
|
260
|
+
|
261
|
+
let(:subject ){ Configliere::Param.new({ :no => :in_between, "str_key" => "strk_val", :sym_key => "symk_val"}) }
|
261
262
|
|
262
263
|
it 'is indifferent to whether keys are strings or symbols' do
|
263
|
-
|
264
|
+
subject.values_at("sym_key", :str_key, :no).should == ["symk_val", "strk_val", :in_between]
|
264
265
|
end
|
265
266
|
end
|
266
267
|
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
require '
|
3
|
-
require 'rspec'
|
1
|
+
require 'bundler/setup' ; Bundler.require(:default, :development, :test)
|
2
|
+
require 'rspec/autorun'
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
if ENV['CONFIGLIERE_COV']
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
11
|
+
end
|
7
12
|
|
8
13
|
require 'configliere'
|
9
|
-
require 'json'
|
10
|
-
require 'yaml'
|
metadata
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: configliere
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.14
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
9
|
-
- mrflip
|
8
|
+
- Infochimps
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-13 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: multi_json
|
17
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
18
17
|
none: false
|
19
18
|
requirements:
|
20
19
|
- - ! '>='
|
@@ -22,21 +21,31 @@ dependencies:
|
|
22
21
|
version: '1.1'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.1'
|
26
30
|
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
28
|
-
requirement:
|
31
|
+
name: highline
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
29
33
|
none: false
|
30
34
|
requirements:
|
31
35
|
- - ! '>='
|
32
36
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
37
|
+
version: 1.5.2
|
34
38
|
type: :runtime
|
35
39
|
prerelease: false
|
36
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.5.2
|
37
46
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
39
|
-
requirement:
|
47
|
+
name: jruby-openssl
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
40
49
|
none: false
|
41
50
|
requirements:
|
42
51
|
- - ! '>='
|
@@ -44,21 +53,15 @@ dependencies:
|
|
44
53
|
version: '0'
|
45
54
|
type: :runtime
|
46
55
|
prerelease: false
|
47
|
-
version_requirements:
|
48
|
-
- !ruby/object:Gem::Dependency
|
49
|
-
name: highline
|
50
|
-
requirement: &2165097440 !ruby/object:Gem::Requirement
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
57
|
none: false
|
52
58
|
requirements:
|
53
59
|
- - ! '>='
|
54
60
|
- !ruby/object:Gem::Version
|
55
|
-
version:
|
56
|
-
type: :runtime
|
57
|
-
prerelease: false
|
58
|
-
version_requirements: *2165097440
|
61
|
+
version: '0'
|
59
62
|
- !ruby/object:Gem::Dependency
|
60
63
|
name: bundler
|
61
|
-
requirement:
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
62
65
|
none: false
|
63
66
|
requirements:
|
64
67
|
- - ~>
|
@@ -66,10 +69,15 @@ dependencies:
|
|
66
69
|
version: '1.1'
|
67
70
|
type: :development
|
68
71
|
prerelease: false
|
69
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.1'
|
70
78
|
- !ruby/object:Gem::Dependency
|
71
79
|
name: rake
|
72
|
-
requirement:
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
73
81
|
none: false
|
74
82
|
requirements:
|
75
83
|
- - ! '>='
|
@@ -77,8 +85,61 @@ dependencies:
|
|
77
85
|
version: '0'
|
78
86
|
type: :development
|
79
87
|
prerelease: false
|
80
|
-
version_requirements:
|
81
|
-
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: yard
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0.7'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0.7'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2.8'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '2.8'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: jeweler
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '1.6'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '1.6'
|
142
|
+
description: ! "You've got a script. It's got some settings. Some settings are for
|
82
143
|
this module, some are for that module. Most of them don't change. Except on your
|
83
144
|
laptop, where the paths are different. Or when you're in production mode. Or when
|
84
145
|
you're testing from the command line.\n\n \"\" So, Consigliere of mine, I think
|
@@ -96,7 +157,8 @@ extra_rdoc_files:
|
|
96
157
|
- README.textile
|
97
158
|
files:
|
98
159
|
- .rspec
|
99
|
-
- .
|
160
|
+
- .travis.yml
|
161
|
+
- .yardopts
|
100
162
|
- CHANGELOG.textile
|
101
163
|
- FEATURES.txt
|
102
164
|
- Gemfile
|
@@ -149,9 +211,9 @@ files:
|
|
149
211
|
- spec/configliere_spec.rb
|
150
212
|
- spec/spec.opts
|
151
213
|
- spec/spec_helper.rb
|
152
|
-
homepage:
|
214
|
+
homepage: https://github.com/infochimps-labs/configliere
|
153
215
|
licenses:
|
154
|
-
- Apache
|
216
|
+
- Apache 2.0
|
155
217
|
post_install_message:
|
156
218
|
rdoc_options: []
|
157
219
|
require_paths:
|
@@ -164,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
164
226
|
version: '0'
|
165
227
|
segments:
|
166
228
|
- 0
|
167
|
-
hash:
|
229
|
+
hash: 1408788613005433508
|
168
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
169
231
|
none: false
|
170
232
|
requirements:
|
@@ -173,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
235
|
version: '0'
|
174
236
|
requirements: []
|
175
237
|
rubyforge_project:
|
176
|
-
rubygems_version: 1.8.
|
238
|
+
rubygems_version: 1.8.24
|
177
239
|
signing_key:
|
178
240
|
specification_version: 3
|
179
241
|
summary: Wise, discreet configuration management
|
data/.watchr
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# -*- ruby -*-
|
2
|
-
|
3
|
-
def run_spec(file)
|
4
|
-
unless File.exist?(file)
|
5
|
-
puts "#{file} does not exist"
|
6
|
-
return
|
7
|
-
end
|
8
|
-
|
9
|
-
puts "Running #{file}"
|
10
|
-
system "bundle exec rspec #{file}"
|
11
|
-
puts
|
12
|
-
end
|
13
|
-
|
14
|
-
watch("spec/.*/*_spec\.rb") do |match|
|
15
|
-
run_spec match[0]
|
16
|
-
end
|
17
|
-
|
18
|
-
watch("lib/(.*)\.rb") do |match|
|
19
|
-
run_spec %{spec/#{match[1]}_spec.rb}
|
20
|
-
end
|