runger_config 4.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/bin/release +27 -0
  4. data/lib/generators/runger/app_config/app_config_generator.rb +6 -10
  5. data/lib/generators/runger/config/config_generator.rb +44 -41
  6. data/lib/generators/runger/install/install_generator.rb +35 -37
  7. data/lib/runger/auto_cast.rb +3 -3
  8. data/lib/runger/config.rb +114 -94
  9. data/lib/runger/dynamic_config.rb +21 -23
  10. data/lib/runger/ejson_parser.rb +24 -24
  11. data/lib/runger/env.rb +50 -52
  12. data/lib/runger/ext/deep_dup.rb +33 -36
  13. data/lib/runger/ext/deep_freeze.rb +28 -32
  14. data/lib/runger/ext/flatten_names.rb +23 -27
  15. data/lib/runger/ext/hash.rb +26 -29
  16. data/lib/runger/ext/string_constantize.rb +12 -15
  17. data/lib/runger/loaders/base.rb +11 -15
  18. data/lib/runger/loaders/doppler.rb +38 -42
  19. data/lib/runger/loaders/ejson.rb +65 -63
  20. data/lib/runger/loaders/env.rb +6 -10
  21. data/lib/runger/loaders/yaml.rb +69 -66
  22. data/lib/runger/loaders.rb +69 -71
  23. data/lib/runger/option_parser_builder.rb +16 -18
  24. data/lib/runger/optparse_config.rb +11 -10
  25. data/lib/runger/rails/autoload.rb +24 -26
  26. data/lib/runger/rails/config.rb +13 -17
  27. data/lib/runger/rails/loaders/credentials.rb +53 -57
  28. data/lib/runger/rails/loaders/secrets.rb +21 -25
  29. data/lib/runger/rails/loaders/yaml.rb +1 -6
  30. data/lib/runger/rails/loaders.rb +3 -3
  31. data/lib/runger/rails/settings.rb +49 -49
  32. data/lib/runger/rails.rb +9 -11
  33. data/lib/runger/railtie.rb +3 -2
  34. data/lib/runger/rbs.rb +29 -29
  35. data/lib/runger/settings.rb +82 -84
  36. data/lib/runger/testing/helpers.rb +26 -28
  37. data/lib/runger/testing.rb +2 -2
  38. data/lib/runger/tracing.rb +143 -136
  39. data/lib/runger/type_casting.rb +16 -11
  40. data/lib/runger/utils/which.rb +10 -12
  41. data/lib/runger/version.rb +1 -1
  42. data/lib/runger.rb +1 -1
  43. data/lib/runger_config.rb +34 -27
  44. metadata +20 -19
@@ -1,188 +1,195 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Runger
4
- # Provides method to trace values association
5
- module Tracing
6
- using Runger::Ext::DeepDup
7
-
8
- using(Module.new do
9
- refine Thread::Backtrace::Location do
10
- def path_lineno = "#{path}:#{lineno}"
11
- end
12
- end)
3
+ # Provides method to trace values association
4
+ module Runger::Tracing
5
+ using Runger::Ext::DeepDup
13
6
 
14
- class Trace
15
- UNDEF = Object.new
7
+ using(Module.new do
8
+ refine Thread::Backtrace::Location do
9
+ def path_lineno = "#{path}:#{lineno}"
10
+ end
11
+ end)
16
12
 
17
- attr_reader :type, :value, :source
13
+ class Trace
14
+ UNDEF = Object.new
18
15
 
19
- def initialize(type = :trace, value = UNDEF, **source)
20
- @type = type
21
- @source = source
22
- @value = (value == UNDEF) ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
23
- end
16
+ attr_reader :type, :value, :source
24
17
 
25
- def dig(...)
26
- value.dig(...)
27
- end
18
+ def initialize(type = :trace, value = UNDEF, **source)
19
+ @type = type
20
+ @source = source
21
+ @value = (value == UNDEF) ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
22
+ end
23
+
24
+ def dig(...)
25
+ value.dig(...)
26
+ end
28
27
 
29
- def record_value(val, *path, **)
30
- key = path.pop
31
- trace = if val.is_a?(Hash)
32
- Trace.new.tap { _1.merge_values(val, **) }
28
+ def record_value(val, *path, **options)
29
+ key = path.pop
30
+ trace =
31
+ if val.is_a?(Hash)
32
+ Trace.new.tap { _1.merge_values(val, **options) }
33
33
  else
34
- Trace.new(:value, val, **)
34
+ Trace.new(:value, val, **options)
35
35
  end
36
36
 
37
- target_trace = path.empty? ? self : value.dig(*path)
38
- target_trace.record_key(key.to_s, trace)
37
+ target_trace = path.empty? ? self : value.dig(*path)
38
+ target_trace.record_key(key.to_s, trace)
39
39
 
40
- val
41
- end
40
+ val
41
+ end
42
42
 
43
- def merge_values(hash, **)
44
- return hash unless hash
43
+ def merge_values(hash, **options)
44
+ return hash unless hash
45
45
 
46
- hash.each do |key, val|
47
- if val.is_a?(Hash)
48
- value[key.to_s].merge_values(val, **)
49
- else
50
- value[key.to_s] = Trace.new(:value, val, **)
51
- end
46
+ hash.each do |key, val|
47
+ if val.is_a?(Hash)
48
+ value[key.to_s].merge_values(val, **options)
49
+ else
50
+ value[key.to_s] = Trace.new(:value, val, **options)
52
51
  end
53
-
54
- hash
55
52
  end
56
53
 
57
- def record_key(key, key_trace)
58
- @value = Hash.new { |h, k| h[k] = Trace.new(:trace) } unless value.is_a?(::Hash)
54
+ hash
55
+ end
59
56
 
60
- value[key] = key_trace
61
- end
57
+ def record_key(key, key_trace)
58
+ @value = Hash.new { |h, k| h[k] = Trace.new(:trace) } unless value.is_a?(::Hash)
62
59
 
63
- def merge!(another_trace)
64
- raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
65
- raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
60
+ value[key] = key_trace
61
+ end
66
62
 
67
- another_trace.value.each do |key, sub_trace|
68
- if sub_trace.trace?
69
- value[key].merge! sub_trace
70
- else
71
- value[key] = sub_trace
72
- end
73
- end
63
+ def merge!(another_trace)
64
+ unless trace?
65
+ raise(ArgumentError,
66
+ "You can only merge into a :trace type, and this is :#{type}")
67
+ end
68
+ unless another_trace.trace?
69
+ raise(ArgumentError,
70
+ "You can only merge a :trace type, but trying :#{type}")
74
71
  end
75
72
 
76
- def keep_if(...)
77
- raise ArgumentError, "You can only filter :trace type, and this is :#{type}" unless trace?
78
- value.keep_if(...)
73
+ another_trace.value.each do |key, sub_trace|
74
+ if sub_trace.trace?
75
+ value[key].merge!(sub_trace)
76
+ else
77
+ value[key] = sub_trace
78
+ end
79
79
  end
80
+ end
80
81
 
81
- def clear = value.clear
82
+ def keep_if(...)
83
+ raise(ArgumentError, "You can only filter :trace type, and this is :#{type}") unless trace?
82
84
 
83
- def trace? = type == :trace
85
+ value.keep_if(...)
86
+ end
84
87
 
85
- def to_h
86
- if trace?
87
- value.transform_values(&:to_h).tap { _1.default_proc = nil }
88
- else
89
- {value:, source:}
90
- end
88
+ def clear = value.clear
89
+
90
+ def trace? = type == :trace
91
+
92
+ def to_h
93
+ if trace?
94
+ value.transform_values(&:to_h).tap { _1.default_proc = nil }
95
+ else
96
+ { value:, source: }
91
97
  end
98
+ end
92
99
 
93
- def dup = self.class.new(type, value.dup, **source)
94
-
95
- def pretty_print(q)
96
- if trace?
97
- q.nest(2) do
98
- q.breakable ""
99
- q.seplist(value, nil, :each) do |k, v|
100
- q.group do
101
- q.text k
102
- q.text " =>"
103
- if v.trace?
104
- q.text " { "
105
- q.pp v
106
- q.breakable " "
107
- q.text "}"
108
- else
109
- q.breakable " "
110
- q.pp v
111
- end
100
+ def dup = self.class.new(type, value.dup, **source)
101
+
102
+ def pretty_print(q)
103
+ if trace?
104
+ q.nest(2) do
105
+ q.breakable('')
106
+ q.seplist(value, nil, :each) do |k, v|
107
+ q.group do
108
+ q.text k
109
+ q.text ' =>'
110
+ if v.trace?
111
+ q.text ' { '
112
+ q.pp(v)
113
+ q.breakable(' ')
114
+ q.text '}'
115
+ else
116
+ q.breakable(' ')
117
+ q.pp(v)
112
118
  end
113
119
  end
114
120
  end
115
- else
116
- q.pp value
117
- q.group(0, " (", ")") do
118
- q.seplist(source, lambda { q.breakable " " }, :each) do |k, v|
119
- q.group do
120
- q.text k.to_s
121
- q.text "="
122
- q.text v.to_s
123
- end
121
+ end
122
+ else
123
+ q.pp(value)
124
+ q.group(0, ' (', ')') do
125
+ q.seplist(source, lambda { q.breakable(' ') }, :each) do |k, v|
126
+ q.group do
127
+ q.text k.to_s
128
+ q.text '='
129
+ q.text v.to_s
124
130
  end
125
131
  end
126
132
  end
127
133
  end
128
134
  end
135
+ end
129
136
 
130
- class << self
131
- def capture
132
- unless Settings.tracing_enabled
133
- yield
134
- return
135
- end
136
-
137
- trace = Trace.new
138
- trace_stack.push trace
137
+ class << self
138
+ def capture
139
+ unless ::Runger::Settings.tracing_enabled
139
140
  yield
140
- trace_stack.last
141
- ensure
142
- trace_stack.pop
141
+ return
143
142
  end
144
143
 
145
- def trace_stack
146
- (Thread.current[:__runger__trace_stack__] ||= [])
147
- end
144
+ trace = Trace.new
145
+ trace_stack.push(trace)
146
+ yield
147
+ trace_stack.last
148
+ ensure
149
+ trace_stack.pop
150
+ end
148
151
 
149
- def current_trace = trace_stack.last
152
+ def trace_stack
153
+ (Thread.current[:__runger__trace_stack__] ||= [])
154
+ end
150
155
 
151
- alias_method :tracing?, :current_trace
156
+ def current_trace = trace_stack.last
152
157
 
153
- def source_stack
154
- (Thread.current[:__runger__trace_source_stack__] ||= [])
155
- end
158
+ alias tracing? current_trace
156
159
 
157
- def current_trace_source
158
- source_stack.last || accessor_source(caller_locations(2, 1).first)
159
- end
160
+ def source_stack
161
+ (Thread.current[:__runger__trace_source_stack__] ||= [])
162
+ end
160
163
 
161
- def with_trace_source(src)
162
- source_stack << src
163
- yield
164
- ensure
165
- source_stack.pop
166
- end
164
+ def current_trace_source
165
+ source_stack.last || accessor_source(caller_locations(2, 1).first)
166
+ end
167
+
168
+ def with_trace_source(src)
169
+ source_stack << src
170
+ yield
171
+ ensure
172
+ source_stack.pop
173
+ end
167
174
 
168
- private
175
+ private
169
176
 
170
- def accessor_source(location)
171
- {type: :accessor, called_from: location.path_lineno}
172
- end
177
+ def accessor_source(location)
178
+ { type: :accessor, called_from: location.path_lineno }
173
179
  end
180
+ end
174
181
 
175
- module_function
182
+ module_function
176
183
 
177
- def trace!(type, *path, **)
178
- return yield unless Tracing.tracing?
179
- val = yield
180
- if val.is_a?(Hash)
181
- Tracing.current_trace.merge_values(val, type:, **)
182
- elsif !path.empty?
183
- Tracing.current_trace.record_value(val, *path, type:, **)
184
- end
185
- val
184
+ def trace!(type, *path, **options)
185
+ return yield unless ::Runger::Tracing.tracing?
186
+
187
+ val = yield
188
+ if val.is_a?(Hash)
189
+ ::Runger::Tracing.current_trace.merge_values(val, type:, **options)
190
+ elsif !path.empty?
191
+ ::Runger::Tracing.current_trace.record_value(val, *path, type:, **options)
186
192
  end
193
+ val
187
194
  end
188
195
  end
@@ -15,7 +15,8 @@ module Runger
15
15
 
16
16
  def accept(name_or_object, &block)
17
17
  if !block && !name_or_object.respond_to?(:call)
18
- raise ArgumentError, "Please, provide a type casting block or an object implementing #call(val) method"
18
+ raise(ArgumentError,
19
+ 'Please, provide a type casting block or an object implementing #call(val) method')
19
20
  end
20
21
 
21
22
  registry[name_or_object] = block || name_or_object
@@ -26,9 +27,13 @@ module Runger
26
27
 
27
28
  caster =
28
29
  if type_id.is_a?(Symbol) || type_id.nil?
29
- registry.fetch(type_id) { raise ArgumentError, "Unknown type: #{type_id}" }
30
+ registry.fetch(type_id) { raise(ArgumentError, "Unknown type: #{type_id}") }
30
31
  else
31
- raise ArgumentError, "Type must implement #call(val): #{type_id}" unless type_id.respond_to?(:call)
32
+ unless type_id.respond_to?(:call)
33
+ raise(ArgumentError,
34
+ "Type must implement #call(val): #{type_id}")
35
+ end
36
+
32
37
  type_id
33
38
  end
34
39
 
@@ -58,7 +63,7 @@ module Runger
58
63
  obj.accept(:float, &:to_f)
59
64
 
60
65
  obj.accept(:date) do
61
- require "date" unless defined?(::Date)
66
+ require 'date' unless defined?(::Date)
62
67
 
63
68
  next _1 if _1.is_a?(::Date)
64
69
 
@@ -68,7 +73,7 @@ module Runger
68
73
  end
69
74
 
70
75
  obj.accept(:datetime) do
71
- require "date" unless defined?(::Date)
76
+ require 'date' unless defined?(::Date)
72
77
 
73
78
  next _1 if _1.is_a?(::DateTime)
74
79
 
@@ -78,7 +83,7 @@ module Runger
78
83
  end
79
84
 
80
85
  obj.accept(:uri) do
81
- require "uri" unless defined?(::URI)
86
+ require 'uri' unless defined?(::URI)
82
87
 
83
88
  next _1 if _1.is_a?(::URI)
84
89
 
@@ -90,8 +95,8 @@ module Runger
90
95
  end
91
96
  end
92
97
 
93
- unless "".respond_to?(:safe_constantize)
94
- require "runger/ext/string_constantize"
98
+ unless ''.respond_to?(:safe_constantize)
99
+ require 'runger/ext/string_constantize'
95
100
  using Runger::Ext::StringConstantize
96
101
  end
97
102
 
@@ -115,16 +120,16 @@ module Runger
115
120
 
116
121
  case caster_config
117
122
  in Hash[array:, type:, **nil]
118
- registry.deserialize(val, type, array: array)
123
+ registry.deserialize(val, type, array:)
119
124
  in Hash[config: subconfig]
120
125
  subconfig = subconfig.safe_constantize if subconfig.is_a?(::String)
121
- raise ArgumentError, "Config is not found: #{subconfig}" unless subconfig
126
+ raise(ArgumentError, "Config is not found: #{subconfig}") unless subconfig
122
127
 
123
128
  subconfig.new(val)
124
129
  in Hash
125
130
  return val unless val.is_a?(Hash)
126
131
 
127
- caster_config.each do |k, v|
132
+ caster_config.each_key do |k|
128
133
  ks = k.to_s
129
134
  next unless val.key?(ks)
130
135
 
@@ -1,18 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Runger
4
- module Utils
5
- # Cross-platform solution
6
- # taken from https://stackoverflow.com/a/5471032
7
- def self.which(cmd)
8
- exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
9
- ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
10
- exts.each do |ext|
11
- exe = File.join(path, "#{cmd}#{ext}")
12
- return exe if File.executable?(exe) && !File.directory?(exe)
13
- end
3
+ module Runger::Utils
4
+ # Cross-platform solution
5
+ # taken from https://stackoverflow.com/a/5471032
6
+ def self.which(cmd)
7
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
8
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
9
+ exts.each do |ext|
10
+ exe = File.join(path, "#{cmd}#{ext}")
11
+ return exe if File.executable?(exe) && !File.directory?(exe)
14
12
  end
15
- nil
16
13
  end
14
+ nil
17
15
  end
18
16
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Runger # :nodoc:
4
- VERSION = "4.0.0"
4
+ VERSION = '5.1.0'
5
5
  end
data/lib/runger.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "runger_config"
3
+ require 'runger_config'
data/lib/runger_config.rb CHANGED
@@ -1,23 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "runger/version"
4
-
5
- require "runger/ext/deep_dup"
6
- require "runger/ext/deep_freeze"
7
- require "runger/ext/hash"
8
- require "runger/ext/flatten_names"
9
-
10
- require "runger/utils/deep_merge"
11
- require "runger/utils/which"
12
-
13
- require "runger/settings"
14
- require "runger/tracing"
15
- require "runger/config"
16
- require "runger/auto_cast"
17
- require "runger/type_casting"
18
- require "runger/env"
19
- require "runger/loaders"
20
- require "runger/rbs"
3
+ require 'runger/version'
4
+
5
+ module Runger ; end
6
+ module Runger::Ext ; end
7
+ module Runger::Utils ; end
8
+ module Runger::Tracing ; end
9
+ module Runger::Loaders ; end
10
+ module Runger::Testing ; end
11
+
12
+ require 'runger/ext/deep_dup'
13
+ require 'runger/ext/deep_freeze'
14
+ require 'runger/ext/flatten_names'
15
+ require 'runger/ext/hash'
16
+
17
+ require 'runger/utils/deep_merge'
18
+ require 'runger/utils/which'
19
+
20
+ require 'runger/auto_cast'
21
+ require 'runger/config'
22
+ require 'runger/env'
23
+ require 'runger/loaders'
24
+ require 'runger/rbs'
25
+ require 'runger/settings'
26
+ require 'runger/tracing'
27
+ require 'runger/type_casting'
21
28
 
22
29
  module Runger # :nodoc:
23
30
  class << self
@@ -31,19 +38,19 @@ module Runger # :nodoc:
31
38
  end
32
39
 
33
40
  # Configure default loaders
34
- loaders.append :yml, Loaders::YAML
35
- loaders.append :ejson, Loaders::EJSON if Utils.which("ejson")
36
- loaders.append :env, Loaders::Env
41
+ loaders.append(:yml, Loaders::YAML)
42
+ loaders.append(:ejson, Loaders::EJSON) if Utils.which('ejson')
43
+ loaders.append(:env, Loaders::Env)
37
44
 
38
- if ENV.key?("DOPPLER_TOKEN") && ENV["RUNGER_CONFIG_DISABLE_DOPPLER"] != "true"
39
- loaders.append :doppler, Loaders::Doppler
45
+ if ENV.key?('DOPPLER_TOKEN') && ENV['RUNGER_CONFIG_DISABLE_DOPPLER'] != 'true'
46
+ loaders.append(:doppler, Loaders::Doppler)
40
47
  end
41
48
  end
42
49
 
43
- if defined?(::Rails::VERSION)
44
- require "runger/rails"
50
+ if defined?(Rails::VERSION)
51
+ require 'runger/rails'
45
52
  else
46
- require "runger/rails/autoload"
53
+ require 'runger/rails/autoload'
47
54
  end
48
55
 
49
- require "runger/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
56
+ require 'runger/testing' if ENV['RACK_ENV'] == 'test' || ENV['RAILS_ENV'] == 'test'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runger_config
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Runger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-20 00:00:00.000000000 Z
11
+ date: 2024-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 1.1.3
41
+ - !ruby/object:Gem::Dependency
42
+ name: ejson
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.3.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.1
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +94,6 @@ dependencies:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
96
  version: '3.18'
83
- - !ruby/object:Gem::Dependency
84
- name: ejson
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: 1.3.1
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: 1.3.1
97
97
  description: "\n Configuration DSL for Ruby libraries and applications.\n Allows
98
98
  you to easily follow the twelve-factor application principles (https://12factor.net/config).\n
99
99
  \ "
@@ -106,6 +106,7 @@ files:
106
106
  - CHANGELOG.md
107
107
  - LICENSE.txt
108
108
  - README.md
109
+ - bin/release
109
110
  - lib/generators/runger/app_config/USAGE
110
111
  - lib/generators/runger/app_config/app_config_generator.rb
111
112
  - lib/generators/runger/config/USAGE
@@ -160,7 +161,7 @@ licenses:
160
161
  - MIT
161
162
  metadata:
162
163
  bug_tracker_uri: http://github.com/davidrunger/runger_config/issues
163
- changelog_uri: https://github.com/davidrunger/runger_config/blob/master/CHANGELOG.md
164
+ changelog_uri: https://github.com/davidrunger/runger_config/blob/main/CHANGELOG.md
164
165
  documentation_uri: http://github.com/davidrunger/runger_config
165
166
  funding_uri: https://github.com/sponsors/davidrunger
166
167
  homepage_uri: http://github.com/davidrunger/runger_config
@@ -174,14 +175,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
175
  requirements:
175
176
  - - ">="
176
177
  - !ruby/object:Gem::Version
177
- version: 3.2.2
178
+ version: 3.3.0
178
179
  required_rubygems_version: !ruby/object:Gem::Requirement
179
180
  requirements:
180
181
  - - ">="
181
182
  - !ruby/object:Gem::Version
182
183
  version: '0'
183
184
  requirements: []
184
- rubygems_version: 3.4.22
185
+ rubygems_version: 3.5.11
185
186
  signing_key:
186
187
  specification_version: 4
187
188
  summary: Configuration DSL for Ruby libraries and applications