justdi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f0ea82e7185cd276d6b7688f6c4ac8b03bab4618b8fdcffe7760b2bfaedb669c
4
+ data.tar.gz: 7f1b90d774ee9a7c214395ea4868e0ae1f081e9a817d34c9a59a065cc7a650ec
5
+ SHA512:
6
+ metadata.gz: dc9afbe71f5b9fb4947337c6eb3bfcc97729bb8b57f0cba836dea7a09155f306e999185e10d4faf9c3913160784c2c540dd2b6667e13779c82fed1253d74bc8a
7
+ data.tar.gz: 17697521c9b805b2dc7c51909f17cee179f66f5de134ab20263101cfd1bfc9b4e2b7d421a303508be2b783ff26372b59d63369dc9c98e901a39575bdbb1e71ab
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ # IDE
14
+ /.idea/
15
+ /.vscode/*
16
+ !/.vscode/launch.json
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,35 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
11
+
12
+ AllCops:
13
+ TargetRubyVersion: 2.7
14
+
15
+ Style/ClassAndModuleChildren:
16
+ Enabled: true
17
+
18
+ Layout/FirstHashElementIndentation:
19
+ Enabled: false
20
+ Layout/SpaceAroundMethodCallOperator:
21
+ Enabled: true
22
+
23
+ Lint/RaiseException:
24
+ Enabled: true
25
+ Lint/StructNewOverride:
26
+ Enabled: true
27
+
28
+ Style/ExponentialNotation:
29
+ Enabled: true
30
+ Style/HashEachMethods:
31
+ Enabled: true
32
+ Style/HashTransformKeys:
33
+ Enabled: true
34
+ Style/HashTransformValues:
35
+ Enabled: true
@@ -0,0 +1,6 @@
1
+ ---
2
+ language: ruby
3
+ cache: bundler
4
+ rvm:
5
+ - 2.7.1
6
+ before_install: gem install bundler -v 2.1.4
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at dkruban@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [https://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: https://contributor-covenant.org
74
+ [version]: https://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in justdi.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 12.0'
9
+
10
+ gem 'faker', '~> 2.11.0'
11
+ gem 'rspec', '~> 3.0'
12
+ gem 'rspec-mocks', '~> 3.9.1'
13
+
14
+ gem 'rubocop', '~> 0.82.0', require: false
15
+
16
+ group :development, optional: true do
17
+ gem 'debase', require: false
18
+ gem 'ruby-debug-ide', require: false
19
+ end
@@ -0,0 +1,66 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ justdi (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ concurrent-ruby (1.1.6)
11
+ debase (0.2.4.1)
12
+ debase-ruby_core_source (>= 0.10.2)
13
+ debase-ruby_core_source (0.10.9)
14
+ diff-lcs (1.3)
15
+ faker (2.11.0)
16
+ i18n (>= 1.6, < 2)
17
+ i18n (1.8.2)
18
+ concurrent-ruby (~> 1.0)
19
+ jaro_winkler (1.5.4)
20
+ parallel (1.19.1)
21
+ parser (2.7.1.2)
22
+ ast (~> 2.4.0)
23
+ rainbow (3.0.0)
24
+ rake (12.3.3)
25
+ rexml (3.2.4)
26
+ rspec (3.9.0)
27
+ rspec-core (~> 3.9.0)
28
+ rspec-expectations (~> 3.9.0)
29
+ rspec-mocks (~> 3.9.0)
30
+ rspec-core (3.9.2)
31
+ rspec-support (~> 3.9.3)
32
+ rspec-expectations (3.9.2)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.9.0)
35
+ rspec-mocks (3.9.1)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.9.0)
38
+ rspec-support (3.9.3)
39
+ rubocop (0.82.0)
40
+ jaro_winkler (~> 1.5.1)
41
+ parallel (~> 1.10)
42
+ parser (>= 2.7.0.1)
43
+ rainbow (>= 2.2.2, < 4.0)
44
+ rexml
45
+ ruby-progressbar (~> 1.7)
46
+ unicode-display_width (>= 1.4.0, < 2.0)
47
+ ruby-debug-ide (0.7.2)
48
+ rake (>= 0.8.1)
49
+ ruby-progressbar (1.10.1)
50
+ unicode-display_width (1.7.0)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ debase
57
+ faker (~> 2.11.0)
58
+ justdi!
59
+ rake (~> 12.0)
60
+ rspec (~> 3.0)
61
+ rspec-mocks (~> 3.9.1)
62
+ rubocop (~> 0.82.0)
63
+ ruby-debug-ide
64
+
65
+ BUNDLED WITH
66
+ 2.1.4
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Dmitry Ruban
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.
@@ -0,0 +1,76 @@
1
+ # Justdi
2
+
3
+ Simple DI container
4
+
5
+ You can find the full documentation at [Wiki section](./wiki/readme.md)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'justdi'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ bundle install
19
+ ```
20
+
21
+ Or install it yourself as:
22
+
23
+ ```bash
24
+ gem install justdi
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ### Container as register
30
+
31
+ ```ruby
32
+ container = Justdi::Container.new
33
+
34
+ container.register(:orm).use_class(CustomOrm)
35
+ container.get(:orm) # => #<CustomOrm:0x0000000000000000>
36
+ ```
37
+
38
+ ### IOC approach
39
+
40
+ ```ruby
41
+ class Repository
42
+ extend Justdi::Injectable
43
+ dependency :orm
44
+
45
+ attr_reader :orm
46
+
47
+ def initialize(orm:)
48
+ @orm = orm
49
+ end
50
+
51
+ def last(limit = 10)
52
+ orm.find(limit: limit, order: [:updated_at, :DESC])
53
+ end
54
+ end
55
+
56
+ repo = container.resolve(Repository) # => #<Repository:0x0000000000000000>
57
+ repo.last
58
+ ```
59
+
60
+ ## Development
61
+
62
+ 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.
63
+
64
+ 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).
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/RuBAN-GT/justdi. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/RuBAN-GT/justdi/blob/master/CODE_OF_CONDUCT.md).
69
+
70
+ ## License
71
+
72
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
73
+
74
+ ## Code of Conduct
75
+
76
+ Everyone interacting in the Justdi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/RuBAN-GT/justdi/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'justdi'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require 'pathname'
12
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', Pathname.new(__FILE__).realpath)
13
+
14
+ bundle_binstub = File.expand_path('bundle', __dir__)
15
+
16
+ if File.file?(bundle_binstub)
17
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
18
+ load(bundle_binstub)
19
+ else
20
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
21
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
22
+ end
23
+ end
24
+
25
+ require 'rubygems'
26
+ require 'bundler/setup'
27
+
28
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/justdi/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'justdi'
7
+ spec.version = Justdi::VERSION
8
+ spec.authors = ['Dmitry Ruban']
9
+ spec.email = ['dkruban@gmail.com']
10
+
11
+ spec.summary = 'Simple DI container'
12
+ spec.description = 'Simple DI container'
13
+ spec.homepage = 'https://github.com/RuBAN-GT/justdi'
14
+ spec.license = 'MIT'
15
+
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = spec.homepage
20
+
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(.github|.vscode|wiki|spec)/}) }
23
+ end
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Basic
4
+ require 'justdi/version'
5
+
6
+ require 'justdi/errors/no_dependency_error'
7
+ require 'justdi/errors/unknown_definition_type_error'
8
+ require 'justdi/errors/unknown_destination_error'
9
+
10
+ require 'justdi/resolvers/class_resolver'
11
+ require 'justdi/resolvers/factory_resolver'
12
+ require 'justdi/resolver'
13
+ require 'justdi/register_handler'
14
+
15
+ require 'justdi/definition'
16
+ require 'justdi/definition_store'
17
+ require 'justdi/injectable'
18
+ require 'justdi/container'
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Justdi
6
+ # Generic container
7
+ class Container
8
+ extend Forwardable
9
+
10
+ def_delegators :store, :register, :has?, :empty?, :set, :[]=
11
+
12
+ class << self
13
+ # Resolver module
14
+ # @return [Module<Justdi::Resolver>]
15
+ def resolver
16
+ @resolver ||= Justdi::Resolver
17
+ end
18
+
19
+ # Class for generation
20
+ # @return [Class<Justdi::DefinitionStore>]
21
+ def store
22
+ @store ||= Justdi::DefinitionStore
23
+ end
24
+
25
+ protected
26
+
27
+ attr_writer :resolver, :store
28
+
29
+ alias use_resolver resolver=
30
+ alias use_store store=
31
+ end
32
+
33
+ # Load and resolve dependency
34
+ #
35
+ # @param token [String, Symbol, Numeric, Class]
36
+ # @return [*]
37
+ def get(token)
38
+ store.get(token)&.resolve do |definition|
39
+ self.class.resolver.call definition, self
40
+ end
41
+ end
42
+
43
+ # Short getting syntax
44
+ # @param token [String, Symbol, Numeric, Class]
45
+ # @return [*]
46
+ def [](token)
47
+ get(token)
48
+ end
49
+
50
+ # Resolve dependency
51
+ #
52
+ # @param klass [Class<T>]
53
+ # @return [T]
54
+ def resolve(klass)
55
+ self.class.resolver.class_value(klass, self)
56
+ end
57
+
58
+ # Merge containers
59
+ #
60
+ # @param containers [Array<Justdi::Container>]
61
+ def merge(*containers)
62
+ containers.each { |container| store.merge container.store }
63
+ end
64
+
65
+ # Import definition store
66
+ #
67
+ # @param def_store [Justdi::DefinitionStore]
68
+ # @param overwrite [Boolean]
69
+ def import_store(def_store, overwrite: true)
70
+ return store.merge(def_store) if overwrite
71
+
72
+ def_store.each do |key, value|
73
+ store.set(key, value) unless store.has?(key)
74
+ end
75
+ end
76
+
77
+ protected
78
+
79
+ # Definition store
80
+ # @return [Justdi::DefinitionStore]
81
+ def store
82
+ @store ||= self.class.store.new
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Wrapper over pure container values
5
+ class Definition
6
+ CLASS = :class
7
+ STATIC = :static
8
+ FACTORY = :factory
9
+
10
+ attr_reader :type, :pure_value, :value
11
+
12
+ # @param type [Symbol]
13
+ # @param value [*]
14
+ def initialize(type: STATIC, value:)
15
+ @type = type
16
+ @pure_value = value
17
+ @is_resolved = false
18
+ end
19
+
20
+ # Definition value is resolved
21
+ #
22
+ # @return [Boolean]
23
+ def resolved?
24
+ @is_resolved
25
+ end
26
+
27
+ # Get resolved value using generator
28
+ #
29
+ # @yield [self] generating resolved value
30
+ # @return [*]
31
+ def resolve
32
+ return value if resolved?
33
+
34
+ @value = yield self
35
+ @is_resolved = true
36
+
37
+ @value
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Store of entities definitions
5
+ class DefinitionStore
6
+ class << self
7
+ # Class for stores generation
8
+ # @return [Class<Hash>]
9
+ def general_store
10
+ @general_store ||= Hash
11
+ end
12
+
13
+ # Class for register handlers generation
14
+ # @return [Class<Justdi::RegisterHandler>]
15
+ def register_handler
16
+ @register_handler ||= Justdi::RegisterHandler
17
+ end
18
+
19
+ protected
20
+
21
+ attr_writer :general_store, :register_handler
22
+
23
+ alias use_general_store general_store=
24
+ alias use_register_handler register_handler=
25
+ end
26
+
27
+ # Register definition declaration
28
+ #
29
+ # @example Register a static value
30
+ # "container.register(:example).use_value(42)"
31
+ #
32
+ # @param token [String, Symbol, Numeric, Class]
33
+ # @return [Justdi::RegisterHandler]
34
+ def register(token)
35
+ self.class.register_handler.new { |value| store[token] = value }
36
+ end
37
+
38
+ # Register any dependency declaration manually
39
+ #
40
+ # @param token [String, Symbol, Numeric, Class]
41
+ # @param definition [Hash]
42
+ # @option definition [Symbol] :type
43
+ # @option definition [*] :value
44
+ def set(token, **definition)
45
+ store[token] = Justdi::Definition.new(**definition)
46
+ end
47
+
48
+ # Short definition syntax
49
+ # @param token [String, Symbol, Numeric, Class]
50
+ # @param definition [Hash]
51
+ def []=(token, definition)
52
+ set(token, **definition)
53
+ end
54
+
55
+ # Check existence of dependency
56
+ # @return [Boolean]
57
+ def has?(token)
58
+ store.key? token
59
+ end
60
+
61
+ # Store is empty
62
+ # @return [Boolean]
63
+ def empty?
64
+ store.empty?
65
+ end
66
+
67
+ # Load dependency
68
+ #
69
+ # @param token [String, Symbol, Numeric, Class]
70
+ # @return [*]
71
+ def get(token)
72
+ store[token]
73
+ end
74
+
75
+ # Short getting syntax
76
+ # @param token [String, Symbol, Numeric, Class]
77
+ # @return [*]
78
+ def [](token)
79
+ get(token)
80
+ end
81
+
82
+ # Merge definition stores
83
+ #
84
+ # @param def_store [DefinitionStore]
85
+ def merge(def_store)
86
+ @store = store.merge def_store.all
87
+ end
88
+
89
+ # Return all definitions
90
+ # @return [Hash]
91
+ def all
92
+ store.clone.freeze
93
+ end
94
+
95
+ # Iterates over existing values
96
+ #
97
+ # @yield [token, definition]
98
+ def each
99
+ store.each { |(key, value)| yield key, value }
100
+ end
101
+
102
+ protected
103
+
104
+ # Definition store
105
+ # @return [Hash]
106
+ def store
107
+ @store ||= self.class.general_store.new
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ class NoDependencyError < StandardError
5
+ def initialize(token)
6
+ super "Dependency #{token} is not defined"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ class UnknownDefinitionTypeError < StandardError
5
+ def initialize(definition)
6
+ super "Unknown definition type: #{definition.type}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ class UnknownDestinationError < StandardError
5
+ def initialize(destination)
6
+ super "Unknown destination: #{destination}"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Dependencies configuration
5
+ module Injectable
6
+ # Set dependency
7
+ #
8
+ # @param token [String, Symbol, Numeric, Class]
9
+ def dependency(token, **opts)
10
+ module_dependencies[token] = opts.transform_keys(&:to_sym)
11
+ end
12
+
13
+ # Get all dependencies
14
+ #
15
+ # @return [Hash]
16
+ def dependencies
17
+ module_dependencies.clone.freeze
18
+ end
19
+
20
+ protected
21
+
22
+ def module_dependencies
23
+ @module_dependencies ||= {}
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Utilitary handler registering value definitions
5
+ class RegisterHandler
6
+ attr_reader :callback
7
+
8
+ def initialize(&block)
9
+ @callback = block
10
+ end
11
+
12
+ # Push class definition into container
13
+ #
14
+ # @param klass [Class]
15
+ def use_class(klass)
16
+ callback.call(
17
+ Definition.new(type: Definition::CLASS, value: klass)
18
+ )
19
+ end
20
+
21
+ # Push a static definition into container
22
+ #
23
+ # @param value [*]
24
+ def use_value(value)
25
+ callback.call(
26
+ Definition.new(type: Definition::STATIC, value: value)
27
+ )
28
+ end
29
+
30
+ # Push a factory definition into container
31
+ #
32
+ # @param factory [Proc]
33
+ # @param block [Proc]
34
+ def use_factory(factory = nil, &block)
35
+ factory = block if block_given?
36
+ callback.call(
37
+ Definition.new(type: Definition::FACTORY, value: factory)
38
+ )
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Wrapper over typed definition resolvers
5
+ module Resolver
6
+ class << self
7
+ # Resolve definition using container
8
+ #
9
+ # @param definition [Justdi::Definition]
10
+ # @param container [Justdi::Container]
11
+ # @return [*]
12
+ #
13
+ # @raise [Justdi::UnknownDefinitionTypeError]
14
+ def call(definition, container)
15
+ resolver_method = "#{definition.type}_value"
16
+ unless respond_to? resolver_method
17
+ raise Justdi::UnknownDefinitionTypeError, definition
18
+ end
19
+
20
+ send resolver_method, definition.pure_value, container
21
+ end
22
+
23
+ # Resolve class using container
24
+ #
25
+ # @param value [Class]
26
+ # @param container [Justdi::Container]
27
+ # @return [Object]
28
+ def class_value(value, container)
29
+ ClassResolver.call value, container
30
+ end
31
+
32
+ # Resolve a static value
33
+ #
34
+ # @param value [*]
35
+ # @param container [Justdi::Container]
36
+ # @return [*]
37
+ def static_value(value, _)
38
+ value
39
+ end
40
+
41
+ # Invoke factory injecting container
42
+ #
43
+ # @param factory [Proc]
44
+ # @param container [Justdi::Container]
45
+ # @return [*]
46
+ def factory_value(factory, container)
47
+ FactoryResolver.call factory, container
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Builder of any classes as pure or injectables
5
+ module ClassResolver
6
+ # Destinations enum
7
+ module Destination
8
+ DEFAULT = :initializer
9
+ INITIALIZER = DEFAULT
10
+ CLASS_METHOD = :class_method
11
+ INSTANCE_METHOD = :method
12
+ end
13
+
14
+ class << self
15
+ # Build class by resolving all dependencies
16
+ #
17
+ # @param klass [Class<T>]
18
+ # @param container [Justdi::Container]
19
+ # @return [T]
20
+ #
21
+ # @raise [Justdi::NoDependencyError]
22
+ # @raise [Justdi::UnknownDestinationError]
23
+ def call(klass, container)
24
+ return klass.new unless klass.is_a? Justdi::Injectable
25
+
26
+ safe_klass = Class.new(klass)
27
+ klass_args = klass.dependencies.each_with_object({}) do |(token, metadata), hash|
28
+ resolve_dependency(safe_klass, token, metadata, container, hash)
29
+ end
30
+
31
+ safe_klass.new(**klass_args)
32
+ end
33
+
34
+ protected
35
+
36
+ def resolve_dependency(klass, token, metadata, container, hash)
37
+ raise Justdi::NoDependencyError, token unless container.has?(token)
38
+
39
+ resolve_destination(klass, token, metadata, container.get(token), hash)
40
+ end
41
+
42
+ def resolve_destination(klass, token, metadata, value, store)
43
+ destination = metadata[:destination] || Destination::DEFAULT
44
+
45
+ case destination
46
+ when Destination::DEFAULT, Destination::INITIALIZER
47
+ store[token] = value
48
+ when Destination::INSTANCE_METHOD
49
+ klass.define_method(token) { value }
50
+ when Destination::CLASS_METHOD
51
+ klass.define_singleton_method(token) { value }
52
+ else
53
+ raise Justdi::UnknownDestinationError(destination)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ # Resolver of any factories
5
+ module FactoryResolver
6
+ class << self
7
+ # Build value from factory
8
+ #
9
+ # @param factory [Proc]
10
+ # @param container [Justdi::Container]
11
+ # @return [*]
12
+ def call(factory, container)
13
+ factory.call container
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Justdi
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: justdi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dmitry Ruban
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-07-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Simple DI container
14
+ email:
15
+ - dkruban@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".rspec"
22
+ - ".rubocop.yml"
23
+ - ".travis.yml"
24
+ - CODE_OF_CONDUCT.md
25
+ - Gemfile
26
+ - Gemfile.lock
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - bin/console
31
+ - bin/rspec
32
+ - bin/setup
33
+ - justdi.gemspec
34
+ - lib/justdi.rb
35
+ - lib/justdi/container.rb
36
+ - lib/justdi/definition.rb
37
+ - lib/justdi/definition_store.rb
38
+ - lib/justdi/errors/no_dependency_error.rb
39
+ - lib/justdi/errors/unknown_definition_type_error.rb
40
+ - lib/justdi/errors/unknown_destination_error.rb
41
+ - lib/justdi/injectable.rb
42
+ - lib/justdi/register_handler.rb
43
+ - lib/justdi/resolver.rb
44
+ - lib/justdi/resolvers/class_resolver.rb
45
+ - lib/justdi/resolvers/factory_resolver.rb
46
+ - lib/justdi/version.rb
47
+ homepage: https://github.com/RuBAN-GT/justdi
48
+ licenses:
49
+ - MIT
50
+ metadata:
51
+ homepage_uri: https://github.com/RuBAN-GT/justdi
52
+ source_code_uri: https://github.com/RuBAN-GT/justdi
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.3.0
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.1.2
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: Simple DI container
72
+ test_files: []