hinge 0.1.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/.gitignore +9 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/README.md +60 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/hinge.gemspec +24 -0
- data/lib/hinge.rb +8 -0
- data/lib/hinge/resolver.rb +95 -0
- data/lib/hinge/version.rb +3 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 674762229cddb1ad8dc8dc7ed1efcb6efaf1736e
|
4
|
+
data.tar.gz: eba6a6ac27016e695befd31b4e99cddd266a31da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7c9b6045fce65cb1781703d79a737fabfa71fb5dc708962cc27f45033ac902b87c9040f9b9d4821ab37fcbd1d300dac6e5a029a3001c20ca9ce23a46ea61cd82
|
7
|
+
data.tar.gz: cee48df84aeba75c3641dbfc00f37b3618b12fce607e18bc46b5f3f69f3fc4735f897c1cdbff1e9dfc85f8d30be0e41299ea63f4c1eb8fb8fa303649b6fe44b2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Hinge
|
2
|
+
|
3
|
+
Hinge is a trivial dependency resolver. With Hinge, you can write down object dependencies
|
4
|
+
in a linear fashion to have a simple overview of them.
|
5
|
+
|
6
|
+
```
|
7
|
+
class Deps
|
8
|
+
# This method implements building a logger. It has no dependencies.
|
9
|
+
def build_logger
|
10
|
+
Logger.new($stdout)
|
11
|
+
end
|
12
|
+
|
13
|
+
# This method builds an instance of the `Processor` class you want to use,
|
14
|
+
# initialized with the logger from the previous method.
|
15
|
+
# This matching is done by the name of the parameter!
|
16
|
+
def build_processor(logger)
|
17
|
+
Processor.new(logger)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Named parameters can be used as well!
|
21
|
+
def build_runner(logger:)
|
22
|
+
Runner.new(logger)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
deps = Deps.new
|
27
|
+
resolver = Hinge.resolver(deps)
|
28
|
+
processor = resolver.resolve(:processor)
|
29
|
+
resolver.resolve(:processor).equal?(processor) # => true (the same object is returned as last time)
|
30
|
+
```
|
31
|
+
|
32
|
+
And that's all!
|
33
|
+
|
34
|
+
Varying dependencies depending on the environment can now easily be handled in a number of ways,
|
35
|
+
ranging from `if/else` or `case` constructs to inheritance or overriding methods of `Deps`.
|
36
|
+
|
37
|
+
|
38
|
+
## Installation
|
39
|
+
|
40
|
+
Add this line to your application's Gemfile:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
gem 'hinge'
|
44
|
+
```
|
45
|
+
|
46
|
+
And then execute:
|
47
|
+
|
48
|
+
$ bundle
|
49
|
+
|
50
|
+
Or install it yourself as:
|
51
|
+
|
52
|
+
$ gem install hinge
|
53
|
+
|
54
|
+
## Development
|
55
|
+
|
56
|
+
Tests are written in RSpec.
|
57
|
+
|
58
|
+
## Contributing
|
59
|
+
|
60
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/schnittchen/hinge.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hinge"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/hinge.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hinge/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hinge"
|
8
|
+
spec.version = Hinge::VERSION
|
9
|
+
spec.authors = ["Thomas Stratmann"]
|
10
|
+
spec.email = ["thomas.stratmann@9elements.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Trivial dependency resolver using introspection}
|
13
|
+
spec.description = %q{Trivial dependency resolver using introspection}
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
end
|
data/lib/hinge.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Hinge
|
2
|
+
class Resolver
|
3
|
+
Error = Class.new(RuntimeError)
|
4
|
+
UnknownDependency = Class.new(Error)
|
5
|
+
CircularReference = Class.new(Error)
|
6
|
+
|
7
|
+
def initialize(container)
|
8
|
+
@container = container
|
9
|
+
@nodes = {}
|
10
|
+
@resolved = {}
|
11
|
+
end
|
12
|
+
attr_reader :container
|
13
|
+
attr_reader :resolved
|
14
|
+
|
15
|
+
def resolve(name)
|
16
|
+
return resolved[name] if resolved.key?(name)
|
17
|
+
|
18
|
+
raise ArgumentError unless name.is_a?(Symbol)
|
19
|
+
|
20
|
+
node = node(name)
|
21
|
+
populate_node(node)
|
22
|
+
|
23
|
+
resolved[name]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def populate_node(node, history = [])
|
29
|
+
unless node.method
|
30
|
+
complain(UnknownDependency, node, history, "method #{node.method_name} not found")
|
31
|
+
end
|
32
|
+
if history.include?(node.name)
|
33
|
+
complain(CircularReference, node, history, "dependency #{node.name} depends on itself")
|
34
|
+
end
|
35
|
+
|
36
|
+
node.dependency_names.each do |dep_name|
|
37
|
+
populate_node node(dep_name), history + [node.name]
|
38
|
+
end
|
39
|
+
|
40
|
+
ordered_dependent_values =
|
41
|
+
node.dependency_names.map do |dep_name|
|
42
|
+
@resolved[dep_name]
|
43
|
+
end
|
44
|
+
@resolved[node.name] = node.invoke(ordered_dependent_values)
|
45
|
+
end
|
46
|
+
|
47
|
+
def complain(exn, node, history, msg = "")
|
48
|
+
raise exn, "#{msg} (resolving #{[*history, node.name].join(" -> ")})"
|
49
|
+
end
|
50
|
+
|
51
|
+
def node(name)
|
52
|
+
@nodes[name] ||= Node.new(name, container)
|
53
|
+
end
|
54
|
+
|
55
|
+
class Node
|
56
|
+
def initialize(name, container)
|
57
|
+
@name = name
|
58
|
+
@method_name = "build_#{@name}"
|
59
|
+
@method =
|
60
|
+
begin
|
61
|
+
container.method(@method_name)
|
62
|
+
rescue NameError
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
if @method
|
67
|
+
@dependency_names = []
|
68
|
+
@positional_args_count = 0
|
69
|
+
@keyword_arg_names = []
|
70
|
+
|
71
|
+
@method.parameters.each do |kind, argument_name|
|
72
|
+
argument_name = argument_name.to_sym
|
73
|
+
@dependency_names << argument_name
|
74
|
+
case kind
|
75
|
+
when :req
|
76
|
+
@positional_args_count += 1
|
77
|
+
when :keyreq
|
78
|
+
@keyword_arg_names << argument_name
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
attr_reader :name, :method_name, :method, :dependency_names
|
84
|
+
|
85
|
+
def invoke(ordered_values)
|
86
|
+
splat = ordered_values.take(@positional_args_count)
|
87
|
+
splat << Hash[
|
88
|
+
@keyword_arg_names.zip(ordered_values.drop(@positional_args_count))
|
89
|
+
] if @keyword_arg_names.any?
|
90
|
+
@method.call(*splat)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hinge
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Thomas Stratmann
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: Trivial dependency resolver using introspection
|
42
|
+
email:
|
43
|
+
- thomas.stratmann@9elements.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- ".rspec"
|
50
|
+
- Gemfile
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- bin/console
|
54
|
+
- bin/setup
|
55
|
+
- hinge.gemspec
|
56
|
+
- lib/hinge.rb
|
57
|
+
- lib/hinge/resolver.rb
|
58
|
+
- lib/hinge/version.rb
|
59
|
+
homepage:
|
60
|
+
licenses: []
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.5.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Trivial dependency resolver using introspection
|
82
|
+
test_files: []
|