directive 0.22.0
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/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/lib/directive.rb +92 -0
- data/lib/directive/config_builder.rb +55 -0
- data/lib/directive/config_builder/double_configure.rb +39 -0
- data/lib/directive/config_delegation.rb +45 -0
- data/lib/directive/config_object.rb +61 -0
- data/lib/directive/evaluator.rb +20 -0
- data/lib/directive/reader.rb +44 -0
- data/lib/directive/spec_helper.rb +10 -0
- data/lib/directive/spec_helper/dsl.rb +60 -0
- data/lib/directive/spec_helper/matchers.rb +18 -0
- data/lib/directive/spec_helper/matchers/configuration/define_config_option.rb +41 -0
- data/lib/directive/spec_helper/matchers/global/delegate_config_to.rb +62 -0
- data/lib/directive/version.rb +6 -0
- metadata +179 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1edf4b07fc8b982911d978b267df2c336f4b0dba62dad286c80ae44a3b280215
|
4
|
+
data.tar.gz: 7c1ae7fb109c9ff8261a6f8fda3b4fd15c7eda5682243fe8500ef0a7e75dc9b5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a907b40c95e58500e348cec77ebf864d2e3896ee13edcbe162c01d2b109964db5e056017f3abc9196b5e107c55f9dc374e2753d9e6e32749907a715ebc455ad1
|
7
|
+
data.tar.gz: 6cd58b199249b11d00fe6b052d01ac0de6b74f8d879043364b8d2ac9cfce948fdb29d016e6ba27106bc27574d848e3d7572e3386f8e48afb33e8435639b72f1c
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Allen Rettberg
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Directive
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/directive`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'directive'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install directive
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/directive.
|
36
|
+
|
37
|
+
## License
|
38
|
+
|
39
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/lib/directive.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "directive/config_builder"
|
4
|
+
require_relative "directive/config_delegation"
|
5
|
+
require_relative "directive/config_object"
|
6
|
+
require_relative "directive/evaluator"
|
7
|
+
require_relative "directive/reader"
|
8
|
+
|
9
|
+
# NOTE: This is still a pre-release feature! Use at your own risk - it may change before being released.
|
10
|
+
#
|
11
|
+
# A utility for creating read-only gem configuration singletons.
|
12
|
+
#
|
13
|
+
# Usage:
|
14
|
+
# # In your gem:
|
15
|
+
# module SomeGem
|
16
|
+
# module Configuration
|
17
|
+
# extend Directive
|
18
|
+
#
|
19
|
+
# configuration_options do
|
20
|
+
# option :some_config_option
|
21
|
+
# option :some_option_with_a_default, default: "I probably know what's best"
|
22
|
+
#
|
23
|
+
# nested :whats_behind do
|
24
|
+
# option :door_one, default: "It's a goat"
|
25
|
+
# option :door_two, default: "Another goat"
|
26
|
+
# option :door_three, default: "It's a car!"
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # Then, in the application using the gem:
|
33
|
+
# SomeGem::Configuration.configure do |config|
|
34
|
+
# config.some_config_option = 12345
|
35
|
+
# config.some_option_with_a_default = "Nope, you really don't"
|
36
|
+
#
|
37
|
+
# config.whats_behind do |nested|
|
38
|
+
# nested.door_one = "It's a boat!"
|
39
|
+
# nested.door_three = "The teletubbies on repeat 😱"
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# # Then, back in your gem code:
|
44
|
+
# puts Configuration.config.some_config_option
|
45
|
+
# => 12345
|
46
|
+
# puts Configuration.config.whats_behind.door_one
|
47
|
+
# => "It's a boat!"
|
48
|
+
#
|
49
|
+
# # Or, if you want to select dynamically:
|
50
|
+
# doors = %i[door_one door_two door_three]
|
51
|
+
# Configuration.config.config_eval(whats_behind, doors.sample).read
|
52
|
+
# => "The teletubbies on repeat 😱"
|
53
|
+
module Directive
|
54
|
+
delegate :configure, :config_eval, to: :_config_builder
|
55
|
+
|
56
|
+
# @return [Directive::ConfigReader] A read-only object containing configuration options set inside {#configure}
|
57
|
+
def config
|
58
|
+
_config_builder.reader
|
59
|
+
end
|
60
|
+
|
61
|
+
# Run a callback before the configure block is evaluated.
|
62
|
+
#
|
63
|
+
# Note: if configure is called multiple times for your gem, this block will get run each time!
|
64
|
+
#
|
65
|
+
def before_configure(&block)
|
66
|
+
_config_builder_class.set_callback(:configure, :before, &block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Run a callback after the configure block is evaluated.
|
70
|
+
#
|
71
|
+
# Note: if configure is called multiple times for your gem, this block will get run each time!
|
72
|
+
#
|
73
|
+
def after_configure(&block)
|
74
|
+
_config_builder_class.set_callback(:configure, :after, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def configuration_options(&block)
|
80
|
+
_config_builder.instance_exec(&block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def _config_builder
|
84
|
+
@_config_builder ||= _config_builder_class.new
|
85
|
+
end
|
86
|
+
|
87
|
+
def _config_builder_class
|
88
|
+
@_config_builder_class ||= Class.new(ConfigBuilder).tap do |klass|
|
89
|
+
const_set(:ConfigBuilder, klass)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "technologic"
|
4
|
+
require "active_support/callbacks"
|
5
|
+
|
6
|
+
require_relative "config_builder/double_configure"
|
7
|
+
|
8
|
+
module Directive
|
9
|
+
class ConfigBuilder
|
10
|
+
include ActiveSupport::Callbacks
|
11
|
+
define_callbacks :configure
|
12
|
+
|
13
|
+
# This concern uses the configure callback, so it needs to be included after the callback is defined
|
14
|
+
include DoubleConfigure
|
15
|
+
|
16
|
+
delegate :config_eval, to: :reader
|
17
|
+
|
18
|
+
def reader
|
19
|
+
@reader ||= Reader.new(configuration)
|
20
|
+
end
|
21
|
+
|
22
|
+
def configure
|
23
|
+
run_callbacks :configure do
|
24
|
+
mutex.synchronize do
|
25
|
+
yield configuration
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# NOTE: options must be set up before {#configure} is called
|
31
|
+
def option(*args, &block)
|
32
|
+
config_class.__send__(:option, *args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def nested(*args, &block)
|
36
|
+
config_class.__send__(:nested, *args, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_writer :configure_called
|
42
|
+
|
43
|
+
def configuration
|
44
|
+
config_class.instance
|
45
|
+
end
|
46
|
+
|
47
|
+
def config_class
|
48
|
+
@config_class ||= Class.new(ConfigObject)
|
49
|
+
end
|
50
|
+
|
51
|
+
def mutex
|
52
|
+
@mutex = Mutex.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "technologic"
|
4
|
+
|
5
|
+
module Directive
|
6
|
+
module DoubleConfigure
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
include Technologic
|
10
|
+
|
11
|
+
included do
|
12
|
+
set_callback :configure, :after, :warn_on_multiple_configure_calls
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def configure_called?
|
18
|
+
@configure_called
|
19
|
+
end
|
20
|
+
|
21
|
+
def warn_on_multiple_configure_calls
|
22
|
+
unless configure_called?
|
23
|
+
@configure_called = true
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
puts <<~WARNING
|
28
|
+
#{self.class._configurable_module_name}.configure has been called more than once, which can lead to unexpected consequences.
|
29
|
+
For the most predictable behavior, configure should only be called once.
|
30
|
+
WARNING
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
def _configurable_module_name
|
35
|
+
name.gsub("::#{name.demodulize}", "")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
|
5
|
+
module Directive
|
6
|
+
module ConfigDelegation
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
DEFAULT_CONFIGURATION_MODULE_NAME = "Configuration"
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
delegate :config, :configure, to: :_configuration_module
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
# Sets up delegation from a top-level class to a nested Configuration module.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# class SomeClass
|
20
|
+
# include Spicerack::Configurable::ConfigDelegation
|
21
|
+
#
|
22
|
+
# delegates_to_configuration
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# module SomeClass::Configuration
|
26
|
+
# include Spicerack::Configurable
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# SomeClass.config
|
30
|
+
# => returns SomeClass::Configuration.config
|
31
|
+
# SomeClass.configure do { |config| # config is the yielded config object from SomeClass::Configuration.configure }
|
32
|
+
#
|
33
|
+
# @param config_class [Spicerack::Configurable] A module that extends Spicerack::Configurable. Defaults to the module +YourGem::Configuration+
|
34
|
+
def delegates_to_configuration(config_class = nil)
|
35
|
+
@_configuration_module = config_class || "#{self}::#{DEFAULT_CONFIGURATION_MODULE_NAME}".constantize
|
36
|
+
end
|
37
|
+
|
38
|
+
def _configuration_module
|
39
|
+
raise NoMethodError, "Configuration not set up for #{self}. Did you forget to call delegates_to_configuration?" if @_configuration_module.nil?
|
40
|
+
|
41
|
+
@_configuration_module
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Directive
|
4
|
+
class ConfigObject < Spicerack::InputObject
|
5
|
+
include Singleton
|
6
|
+
include ActiveModel::AttributeAssignment
|
7
|
+
|
8
|
+
alias_method :assign, :assign_attributes
|
9
|
+
|
10
|
+
RESERVED_WORDS = %i[config_eval].freeze
|
11
|
+
|
12
|
+
class_attribute :_nested_options, instance_writer: false, default: []
|
13
|
+
class_attribute :_nested_builders, instance_writer: false, default: {}
|
14
|
+
|
15
|
+
class << self
|
16
|
+
private
|
17
|
+
|
18
|
+
def option(name, *)
|
19
|
+
_ensure_safe_option_name(name)
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
def nested(namespace, &block)
|
25
|
+
_ensure_safe_option_name(namespace)
|
26
|
+
|
27
|
+
nested_config_builder_for(namespace).tap do |builder|
|
28
|
+
builder.instance_exec(&block)
|
29
|
+
|
30
|
+
_nested_options << namespace.to_sym
|
31
|
+
define_method(namespace) do |&nested_configure_block|
|
32
|
+
builder.__send__(:configuration).tap do |nested_config|
|
33
|
+
nested_configure_block.call(nested_config) if nested_configure_block.respond_to?(:call)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def nested_config_builder_for(namespace)
|
40
|
+
_nested_builders[namespace.to_sym] ||= ConfigBuilder.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def _ensure_safe_option_name(name)
|
44
|
+
raise ArgumentError, "#{name.inspect} is reserved and cannot be used at a config option" if name.to_sym.in? RESERVED_WORDS
|
45
|
+
raise ArgumentError, "#{name.inspect} is already in use" if _nested_options.include?(name.to_sym)
|
46
|
+
|
47
|
+
puts "Warning: the config option #{name} is already defined" if _options.include?(name.to_sym) # rubocop:disable Rails/Output
|
48
|
+
end
|
49
|
+
|
50
|
+
def inherited(base)
|
51
|
+
base._nested_options = _nested_options.dup
|
52
|
+
base._nested_builders = _nested_builders.dup
|
53
|
+
super
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"#<#{self.class.superclass} #{super}>"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Directive
|
4
|
+
class Evaluator
|
5
|
+
# @param path [Array<Symbol, String>] A message path for the desired config
|
6
|
+
# @param configuration [Spicerack::Configurable::Reader]
|
7
|
+
def initialize(path, configuration)
|
8
|
+
@path = path
|
9
|
+
@configuration = configuration
|
10
|
+
end
|
11
|
+
|
12
|
+
def read
|
13
|
+
path.inject(configuration, &:public_send)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :path, :configuration
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Directive
|
4
|
+
class Reader
|
5
|
+
include Tablesalt::StringableObject
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
@config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def config_eval(*path)
|
12
|
+
Evaluator.new(path, self)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :config
|
18
|
+
|
19
|
+
def method_missing(method_name, *)
|
20
|
+
name = method_name.to_sym
|
21
|
+
|
22
|
+
return mutex.synchronize { config.public_send(name) } if config._options.map(&:to_sym).include?(name)
|
23
|
+
return config._nested_builders[name].reader if config._nested_builders.key?(name)
|
24
|
+
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def respond_to_missing?(method_name, *)
|
29
|
+
name = method_name.to_sym
|
30
|
+
|
31
|
+
config._options.map(&:to_sym).include?(name) ||
|
32
|
+
config._nested_builders.key?(name) ||
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def mutex
|
37
|
+
@mutex ||= Mutex.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def stringable_attributes
|
41
|
+
config._options + config._nested_options
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "spec_helper/matchers"
|
4
|
+
require_relative "spec_helper/dsl"
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.include(Directive::SpecHelper::Matchers::Global)
|
8
|
+
config.include(Directive::SpecHelper::Matchers::Configuration, type: :configuration)
|
9
|
+
config.include(Directive::SpecHelper::DSL, type: :configuration)
|
10
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "matchers"
|
4
|
+
|
5
|
+
module Directive
|
6
|
+
module SpecHelper
|
7
|
+
module DSL
|
8
|
+
def nested_config_option(config_name, &block)
|
9
|
+
in_nested_config_stack(config_name) do |nested_stack|
|
10
|
+
describe(config_name.to_s, caller: caller) do
|
11
|
+
subject { nested_config }
|
12
|
+
|
13
|
+
let(:parent_config) { parent_config_for_nested(nested_stack) }
|
14
|
+
let(:nested_config) { parent_config.public_send(nested_stack.last) }
|
15
|
+
|
16
|
+
it "defines nested config object #{config_name}" do
|
17
|
+
expect(parent_config).to respond_to config_name
|
18
|
+
expect(parent_config._nested_options).to include config_name.to_sym
|
19
|
+
expect(parent_config.public_send(config_name)).to be_a Spicerack::Configurable::ConfigObject
|
20
|
+
end
|
21
|
+
|
22
|
+
instance_eval(&block)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.included(base)
|
28
|
+
base.extend DSL
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parent_config_for_nested(nested_stack)
|
35
|
+
@parent_config ||= {}
|
36
|
+
|
37
|
+
@parent_config[nested_stack] ||= described_class.
|
38
|
+
__send__(:_config_builder).
|
39
|
+
__send__(:configuration).
|
40
|
+
yield_self do |config|
|
41
|
+
nested_stack[0..-2].inject(config) do |conf, nested_name|
|
42
|
+
conf.public_send(nested_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def in_nested_config_stack(name)
|
48
|
+
_nested_config_stack << name.to_sym
|
49
|
+
|
50
|
+
yield _nested_config_stack.dup
|
51
|
+
|
52
|
+
_nested_config_stack.pop
|
53
|
+
end
|
54
|
+
|
55
|
+
def _nested_config_stack
|
56
|
+
Thread.current[:nested_config_stack] ||= []
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Directive
|
4
|
+
module SpecHelper
|
5
|
+
module Matchers
|
6
|
+
module Configuration
|
7
|
+
extend ::RSpec::Matchers::DSL
|
8
|
+
end
|
9
|
+
|
10
|
+
module Global
|
11
|
+
extend ::RSpec::Matchers::DSL
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
require_relative "matchers/configuration/define_config_option"
|
18
|
+
require_relative "matchers/global/delegate_config_to"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Directive
|
4
|
+
module SpecHelper
|
5
|
+
module Matchers
|
6
|
+
module Configuration
|
7
|
+
# RSpec matcher to test options of a Configurable class
|
8
|
+
#
|
9
|
+
# class ExampleConfiguration
|
10
|
+
# include Spicerack::Configurable
|
11
|
+
#
|
12
|
+
# option :foo
|
13
|
+
# option :bar, default: :baz
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# RSpec.describe ExampleConfiguration, type: :configuration do
|
17
|
+
# subject { described_class.new }
|
18
|
+
#
|
19
|
+
# it { is_expected.to define_config_option :foo }
|
20
|
+
# it { is_expected.to define_config_option :bar, default: :baz }
|
21
|
+
# end
|
22
|
+
define :define_config_option do |option, default: nil|
|
23
|
+
description { "define config option #{option.inspect}" }
|
24
|
+
failure_message { "expected #{@obj} to define config option #{option.inspect} with default #{default.inspect}" }
|
25
|
+
|
26
|
+
match do |obj|
|
27
|
+
@obj = obj
|
28
|
+
|
29
|
+
if obj.is_a? Spicerack::Configurable::ConfigObject
|
30
|
+
expect(obj).to define_option option, default: default
|
31
|
+
else
|
32
|
+
expect(obj).to respond_to :config
|
33
|
+
expect(obj.config.instance_variable_get(:@config)).to be_present
|
34
|
+
expect(obj.config.instance_variable_get(:@config)).to define_option option, default: default
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Directive
|
4
|
+
module SpecHelper
|
5
|
+
module Matchers
|
6
|
+
module Global
|
7
|
+
# RSpec matcher to test options of a Configurable class
|
8
|
+
#
|
9
|
+
# module MyGem
|
10
|
+
# include Spicerack::Configurable::ConfigDelegation
|
11
|
+
# delegates_to_configuration
|
12
|
+
#
|
13
|
+
# class MyGem::Configuration
|
14
|
+
# include Spicerack::Configurable
|
15
|
+
# ...
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# RSpec.describe ExampleConfiguration, type: :configuration do
|
20
|
+
# subject { described_class }
|
21
|
+
#
|
22
|
+
# it { is_expected.to delegate_config_to MyGem::Configuration }
|
23
|
+
# end
|
24
|
+
define :delegate_config_to do |config_module|
|
25
|
+
attr_reader :obj, :config_module
|
26
|
+
|
27
|
+
description { "delegates configuration methods to #{config_module}" }
|
28
|
+
|
29
|
+
failure_message { "expected #{target} to delegate configuration methods to #{config_module}" }
|
30
|
+
failure_message_when_negated { "expected #{target} not to delegate configuration methods to #{config_module}" }
|
31
|
+
|
32
|
+
match_unless_raises do |obj|
|
33
|
+
@obj = obj
|
34
|
+
@config_module = config_module
|
35
|
+
|
36
|
+
stub_configuration_methods
|
37
|
+
|
38
|
+
expect(target.config).to eq config_return_value
|
39
|
+
expect(target.configure).to eq configure_return_value
|
40
|
+
end
|
41
|
+
|
42
|
+
def target
|
43
|
+
obj.is_a?(Module) ? obj : obj.class
|
44
|
+
end
|
45
|
+
|
46
|
+
def stub_configuration_methods
|
47
|
+
allow(config_module).to receive(:config).and_return(config_return_value)
|
48
|
+
allow(config_module).to receive(:configure).and_return(configure_return_value)
|
49
|
+
end
|
50
|
+
|
51
|
+
def config_return_value
|
52
|
+
@config_return_value ||= double
|
53
|
+
end
|
54
|
+
|
55
|
+
def configure_return_value
|
56
|
+
@configure_return_value ||= double
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: directive
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.22.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Allen Rettberg
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.0.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.0.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faker
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.8'
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '2.0'
|
51
|
+
type: :development
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.8'
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: pry
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.10.0
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 0.10.0
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: pry-nav
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 0.2.4
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 0.2.4
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rake
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '10.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '10.0'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: rspec
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '3.0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '3.0'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: shoulda-matchers
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - '='
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 4.0.1
|
124
|
+
type: :development
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 4.0.1
|
131
|
+
description: An easy, self-documenting gem configuration framework
|
132
|
+
email:
|
133
|
+
- allen.rettberg@freshly.com
|
134
|
+
executables: []
|
135
|
+
extensions: []
|
136
|
+
extra_rdoc_files: []
|
137
|
+
files:
|
138
|
+
- LICENSE.txt
|
139
|
+
- README.md
|
140
|
+
- lib/directive.rb
|
141
|
+
- lib/directive/config_builder.rb
|
142
|
+
- lib/directive/config_builder/double_configure.rb
|
143
|
+
- lib/directive/config_delegation.rb
|
144
|
+
- lib/directive/config_object.rb
|
145
|
+
- lib/directive/evaluator.rb
|
146
|
+
- lib/directive/reader.rb
|
147
|
+
- lib/directive/spec_helper.rb
|
148
|
+
- lib/directive/spec_helper/dsl.rb
|
149
|
+
- lib/directive/spec_helper/matchers.rb
|
150
|
+
- lib/directive/spec_helper/matchers/configuration/define_config_option.rb
|
151
|
+
- lib/directive/spec_helper/matchers/global/delegate_config_to.rb
|
152
|
+
- lib/directive/version.rb
|
153
|
+
homepage: https://github.com/Freshly/spicerack/tree/master/directive
|
154
|
+
licenses:
|
155
|
+
- MIT
|
156
|
+
metadata:
|
157
|
+
homepage_uri: https://github.com/Freshly/spicerack/tree/master/directive
|
158
|
+
source_code_uri: https://github.com/Freshly/spicerack/blob/master/directive
|
159
|
+
changelog_uri: https://github.com/Freshly/spicerack/blob/master/directive/CHANGELOG.md
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements: []
|
175
|
+
rubygems_version: 3.0.3
|
176
|
+
signing_key:
|
177
|
+
specification_version: 4
|
178
|
+
summary: Gem configuration made simple
|
179
|
+
test_files: []
|