dry-core 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []