dry-core 0.4.7

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.
@@ -0,0 +1,76 @@
1
+ require 'concurrent/array'
2
+
3
+ module Dry
4
+ module Core
5
+ # An implementation of descendants tracker, heavily inspired
6
+ # by the descendants_tracker gem.
7
+ #
8
+ # @example
9
+ #
10
+ # class Base
11
+ # extend Dry::Core::DescendantsTracker
12
+ # end
13
+ #
14
+ # class A < Base
15
+ # end
16
+ #
17
+ # class B < Base
18
+ # end
19
+ #
20
+ # class C < A
21
+ # end
22
+ #
23
+ # Base.descendants # => [C, B, A]
24
+ # A.descendants # => [C]
25
+ # B.descendants # => []
26
+ #
27
+ module DescendantsTracker
28
+ class << self
29
+ # @api private
30
+ def setup(target)
31
+ target.instance_variable_set(:@descendants, Concurrent::Array.new)
32
+ end
33
+
34
+ private
35
+
36
+ # @api private
37
+ def extended(base)
38
+ super
39
+
40
+ DescendantsTracker.setup(base)
41
+ end
42
+ end
43
+
44
+ # Return the descendants of this class
45
+ #
46
+ # @example
47
+ # descendants = Parent.descendants
48
+ #
49
+ # @return [Array<Class>]
50
+ #
51
+ # @api public
52
+ attr_reader :descendants
53
+
54
+ protected
55
+
56
+ # @api private
57
+ def add_descendant(descendant)
58
+ ancestor = superclass
59
+ if ancestor.respond_to?(:add_descendant, true)
60
+ ancestor.add_descendant(descendant)
61
+ end
62
+ descendants.unshift(descendant)
63
+ end
64
+
65
+ private
66
+
67
+ # @api private
68
+ def inherited(descendant)
69
+ super
70
+
71
+ DescendantsTracker.setup(descendant)
72
+ add_descendant(descendant)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,11 @@
1
+ module Dry
2
+ module Core
3
+ class InvalidClassAttributeValue < StandardError
4
+ def initialize(name, value)
5
+ super(
6
+ "Value #{value.inspect} is invalid for class attribute #{name.inspect}"
7
+ )
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,61 @@
1
+ require 'set'
2
+
3
+ module Dry
4
+ module Core
5
+ # Define extensions that can be later enabled by the user.
6
+ #
7
+ # @example
8
+ #
9
+ # class Foo
10
+ # extend Dry::Core::Extensions
11
+ #
12
+ # register_extension(:bar) do
13
+ # def bar; :bar end
14
+ # end
15
+ # end
16
+ #
17
+ # Foo.new.bar # => NoMethodError
18
+ # Foo.load_extensions(:bar)
19
+ # Foo.new.bar # => :bar
20
+ #
21
+ module Extensions
22
+ # @api private
23
+ def self.extended(obj)
24
+ super
25
+ obj.instance_variable_set(:@__available_extensions__, {})
26
+ obj.instance_variable_set(:@__loaded_extensions__, Set.new)
27
+ end
28
+
29
+ # Register an extension
30
+ #
31
+ # @param [Symbol] name extension name
32
+ # @yield extension block. This block guaranteed not to be called more than once
33
+ def register_extension(name, &block)
34
+ @__available_extensions__[name] = block
35
+ end
36
+
37
+ # Whether an extension is available
38
+ #
39
+ # @param [Symbol] name extension name
40
+ # @return [Boolean] Extension availability
41
+ def available_extension?(name)
42
+ @__available_extensions__.key?(name)
43
+ end
44
+
45
+ # Enables specified extensions. Already enabled extensions remain untouched
46
+ #
47
+ # @param [Array<Symbol>] extensions list of extension names
48
+ def load_extensions(*extensions)
49
+ extensions.each do |ext|
50
+ block = @__available_extensions__.fetch(ext) do
51
+ raise ArgumentError, "Unknown extension: #{ext.inspect}"
52
+ end
53
+ unless @__loaded_extensions__.include?(ext)
54
+ block.call
55
+ @__loaded_extensions__ << ext
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,138 @@
1
+ module Dry
2
+ module Core
3
+ # Helper module providing thin interface around an inflection backend.
4
+ module Inflector
5
+ # List of supported backends
6
+ BACKENDS = {
7
+ activesupport: [
8
+ 'active_support/inflector',
9
+ proc { ::ActiveSupport::Inflector }
10
+ ],
11
+ dry_inflector: [
12
+ 'dry/inflector',
13
+ proc { Dry::Inflector.new }
14
+ ],
15
+ inflecto: [
16
+ 'inflecto',
17
+ proc { ::Inflecto }
18
+ ]
19
+ }.freeze
20
+
21
+ # Try to activate a backend
22
+ #
23
+ # @api private
24
+ def self.realize_backend(path, backend_factory)
25
+ require path
26
+ rescue LoadError
27
+ nil
28
+ else
29
+ backend_factory.call
30
+ end
31
+
32
+ # Set up first available backend
33
+ #
34
+ # @api private
35
+ def self.detect_backend
36
+ BACKENDS.inject(nil) do |backend, (_, (path, factory))|
37
+ backend || realize_backend(path, factory)
38
+ end || raise(LoadError,
39
+ "No inflector library could be found: "\
40
+ "please install either the `inflecto` or `activesupport` gem.")
41
+ end
42
+
43
+ # Set preferred backend
44
+ #
45
+ # @param [Symbol] name backend name (:activesupport or :inflecto)
46
+ def self.select_backend(name = nil)
47
+ if name && !BACKENDS.key?(name)
48
+ raise NameError, "Invalid inflector library selection: '#{name}'"
49
+ end
50
+ @inflector = name ? realize_backend(*BACKENDS[name]) : detect_backend
51
+ end
52
+
53
+ # Inflector accessor. Lazily initializes a backend
54
+ #
55
+ # @api private
56
+ def self.inflector
57
+ defined?(@inflector) ? @inflector : select_backend
58
+ end
59
+
60
+ # Transform string to camel case
61
+ #
62
+ # @example
63
+ # Dry::Core::Inflector.camelize('foo_bar') # => 'FooBar'
64
+ #
65
+ # @param [String] input input string
66
+ # @return Transformed string
67
+ def self.camelize(input)
68
+ inflector.camelize(input)
69
+ end
70
+
71
+ # Transform string to snake case
72
+ #
73
+ # @example
74
+ # Dry::Core::Inflector.underscore('FooBar') # => 'foo_bar'
75
+ #
76
+ # @param [String] input input string
77
+ # @return Transformed string
78
+ def self.underscore(input)
79
+ inflector.underscore(input)
80
+ end
81
+
82
+ # Get a singlular form of a word
83
+ #
84
+ # @example
85
+ # Dry::Core::Inflector.singularize('chars') # => 'char'
86
+ #
87
+ # @param [String] input input string
88
+ # @return Transformed string
89
+ def self.singularize(input)
90
+ inflector.singularize(input)
91
+ end
92
+
93
+ # Get a plural form of a word
94
+ #
95
+ # @example
96
+ # Dry::Core::Inflector.pluralize('string') # => 'strings'
97
+ #
98
+ # @param [String] input input string
99
+ # @return Transformed string
100
+ def self.pluralize(input)
101
+ inflector.pluralize(input)
102
+ end
103
+
104
+ # Remove namespaces from a constant name
105
+ #
106
+ # @example
107
+ # Dry::Core::Inflector.demodulize('Deeply::Nested::Name') # => 'Name'
108
+ #
109
+ # @param [String] input input string
110
+ # @return Unnested constant name
111
+ def self.demodulize(input)
112
+ inflector.demodulize(input)
113
+ end
114
+
115
+ # Get a constant value by its name
116
+ #
117
+ # @example
118
+ # Dry::Core::Inflector.constantize('Foo::Bar') # => Foo::Bar
119
+ #
120
+ # @param [String] input input constant name
121
+ # @return Constant value
122
+ def self.constantize(input)
123
+ inflector.constantize(input)
124
+ end
125
+
126
+ # Transform a file path to a constant name
127
+ #
128
+ # @example
129
+ # Dry::Core::Inflector.classify('foo/bar') # => 'Foo::Bar'
130
+ #
131
+ # @param [String] input input string
132
+ # @return Constant name
133
+ def self.classify(input)
134
+ inflector.classify(input)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,68 @@
1
+ module Dry
2
+ module Core
3
+ module Memoizable
4
+ MEMOIZED_HASH = {}.freeze
5
+
6
+ module ClassInterface
7
+ def memoize(*names)
8
+ prepend(Memoizer.new(self, names))
9
+ end
10
+
11
+ def new(*)
12
+ obj = super
13
+ obj.instance_variable_set(:'@__memoized__', MEMOIZED_HASH.dup)
14
+ obj
15
+ end
16
+ end
17
+
18
+ def self.included(klass)
19
+ super
20
+ klass.extend(ClassInterface)
21
+ end
22
+
23
+ attr_reader :__memoized__
24
+
25
+ # @api private
26
+ class Memoizer < Module
27
+ attr_reader :klass
28
+ attr_reader :names
29
+
30
+ # @api private
31
+ def initialize(klass, names)
32
+ @names = names
33
+ @klass = klass
34
+ define_memoizable_names!
35
+ end
36
+
37
+ private
38
+
39
+ # @api private
40
+ def define_memoizable_names!
41
+ names.each do |name|
42
+ meth = klass.instance_method(name)
43
+
44
+ if meth.parameters.size > 0
45
+ define_method(name) do |*args|
46
+ name_with_args = :"#{name}_#{args.hash}"
47
+
48
+ if __memoized__.key?(name_with_args)
49
+ __memoized__[name_with_args]
50
+ else
51
+ __memoized__[name_with_args] = super(*args)
52
+ end
53
+ end
54
+ else
55
+ define_method(name) do
56
+ if __memoized__.key?(name)
57
+ __memoized__[name]
58
+ else
59
+ __memoized__[name] = super()
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ module Dry
2
+ module Core
3
+ VERSION = '0.4.7'.freeze
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,126 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-core
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.7
5
+ platform: ruby
6
+ authors:
7
+ - Nikita Shilnikov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.12'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: A toolset of small support modules used throughout the dry-rb ecosystem.
70
+ email:
71
+ - fg@flashgordon.ru
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".inch.yml"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - ".travis.yml"
81
+ - CHANGELOG.md
82
+ - CONTRIBUTING.md
83
+ - Gemfile
84
+ - LICENSE.txt
85
+ - README.md
86
+ - Rakefile
87
+ - dry-core.gemspec
88
+ - lib/dry-core.rb
89
+ - lib/dry/core.rb
90
+ - lib/dry/core/cache.rb
91
+ - lib/dry/core/class_attributes.rb
92
+ - lib/dry/core/class_builder.rb
93
+ - lib/dry/core/constants.rb
94
+ - lib/dry/core/deprecations.rb
95
+ - lib/dry/core/descendants_tracker.rb
96
+ - lib/dry/core/errors.rb
97
+ - lib/dry/core/extensions.rb
98
+ - lib/dry/core/inflector.rb
99
+ - lib/dry/core/memoizable.rb
100
+ - lib/dry/core/version.rb
101
+ homepage: https://github.com/dry-rb/dry-core
102
+ licenses:
103
+ - MIT
104
+ metadata:
105
+ allowed_push_host: https://rubygems.org
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: 2.1.0
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.7.6
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: A toolset of small support modules used throughout the dry-rb ecosystem.
126
+ test_files: []