piko-quick-lib 0.0.1

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/dry-system-1.2.5/CHANGELOG.md +1311 -0
  3. data/dry-system-1.2.5/LICENSE +21 -0
  4. data/dry-system-1.2.5/README.md +17 -0
  5. data/dry-system-1.2.5/dry-system.gemspec +42 -0
  6. data/dry-system-1.2.5/lib/dry/system/auto_registrar.rb +45 -0
  7. data/dry-system-1.2.5/lib/dry/system/component.rb +190 -0
  8. data/dry-system-1.2.5/lib/dry/system/component_dir.rb +171 -0
  9. data/dry-system-1.2.5/lib/dry/system/config/component_dir.rb +228 -0
  10. data/dry-system-1.2.5/lib/dry/system/config/component_dirs.rb +285 -0
  11. data/dry-system-1.2.5/lib/dry/system/config/namespace.rb +75 -0
  12. data/dry-system-1.2.5/lib/dry/system/config/namespaces.rb +192 -0
  13. data/dry-system-1.2.5/lib/dry/system/constants.rb +13 -0
  14. data/dry-system-1.2.5/lib/dry/system/container.rb +685 -0
  15. data/dry-system-1.2.5/lib/dry/system/errors.rb +132 -0
  16. data/dry-system-1.2.5/lib/dry/system/identifier.rb +176 -0
  17. data/dry-system-1.2.5/lib/dry/system/importer.rb +144 -0
  18. data/dry-system-1.2.5/lib/dry/system/indirect_component.rb +63 -0
  19. data/dry-system-1.2.5/lib/dry/system/loader/autoloading.rb +24 -0
  20. data/dry-system-1.2.5/lib/dry/system/loader.rb +84 -0
  21. data/dry-system-1.2.5/lib/dry/system/magic_comments_parser.rb +31 -0
  22. data/dry-system-1.2.5/lib/dry/system/manifest_registrar.rb +57 -0
  23. data/dry-system-1.2.5/lib/dry/system/plugins/bootsnap.rb +47 -0
  24. data/dry-system-1.2.5/lib/dry/system/plugins/dependency_graph/strategies.rb +66 -0
  25. data/dry-system-1.2.5/lib/dry/system/plugins/dependency_graph.rb +53 -0
  26. data/dry-system-1.2.5/lib/dry/system/plugins/env.rb +30 -0
  27. data/dry-system-1.2.5/lib/dry/system/plugins/logging.rb +73 -0
  28. data/dry-system-1.2.5/lib/dry/system/plugins/monitoring/proxy.rb +51 -0
  29. data/dry-system-1.2.5/lib/dry/system/plugins/monitoring.rb +45 -0
  30. data/dry-system-1.2.5/lib/dry/system/plugins/notifications.rb +27 -0
  31. data/dry-system-1.2.5/lib/dry/system/plugins/plugin.rb +61 -0
  32. data/dry-system-1.2.5/lib/dry/system/plugins/zeitwerk/compat_inflector.rb +22 -0
  33. data/dry-system-1.2.5/lib/dry/system/plugins/zeitwerk.rb +109 -0
  34. data/dry-system-1.2.5/lib/dry/system/plugins.rb +70 -0
  35. data/dry-system-1.2.5/lib/dry/system/provider/source.rb +281 -0
  36. data/dry-system-1.2.5/lib/dry/system/provider/source_dsl.rb +55 -0
  37. data/dry-system-1.2.5/lib/dry/system/provider.rb +291 -0
  38. data/dry-system-1.2.5/lib/dry/system/provider_registrar.rb +289 -0
  39. data/dry-system-1.2.5/lib/dry/system/provider_source_registry.rb +67 -0
  40. data/dry-system-1.2.5/lib/dry/system/provider_sources/settings/config.rb +73 -0
  41. data/dry-system-1.2.5/lib/dry/system/provider_sources/settings/loader.rb +44 -0
  42. data/dry-system-1.2.5/lib/dry/system/provider_sources/settings.rb +40 -0
  43. data/dry-system-1.2.5/lib/dry/system/provider_sources.rb +6 -0
  44. data/dry-system-1.2.5/lib/dry/system/stubs.rb +39 -0
  45. data/dry-system-1.2.5/lib/dry/system/version.rb +7 -0
  46. data/dry-system-1.2.5/lib/dry/system.rb +62 -0
  47. data/dry-system-1.2.5/lib/dry-system.rb +3 -0
  48. data/piko-quick-lib.gemspec +11 -0
  49. metadata +87 -0
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ # Error raised when import is called on an already finalized container
6
+ #
7
+ # @api public
8
+ ContainerAlreadyFinalizedError = Class.new(StandardError)
9
+
10
+ # Error raised when a component dir is added to configuration more than once
11
+ #
12
+ # @api public
13
+ ComponentDirAlreadyAddedError = Class.new(StandardError) do
14
+ def initialize(dir)
15
+ super("Component directory #{dir.inspect} already added")
16
+ end
17
+ end
18
+
19
+ # Error raised when a configured component directory could not be found
20
+ #
21
+ # @api public
22
+ ComponentDirNotFoundError = Class.new(StandardError) do
23
+ def initialize(dir)
24
+ super("Component dir '#{dir}' not found")
25
+ end
26
+ end
27
+
28
+ # Error raised when a namespace for a component dir is added to configuration more
29
+ # than once
30
+ #
31
+ # @api public
32
+ NamespaceAlreadyAddedError = Class.new(StandardError) do
33
+ def initialize(path)
34
+ path_label = path ? "path #{path.inspect}" : "root path"
35
+
36
+ super("Namespace for #{path_label} already added")
37
+ end
38
+ end
39
+
40
+ # Error raised when attempting to register provider using a name that has already been
41
+ # registered
42
+ #
43
+ # @api public
44
+ ProviderAlreadyRegisteredError = Class.new(ArgumentError) do
45
+ def initialize(provider_name)
46
+ super("Provider #{provider_name.inspect} has already been registered")
47
+ end
48
+ end
49
+
50
+ # Error raised when a named provider could not be found
51
+ #
52
+ # @api public
53
+ ProviderNotFoundError = Class.new(ArgumentError) do
54
+ def initialize(name)
55
+ super("Provider #{name.inspect} not found")
56
+ end
57
+ end
58
+
59
+ # Error raised when a named provider source could not be found
60
+ #
61
+ # @api public
62
+ ProviderSourceNotFoundError = Class.new(StandardError) do
63
+ def initialize(name:, group:, keys:)
64
+ msg = "Provider source not found: #{name.inspect}, group: #{group.inspect}"
65
+
66
+ key_list = keys.map { |key| "- #{key[:name].inspect}, group: #{key[:group].inspect}" }
67
+ msg += "Available provider sources:\n\n#{key_list}"
68
+
69
+ super(msg)
70
+ end
71
+ end
72
+
73
+ # Error raised when trying to use a plugin that does not exist.
74
+ #
75
+ # @api public
76
+ PluginNotFoundError = Class.new(StandardError) do
77
+ def initialize(plugin_name)
78
+ super("Plugin #{plugin_name.inspect} does not exist")
79
+ end
80
+ end
81
+
82
+ # Exception raise when a plugin dependency failed to load
83
+ #
84
+ # @api public
85
+ PluginDependencyMissing = Class.new(StandardError) do
86
+ # @api private
87
+ def initialize(plugin, message, gem = nil)
88
+ details = gem ? "#{message} - add #{gem} to your Gemfile" : message
89
+ super("dry-system plugin #{plugin.inspect} failed to load its dependencies: #{details}")
90
+ end
91
+ end
92
+
93
+ # Exception raised when auto-registerable component is not loadable
94
+ #
95
+ # @api public
96
+ ComponentNotLoadableError = Class.new(NameError) do
97
+ # @api private
98
+ def initialize(component, error,
99
+ corrections: DidYouMean::ClassNameChecker.new(error).corrections)
100
+ full_class_name = [error.receiver, error.name].join("::")
101
+
102
+ message = [
103
+ "Component '#{component.key}' is not loadable.",
104
+ "Looking for #{full_class_name}."
105
+ ]
106
+
107
+ if corrections.any?
108
+ case_correction = corrections.find { |correction| correction.casecmp?(full_class_name) }
109
+ if case_correction
110
+ acronyms_needed = case_correction.split("::").difference(full_class_name.split("::"))
111
+ stringified_acronyms_needed = acronyms_needed.map { |acronym|
112
+ "'#{acronym}'"
113
+ } .join(", ")
114
+ message <<
115
+ <<~ERROR_MESSAGE
116
+
117
+ You likely need to add:
118
+
119
+ acronym(#{stringified_acronyms_needed})
120
+
121
+ to your container's inflector, since we found a #{case_correction} class.
122
+ ERROR_MESSAGE
123
+ else
124
+ message << DidYouMean.formatter.message_for(corrections)
125
+ end
126
+ end
127
+
128
+ super(message.join("\n"))
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/constants"
4
+
5
+ module Dry
6
+ module System
7
+ # An identifier representing a component to be registered.
8
+ #
9
+ # Components are eventually registered in the container using plain string
10
+ # identifiers, available as the `identifier` or `key` attribute here. Additional
11
+ # methods are provided to make it easier to evaluate or manipulate these identifiers.
12
+ #
13
+ # @api public
14
+ class Identifier
15
+ include Dry::Equalizer(:key)
16
+
17
+ # @return [String] the identifier's string key
18
+ # @api public
19
+ attr_reader :key
20
+
21
+ # @api private
22
+ def initialize(key)
23
+ @key = key.to_s
24
+ end
25
+
26
+ # @!method to_s
27
+ # Returns the identifier string key
28
+ #
29
+ # @return [String]
30
+ # @see #key
31
+ # @api public
32
+ alias_method :to_s, :key
33
+
34
+ # Returns the root namespace segment of the identifier string, as a symbol
35
+ #
36
+ # @example
37
+ # identifier.key # => "articles.operations.create"
38
+ # identifier.root_key # => :articles
39
+ #
40
+ # @return [Symbol] the root key
41
+ # @api public
42
+ def root_key
43
+ segments.first.to_sym
44
+ end
45
+
46
+ # Returns true if the given leading segments string is a leading part of the {key}.
47
+ #
48
+ # Also returns true if nil or an empty string is given.
49
+ #
50
+ # @example
51
+ # identifier.key # => "articles.operations.create"
52
+ #
53
+ # identifier.start_with?("articles.operations") # => true
54
+ # identifier.start_with?("articles") # => true
55
+ # identifier.start_with?("article") # => false
56
+ # identifier.start_with?(nil) # => true
57
+ #
58
+ # @param leading_segments [String] the one or more leading segments to check
59
+ # @return [Boolean]
60
+ # @api public
61
+ def start_with?(leading_segments)
62
+ leading_segments.to_s.empty? ||
63
+ key.start_with?("#{leading_segments}#{KEY_SEPARATOR}") ||
64
+ key.eql?(leading_segments)
65
+ end
66
+
67
+ # Returns true if the given trailing segments string is the end part of the {key}.
68
+ #
69
+ # Also returns true if nil or an empty string is given.
70
+ #
71
+ # @example
72
+ # identifier.key # => "articles.operations.create"
73
+ #
74
+ # identifier.end_with?("create") # => true
75
+ # identifier.end_with?("operations.create") # => true
76
+ # identifier.end_with?("ate") # => false, not a whole segment
77
+ # identifier.end_with?("nup") # => false, not in key at all
78
+ #
79
+ # @param trailing_segments [String] the one or more trailing key segments to check
80
+ # @return [Boolean]
81
+ # @api public
82
+ def end_with?(trailing_segments)
83
+ trailing_segments.to_s.empty? ||
84
+ key.end_with?("#{KEY_SEPARATOR}#{trailing_segments}") ||
85
+ key.eql?(trailing_segments)
86
+ end
87
+
88
+ # Returns true if the given segments string matches whole segments within the {key}.
89
+ #
90
+ # @example
91
+ # identifier.key # => "articles.operations.create"
92
+ #
93
+ # identifier.include?("operations") # => true
94
+ # identifier.include?("articles.operations") # => true
95
+ # identifier.include?("operations.create") # => true
96
+ #
97
+ # identifier.include?("article") # => false, not a whole segment
98
+ # identifier.include?("update") # => false, not in key at all
99
+ #
100
+ # @param segments [String] the one of more key segments to check
101
+ # @return [Boolean]
102
+ # @api public
103
+ def include?(segments)
104
+ return false if segments.to_s.empty?
105
+
106
+ sep_re = Regexp.escape(KEY_SEPARATOR)
107
+ key.match?(
108
+ /
109
+ (\A|#{sep_re})
110
+ #{Regexp.escape(segments)}
111
+ (\Z|#{sep_re})
112
+ /x
113
+ )
114
+ end
115
+
116
+ # Returns the key with its segments separated by the given separator
117
+ #
118
+ # @example
119
+ # identifier.key # => "articles.operations.create"
120
+ # identifier.key_with_separator("/") # => "articles/operations/create"
121
+ #
122
+ # @return [String] the key using the separator
123
+ # @api private
124
+ def key_with_separator(separator)
125
+ segments.join(separator)
126
+ end
127
+
128
+ # Returns a copy of the identifier with the key's leading namespace(s) replaced
129
+ #
130
+ # @example Changing a namespace
131
+ # identifier.key # => "articles.operations.create"
132
+ # identifier.namespaced(from: "articles", to: "posts").key # => "posts.commands.create"
133
+ #
134
+ # @example Removing a namespace
135
+ # identifier.key # => "articles.operations.create"
136
+ # identifier.namespaced(from: "articles", to: nil).key # => "operations.create"
137
+ #
138
+ # @example Adding a namespace
139
+ # identifier.key # => "articles.operations.create"
140
+ # identifier.namespaced(from: nil, to: "admin").key # => "admin.articles.operations.create"
141
+ #
142
+ # @param from [String, nil] the leading namespace(s) to replace
143
+ # @param to [String, nil] the replacement for the leading namespace
144
+ #
145
+ # @return [Dry::System::Identifier] the copy of the identifier
146
+ #
147
+ # @see #initialize
148
+ # @api private
149
+ def namespaced(from:, to:)
150
+ return self if from == to
151
+
152
+ separated_to = "#{to}#{KEY_SEPARATOR}" if to
153
+
154
+ new_key =
155
+ if from.nil?
156
+ "#{separated_to}#{key}"
157
+ else
158
+ key.sub(
159
+ /^#{Regexp.escape(from.to_s)}#{Regexp.escape(KEY_SEPARATOR)}/,
160
+ separated_to || EMPTY_STRING
161
+ )
162
+ end
163
+
164
+ return self if new_key == key
165
+
166
+ self.class.new(new_key)
167
+ end
168
+
169
+ private
170
+
171
+ def segments
172
+ @segments ||= key.split(KEY_SEPARATOR)
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/constants"
4
+
5
+ module Dry
6
+ module System
7
+ # Default importer implementation
8
+ #
9
+ # This is currently configured by default for every System::Container.
10
+ # Importer objects are responsible for importing components from one
11
+ # container to another. This is used in cases where an application is split
12
+ # into multiple sub-systems.
13
+ #
14
+ # @api private
15
+ class Importer
16
+ # @api private
17
+ class Item
18
+ attr_reader :namespace, :container, :import_keys
19
+
20
+ def initialize(namespace:, container:, import_keys:)
21
+ @namespace = namespace
22
+ @container = container
23
+ @import_keys = import_keys
24
+ end
25
+ end
26
+
27
+ attr_reader :container
28
+
29
+ attr_reader :registry
30
+
31
+ # @api private
32
+ def initialize(container)
33
+ @container = container
34
+ @registry = {}
35
+ end
36
+
37
+ # @api private
38
+ def register(namespace:, container:, keys: nil)
39
+ registry[namespace_key(namespace)] = Item.new(
40
+ namespace: namespace,
41
+ container: container,
42
+ import_keys: keys
43
+ )
44
+ end
45
+
46
+ # @api private
47
+ def [](name)
48
+ registry.fetch(namespace_key(name))
49
+ end
50
+
51
+ # @api private
52
+ def key?(name)
53
+ registry.key?(namespace_key(name))
54
+ end
55
+ alias_method :namespace?, :key?
56
+
57
+ # @api private
58
+ def finalize!
59
+ registry.each_key { import(_1) }
60
+ self
61
+ end
62
+
63
+ # @api private
64
+ def import(namespace, keys: Undefined)
65
+ item = self[namespace]
66
+ keys = Undefined.default(keys, item.import_keys)
67
+
68
+ if keys
69
+ import_keys(item.container, namespace, keys_to_import(keys, item))
70
+ else
71
+ import_all(item.container, namespace)
72
+ end
73
+
74
+ self
75
+ end
76
+
77
+ private
78
+
79
+ # Returns nil if given a nil (i.e. root) namespace. Otherwise, converts the namespace to a
80
+ # string.
81
+ #
82
+ # This ensures imported components are found when either symbols or strings to given as the
83
+ # namespace in {Container.import}.
84
+ def namespace_key(namespace)
85
+ return nil if namespace.nil?
86
+
87
+ namespace.to_s
88
+ end
89
+
90
+ def keys_to_import(keys, item)
91
+ keys
92
+ .then { (arr = item.import_keys) ? _1 & arr : _1 }
93
+ .then { (arr = item.container.exports) ? _1 & arr : _1 }
94
+ end
95
+
96
+ def import_keys(other, namespace, keys)
97
+ merge(container, build_merge_container(other, keys), namespace: namespace)
98
+ end
99
+
100
+ def import_all(other, namespace)
101
+ merge_container =
102
+ if other.exports
103
+ build_merge_container(other, other.exports)
104
+ else
105
+ build_merge_container(other.finalize!, other.keys)
106
+ end
107
+
108
+ merge(container, merge_container, namespace: namespace)
109
+ end
110
+
111
+ # Merges `other` into `container`, favoring the container's existing registrations
112
+ def merge(container, other, namespace:)
113
+ container.merge(other, namespace: namespace) { |_key, old_item, new_item|
114
+ old_item || new_item
115
+ }
116
+ end
117
+
118
+ def build_merge_container(other, keys)
119
+ keys.each_with_object(Core::Container.new) { |key, ic|
120
+ next unless other.key?(key)
121
+
122
+ # Access the other container's items directly so that we can preserve all their
123
+ # options when we merge them with the target container (e.g. if a component in
124
+ # the provider container was registered with a block, we want block registration
125
+ # behavior to be exhibited when later resolving that component from the target
126
+ # container). TODO: Make this part of dry-system's public API.
127
+ item = other._container[key]
128
+
129
+ # By default, we "protect" components that were themselves imported into the
130
+ # other container from being implicitly exported; imported components are
131
+ # considered "private" and must be explicitly included in `exports` to be
132
+ # exported.
133
+ next if item.options[:imported] && !other.exports
134
+
135
+ if item.callable?
136
+ ic.register(key, **item.options, imported: true, &item.item)
137
+ else
138
+ ic.register(key, item.item, **item.options, imported: true)
139
+ end
140
+ }
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ # An indirect component is a component that cannot be directly from a source file
6
+ # directly managed by the container. It may be component that needs to be loaded
7
+ # indirectly, either via a registration manifest file or an imported container
8
+ #
9
+ # Indirect components are an internal abstraction and, unlike ordinary components, are
10
+ # not exposed to users via component dir configuration hooks.
11
+ #
12
+ # @see Container#load_component
13
+ # @see Container#find_component
14
+ #
15
+ # @api private
16
+ class IndirectComponent
17
+ include Dry::Equalizer(:identifier)
18
+
19
+ # @!attribute [r] identifier
20
+ # @return [String] the component's unique identifier
21
+ attr_reader :identifier
22
+
23
+ # @api private
24
+ def initialize(identifier)
25
+ @identifier = identifier
26
+ end
27
+
28
+ # Returns false, indicating that the component is not directly loadable from the
29
+ # files managed by the container
30
+ #
31
+ # This is the inverse of {Component#loadable?}
32
+ #
33
+ # @return [FalseClass]
34
+ #
35
+ # @api private
36
+ def loadable?
37
+ false
38
+ end
39
+
40
+ # Returns the component's unique key
41
+ #
42
+ # @return [String] the key
43
+ #
44
+ # @see Identifier#key
45
+ #
46
+ # @api private
47
+ def key
48
+ identifier.to_s
49
+ end
50
+
51
+ # Returns the root namespace segment of the component's key, as a symbol
52
+ #
53
+ # @see Identifier#root_key
54
+ #
55
+ # @return [Symbol] the root key
56
+ #
57
+ # @api private
58
+ def root_key
59
+ identifier.root_key
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ class Loader
6
+ # Component loader for autoloading-enabled applications
7
+ #
8
+ # This behaves like the default loader, except instead of requiring the given path,
9
+ # it loads the respective constant, allowing the autoloader to load the
10
+ # corresponding file per its own configuration.
11
+ #
12
+ # @see Loader
13
+ # @api public
14
+ class Autoloading < Loader
15
+ class << self
16
+ def require!(component)
17
+ constant(component)
18
+ self
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/system/errors"
4
+
5
+ module Dry
6
+ module System
7
+ # Default component loader implementation
8
+ #
9
+ # This class is configured by default for every System::Container. You can
10
+ # provide your own and use it in your containers too.
11
+ #
12
+ # @example
13
+ # class MyLoader < Dry::System::Loader
14
+ # def call(*args)
15
+ # constant.build(*args)
16
+ # end
17
+ # end
18
+ #
19
+ # class MyApp < Dry::System::Container
20
+ # configure do |config|
21
+ # # ...
22
+ # config.component_dirs.loader = MyLoader
23
+ # end
24
+ # end
25
+ #
26
+ # @api public
27
+ class Loader
28
+ class << self
29
+ # Requires the component's source file
30
+ #
31
+ # @api public
32
+ def require!(component)
33
+ require(component.require_path)
34
+ self
35
+ end
36
+
37
+ # Returns an instance of the component
38
+ #
39
+ # Provided optional args are passed to object's constructor
40
+ #
41
+ # @param [Array] args Optional constructor args
42
+ #
43
+ # @return [Object]
44
+ #
45
+ # @api public
46
+ def call(component, *args, **kwargs)
47
+ require!(component)
48
+
49
+ constant = self.constant(component)
50
+
51
+ if singleton?(constant)
52
+ constant.instance(*args, **kwargs)
53
+ else
54
+ constant.new(*args, **kwargs)
55
+ end
56
+ end
57
+
58
+ # Returns the component's class constant
59
+ #
60
+ # @return [Class]
61
+ #
62
+ # @api public
63
+ def constant(component)
64
+ inflector = component.inflector
65
+ const_name = inflector.camelize(component.const_path)
66
+ inflector.constantize(const_name)
67
+ rescue NameError => exception
68
+ # Ensure it's this component's constant, not any other NameError within the component
69
+ if exception.message =~ /#{const_name}( |\n|$)/
70
+ raise ComponentNotLoadableError.new(component, exception)
71
+ else
72
+ raise exception
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def singleton?(constant)
79
+ constant.respond_to?(:instance) && !constant.respond_to?(:new)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dry
4
+ module System
5
+ class MagicCommentsParser
6
+ VALID_LINE_RE = /^(#.*)?$/
7
+ COMMENT_RE = /^#\s+(?<name>[A-Za-z]{1}[A-Za-z0-9_]+):\s+(?<value>.+?)$/
8
+
9
+ COERCIONS = {
10
+ "true" => true,
11
+ "false" => false
12
+ }.freeze
13
+
14
+ def self.call(file_name)
15
+ {}.tap do |options|
16
+ File.foreach(file_name) do |line|
17
+ break unless line =~ VALID_LINE_RE
18
+
19
+ if (comment = line.match(COMMENT_RE))
20
+ options[comment[:name].to_sym] = coerce(comment[:value])
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.coerce(value)
27
+ COERCIONS.fetch(value) { value }
28
+ end
29
+ end
30
+ end
31
+ end