deptree 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 230b571654f043a6d6d9124bf235c7d319cba19e
4
+ data.tar.gz: f76179e0f130b7be1cfe834f00783bc608a8571d
5
+ SHA512:
6
+ metadata.gz: c21d14cbc467bfa9fa816b6c0dc941052c51fda67201267cb31133eb3f0192d9b76be16727634b2b8bed79520d01a5f6abe7a9d376d70455fc9b3b2c93014691
7
+ data.tar.gz: 94ed3a54b321ff9de72dc5f1dcbf0bdb365334960a07c03b0a3900fc96f6f75d7eeb5caa070e19bc05cd93044105bc2159d675e1279b337dbce4742f0fd5a607
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: "Ruby"
2
+ script: "bundle exec rake spec"
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.0
7
+ - 2.2.0
8
+ - 2.3.0
9
+ - rbx-2
10
+ - jruby
11
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in deptree.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Andrzej Kajetanowicz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # Easly manage initialization steps in your Ruby application.
2
+
3
+ When building Ruby applications, quite often, your application has to be properly configured before it can be run. Configuration steps might include things like: reading config files, instantiating singleton objects (`Logger` for example), configuring various API clients, establishing connections to databases, message brokers etc. It can become a tedious task if your application is complex and some steps depend on another.
4
+
5
+ **Deptree** is attempting to solve this problem by exposing declarative DSL that allows to define configuration steps, dependencies between them and most importantly, executing those steps without explicitly specifying the order of invocation.
6
+
7
+ ## Usage
8
+
9
+ First, you need to define all the configuration steps (with their dependencies) required by your application:
10
+
11
+ ```ruby
12
+ module Application
13
+ module Dependencies
14
+ extend Deptree::DSL
15
+
16
+ dependency :config_file do
17
+ configure do
18
+ Application.config = YAML.load_file('config.yml')[environment]
19
+ end
20
+ end
21
+
22
+ dependency :logger => :config_file do
23
+ configure do
24
+ Application.logger = Logger.new(Application.config.log_path)
25
+ end
26
+ end
27
+
28
+ dependency :github_client => [:logger, :config_file] do
29
+ configure do
30
+ GithubClient.configure do |config|
31
+ config.host = Application.config.github.host
32
+ config.logger = Application.logger
33
+ end
34
+ end
35
+ end
36
+
37
+ helpers do
38
+ def environment
39
+ ENV['RACK_ENV'] || 'development'
40
+ end
41
+ end
42
+ end
43
+ end
44
+ ```
45
+
46
+ Once you're ready to start your app, invoke all the configuration steps:
47
+
48
+ ```ruby
49
+ #!/usr/bin/env ruby
50
+
51
+ require 'application'
52
+
53
+ Application::Dependencies.configure
54
+ # At this point **ALL** the configuration steps required by your application
55
+ # will have run in correct order
56
+
57
+ # Start your application
58
+ Application.run!
59
+
60
+ ```
61
+
62
+ Alternatively, you can choose to invoke only specific steps:
63
+
64
+ ```ruby
65
+ Application::Dependencies.configure(:logger, :github_client)
66
+ # NOTE: config will be automatically loaded since both (logger and github_cllient) depend on it.
67
+ ```
68
+
69
+ ## How it works
70
+ Each configuration step can be represented as a vertex in a [directed graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) where each dependency between two steps forms an edge. In order to invoke those steps in correct order, we first need to sort those vertices in a [topological order](https://en.wikipedia.org/wiki/Topological_sorting). **Deptree** gem uses a simplified version of *Kahn's* alghoritm to achieve this.
71
+
72
+ ## Installation
73
+
74
+ Add this line to your application's Gemfile:
75
+
76
+ ```ruby
77
+ gem 'deptree'
78
+ ```
79
+
80
+ And then execute:
81
+
82
+ $ bundle
83
+
84
+ Or install it yourself as:
85
+
86
+ $ gem install deptree
87
+
88
+ ## Code status
89
+
90
+ [![Build Status](https://travis-ci.org/kajetanowicz/deptree.svg?branch=master)](https://travis-ci.org/kajetanowicz/deptree)
91
+
92
+ ## Contributing
93
+
94
+ 1. Fork it ( https://github.com/kajetanowicz/deptree/fork )
95
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
96
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
97
+ 4. Push to the branch (`git push origin my-new-feature`)
98
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ rescue LoadError
7
+ end
8
+
9
+ task default: :spec
data/deptree.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 'deptree/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "deptree"
8
+ spec.version = Deptree::VERSION
9
+ spec.authors = ["Andrzej Kajetanowicz"]
10
+ spec.email = ["Andrzej.Kajetanowicz@gmail.com"]
11
+ spec.summary = %q{Easly manage initialization steps in your Ruby application.}
12
+ spec.description = %q{Easly manage initialization steps in your Ruby application.}
13
+ spec.homepage = "https://github.com/kajetanowicz/deptree"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec", "~> 3.4"
24
+ end
data/lib/deptree.rb ADDED
@@ -0,0 +1,14 @@
1
+ require "deptree/version"
2
+ require "deptree/errors"
3
+ require "deptree/dsl"
4
+ require "deptree/registry"
5
+ require "deptree/visitor"
6
+ require "deptree/visitor/kahn"
7
+ require "deptree/dependency/actions"
8
+ require "deptree/dependency/prerequsites_proxy"
9
+ require "deptree/dependency"
10
+ require "deptree/definition"
11
+ require "deptree/arguments_parser"
12
+
13
+ module Deptree
14
+ end
@@ -0,0 +1,32 @@
1
+ module Deptree
2
+ class ArgumentsParser
3
+ attr_reader :name, :prerequisites
4
+
5
+ def initialize(args)
6
+ @args = args
7
+ end
8
+
9
+ def parse!
10
+ fail! if @args.size > 1
11
+ args = @args.first
12
+
13
+ case args
14
+ when String, Symbol
15
+ @name, @prerequisites = args, []
16
+ when Hash
17
+ fail! if args.size != 1
18
+ @name, @prerequisites = args.map { |k, v| [k, Array(v)] }.first
19
+ else
20
+ fail!
21
+ end
22
+
23
+ return self
24
+ end
25
+
26
+ private
27
+
28
+ def fail!
29
+ fail InvalidArgumentError.new(@args)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ module Deptree
2
+ class Definition < BasicObject
3
+
4
+ def self.add(configurable, args, block)
5
+ parser = ArgumentsParser.new(args).parse!
6
+ prerequisites = Dependency::PrerequisitesProxy.new(parser.prerequisites, configurable.registry)
7
+ name = parser.name
8
+
9
+ dependency = Dependency.new(name, prerequisites, configurable.helpers)
10
+ new(dependency).instance_eval(&block)
11
+ configurable.registry.add(dependency.name, dependency)
12
+ end
13
+
14
+ def initialize(dependency)
15
+ @dependency = dependency
16
+ end
17
+
18
+ def method_missing(name, &behaviour)
19
+ @dependency.action(name, &behaviour)
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,28 @@
1
+ module Deptree
2
+ class Dependency
3
+ attr_reader :name, :prerequisites, :actions
4
+
5
+ def initialize(name, prerequisites, helpers)
6
+ @name = name
7
+ @prerequisites = prerequisites
8
+ @helpers = helpers
9
+ @actions = Actions.new(self)
10
+ end
11
+
12
+ def action(name, &behaviour)
13
+ @actions.add(name, behaviour)
14
+ end
15
+
16
+ def execute(name)
17
+ if (action = @actions.find(name))
18
+ action.execute
19
+ end
20
+ end
21
+
22
+ def execution_context
23
+ @execution_context ||= Object.new.tap do |ctx|
24
+ ctx.extend(@helpers)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ module Deptree
2
+ class Dependency
3
+ class Actions
4
+ def initialize(dependency)
5
+ @dependency, @actions = dependency, []
6
+ end
7
+
8
+ def add(name, behaviour)
9
+ fail DuplicateActionError.new(@dependency.name, name) if find(name)
10
+
11
+ action = Action.new(name, behaviour, @dependency.execution_context)
12
+ @actions << action
13
+ action
14
+ end
15
+
16
+ def find(name)
17
+ @actions.find { |a| a.name == name }
18
+ end
19
+
20
+ def size
21
+ @actions.size
22
+ end
23
+ end
24
+
25
+ class Action
26
+ attr_reader :name
27
+
28
+ def initialize(name, behaviour, context)
29
+ @name, @behaviour, @context = name, behaviour, context
30
+ end
31
+
32
+ def execute
33
+ @context.instance_eval(&@behaviour)
34
+ end
35
+
36
+ def ==(other)
37
+ name == other.name
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,17 @@
1
+ module Deptree
2
+ class Dependency
3
+ class PrerequisitesProxy
4
+ include Enumerable
5
+
6
+ def initialize(names, registry)
7
+ @names, @registry = names, registry
8
+ end
9
+
10
+ def each(&block)
11
+ @names.each do |name|
12
+ block.call(@registry.find(name))
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Deptree
2
+ module DSL
3
+
4
+ def dependency(*args, &block)
5
+ Definition.add(self, args, block)
6
+ end
7
+
8
+ def configure(*names)
9
+ Visitor.each(registry.select(*names)) do |dependency|
10
+ dependency.execute(:configure)
11
+ end
12
+ end
13
+
14
+ def helpers(&block)
15
+ @helpers ||= Module.new
16
+ @helpers.module_eval(&block) if block_given?
17
+ @helpers
18
+ end
19
+
20
+ def registry
21
+ @registry ||= Deptree::Registry.new
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module Deptree
2
+ class Error < StandardError; end
3
+
4
+ class InvalidArgumentError < Error
5
+ def initialize(arguments)
6
+ args = arguments.map(&:to_s).join(', ')
7
+ super("Dependency takes either a String or a Hash with a single key-value pair. Got: #{args}")
8
+ end
9
+ end
10
+
11
+ class DuplicateActionError < Error
12
+ def initialize(dependency, action)
13
+ super("Dependency #{dependency} already contains the definition of action #{action}")
14
+ end
15
+ end
16
+
17
+ class DuplicateDependencyError < Error
18
+ def initialize(name)
19
+ super("Dependency #{name} has already been added.")
20
+ end
21
+ end
22
+
23
+ class CircularDependencyError < Error
24
+ def initialize
25
+ super("Circular dependency detected")
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ module Deptree
2
+ class Registry
3
+
4
+ def initialize
5
+ @dependencies = {}
6
+ end
7
+
8
+ def add(name, dependency)
9
+ fail DuplicateDependencyError.new(name) if include?(name)
10
+ @dependencies.store(normalize(name), dependency)
11
+ end
12
+
13
+ def find(name)
14
+ @dependencies.fetch(normalize(name))
15
+ end
16
+
17
+ def select(*names)
18
+ if names.empty?
19
+ @dependencies.values
20
+ else
21
+ names.map { |name| find(name) }
22
+ end
23
+ end
24
+
25
+ def include?(name)
26
+ @dependencies.has_key?(name)
27
+ end
28
+
29
+ private
30
+
31
+ def normalize(name)
32
+ name.to_s
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module Deptree
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ require 'set'
2
+
3
+ module Deptree
4
+ class Visitor
5
+ def self.each(roots, &block)
6
+ new(roots).visit(&block)
7
+ end
8
+
9
+ def initialize(roots)
10
+ @roots = roots
11
+ end
12
+
13
+ def visit(&block)
14
+ sorted.each(&block)
15
+ end
16
+
17
+ private
18
+
19
+ def sorted
20
+ Kahn.new(nodes).topsort
21
+ end
22
+
23
+ def nodes
24
+ queue = @roots.dup
25
+ nodes = Set.new
26
+
27
+ while (node = queue.shift) do
28
+ unless nodes.include?(node)
29
+ nodes.add(node)
30
+ node.prerequisites.each { |child| queue.push(child) }
31
+ end
32
+ end
33
+
34
+ nodes
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ module Deptree
2
+ class Visitor
3
+ class Kahn
4
+ def initialize(nodes)
5
+ @nodes = nodes
6
+ end
7
+
8
+ def topsort
9
+ sorted = []
10
+ queue = incoming.keys.select { |node| incoming[node].zero? }
11
+
12
+ fail CircularDependencyError if queue.empty?
13
+
14
+ while (node = queue.shift) do
15
+ sorted << node
16
+ node.prerequisites.each do |child|
17
+ incoming[child] -= 1
18
+ queue.push(child) if incoming[child].zero?
19
+ end
20
+ end
21
+
22
+ fail CircularDependencyError unless incoming.values.all?(&:zero?)
23
+
24
+ sorted.reverse
25
+ end
26
+
27
+ def incoming
28
+ @incoming||= compute_incoming_edges(@nodes)
29
+ end
30
+
31
+ def compute_incoming_edges(nodes)
32
+ incoming = {}
33
+ nodes.each { |node| incoming[node] = 0 }
34
+
35
+ nodes.each do |node|
36
+ node.prerequisites.each do |child|
37
+ incoming[child] += 1
38
+ end
39
+ end
40
+
41
+ incoming
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ module Deptree
2
+ describe ArgumentsParser do
3
+
4
+ def parse(*args)
5
+ ArgumentsParser.new(args).parse!
6
+ end
7
+
8
+ it 'retrieves correct dependency name' do
9
+ parser = parse('bar')
10
+
11
+ expect(parser.name).to eq('bar')
12
+ expect(parser.prerequisites).to eq([])
13
+ end
14
+
15
+ context 'when the dependency has one prerequisite' do
16
+ it 'retrieves correct name and prerequisites' do
17
+ parser = parse('foo' => 'bar')
18
+
19
+ expect(parser.name).to eq('foo')
20
+ expect(parser.prerequisites).to eq(['bar'])
21
+ end
22
+ end
23
+
24
+ context 'when the dependency has many prerequisites' do
25
+ it 'retrieves correct name and prerequisites' do
26
+ parser = parse('foo' => ['bar', 'baz'])
27
+
28
+ expect(parser.name).to eq('foo')
29
+ expect(parser.prerequisites).to eq(['bar', 'baz'])
30
+ end
31
+ end
32
+
33
+ context 'when the dependency name is a Symbol' do
34
+ it 'retrieves correct name and prerequisites' do
35
+ parser = parse(:foo => ['bar', 'baz'])
36
+
37
+ expect(parser.name).to eq(:foo)
38
+ expect(parser.prerequisites).to eq(['bar', 'baz'])
39
+ end
40
+ end
41
+
42
+ context 'when passing invalid arguments' do
43
+ it 'raises an exception' do
44
+ expect { parse('foo' => 'bar', 'bar' => 'baz') }.
45
+ to raise_error(InvalidArgumentError, /Got: {"foo"=>"bar", "bar"=>"baz"}/)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,32 @@
1
+ module Deptree
2
+ describe Definition do
3
+ describe '.add' do
4
+ let(:configurable) do
5
+ double(
6
+ 'Configurable',
7
+ registry: Registry.new,
8
+ helpers: Module.new
9
+ )
10
+ end
11
+
12
+ it 'returns a dependency' do
13
+ expect(Definition.add(configurable, ['foo'], Proc.new {})).to be_a(Dependency)
14
+ end
15
+
16
+ it 'yields a block within an instance of DefinitionContext' do
17
+ expect { |b| Definition.add(configurable, ['bar'], b) }.to yield_control.once
18
+ end
19
+
20
+ it 'allows to define various actions' do
21
+ block = Proc.new do
22
+ action_one do; end
23
+ action_two do; end
24
+ action_three do; end
25
+ end
26
+ dependency = Definition.add(configurable, ['baz'], block)
27
+
28
+ expect(dependency.actions.size).to eq 3
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ module Deptree
2
+ describe Dependency do
3
+ subject(:dependency) { Dependency.new('foo', [], Module.new) }
4
+
5
+ describe '#action' do
6
+ it 'returns an Action' do
7
+ action = dependency.action(:configure) do; end
8
+
9
+ expect(action).to be_an(Dependency::Action)
10
+ expect(action.name).to eq :configure
11
+ end
12
+
13
+ it 'does not allow to define an action with the same name twice' do
14
+ expect {
15
+ dependency.action(:configure) do; end
16
+ dependency.action(:configure) do; end
17
+ }.to raise_error(DuplicateActionError, /foo.*configure/)
18
+ end
19
+ end
20
+
21
+ describe '#execute' do
22
+ it 'executes an action' do
23
+ expect { |blk|
24
+ dependency.action(:foo, &blk)
25
+ dependency.execute(:foo)
26
+
27
+ }.to yield_control.once
28
+ end
29
+
30
+ it 'executes all actions in the same context' do
31
+ ctx = self
32
+ ctx1 = ctx2 = nil
33
+
34
+ dependency.action(:action1) { ctx1 = self }
35
+ dependency.action(:action2) { ctx2 = self }
36
+
37
+ dependency.execute(:action1)
38
+ dependency.execute(:action2)
39
+
40
+ expect(ctx1).to eql(ctx2)
41
+ expect(ctx).not_to eql(ctx1)
42
+ expect(ctx).not_to eql(ctx2)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,65 @@
1
+ describe Deptree::DSL do
2
+
3
+ let(:host_class) {
4
+ Class.new { extend Deptree::DSL }
5
+ }
6
+
7
+ it 'defines DSL for adding dependencies' do
8
+ expect(host_class).to respond_to(:dependency)
9
+ end
10
+
11
+ it 'defines DSL for configuring dependencies' do
12
+ expect(host_class).to respond_to(:configure)
13
+ end
14
+
15
+ it 'defines DSL for adding helpers' do
16
+ expect(host_class).to respond_to(:helpers)
17
+ end
18
+
19
+ describe 'adding a new dependency' do
20
+ it 'allows to add a new dependency to the Registry' do
21
+ host_class.dependency 'foo' do; end
22
+
23
+ expect(host_class.registry).to include 'foo'
24
+ end
25
+ end
26
+
27
+ describe '.helpers' do
28
+ context 'when called without block' do
29
+ it 'returns a module with helper methods' do
30
+ expect(host_class.helpers).to be_a(Module)
31
+ end
32
+
33
+ it 'returns the same module when called multiple times' do
34
+ expect(host_class.helpers).to eq host_class.helpers
35
+ end
36
+ end
37
+
38
+ context 'when called with block' do
39
+ it 'defines methods in an anonymous module' do
40
+ host_class.helpers do
41
+ def foo
42
+ # ...
43
+ end
44
+
45
+ def bar
46
+ # ...
47
+ end
48
+ end
49
+
50
+ host_class.helpers do
51
+ def baz;
52
+ # ...
53
+ end
54
+ end
55
+
56
+ object = Object.new
57
+ object.extend(host_class.helpers)
58
+
59
+ expect(object).to respond_to(:foo)
60
+ expect(object).to respond_to(:bar)
61
+ expect(object).to respond_to(:baz)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,56 @@
1
+ describe Deptree::Registry do
2
+ let(:registry) { Deptree::Registry.new }
3
+
4
+ before do
5
+ registry.add('dep1', double('Dependency', name: 'Dependency1'))
6
+ registry.add('dep2', double('Dependency', name: 'Dependency2'))
7
+ registry.add('dep3', double('Dependency', name: 'Dependency3'))
8
+ registry.add('dep4', double('Dependency', name: 'Dependency4'))
9
+ end
10
+
11
+ describe '#add' do
12
+ it 'registers a dependency' do
13
+ expect(registry).to_not include('dep_name')
14
+
15
+ registry.add('dep_name', double('Dependency1'))
16
+ expect(registry).to include('dep_name')
17
+ end
18
+
19
+ it 'prevents from adding the same dependency more than once' do
20
+ expect {
21
+ registry.add('dep_name', double('Dependency1'))
22
+ registry.add('dep_name', double('Dependency1'))
23
+
24
+ }.to raise_error(Deptree::DuplicateDependencyError, /dep_name/)
25
+ end
26
+ end
27
+
28
+ describe '#find' do
29
+ it 'finds dependency by name' do
30
+ expect(registry.find('dep1').name).to eq 'Dependency1'
31
+ end
32
+
33
+ it 'accepts symbolized names' do
34
+ expect(registry.find(:dep2).name).to eq 'Dependency2'
35
+ end
36
+
37
+ context 'when trying to find non-existing dependency' do
38
+ it 'raises an exception' do
39
+ expect { registry.find('non-existing') }.
40
+ to raise_error(KeyError, /non-existing/)
41
+ end
42
+ end
43
+ end
44
+
45
+ describe '#select' do
46
+ it 'returns selected dependencies' do
47
+ expect(registry.select('dep1', 'dep3').size).to eq 2
48
+ end
49
+
50
+ context 'when called without any names' do
51
+ it 'returns all registered dependencies' do
52
+ expect(registry.select.size).to eq 4
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,231 @@
1
+ describe '.configure' do
2
+ it 'handles single dependency' do
3
+ called = []
4
+ application = Class.new do
5
+ extend Deptree::DSL
6
+
7
+ dependency :one do
8
+ configure { called << :one }
9
+ end
10
+ end
11
+
12
+ application.configure
13
+ expect(called).to eq [:one]
14
+ end
15
+
16
+ it 'handles multiple un-linked dependencies' do
17
+ called = []
18
+ application = Class.new do
19
+ extend Deptree::DSL
20
+
21
+ dependency :one do
22
+ configure { called << :one }
23
+ end
24
+ dependency :two do
25
+ configure { called << :two }
26
+ end
27
+ dependency :three do
28
+ configure { called << :three }
29
+ end
30
+ end
31
+
32
+ application.configure
33
+ expect(called.sort).to eq [:one, :three, :two]
34
+ end
35
+
36
+ it 'only resolves specified dependencies' do
37
+ called = []
38
+ application = Class.new do
39
+ extend Deptree::DSL
40
+
41
+ dependency :one do
42
+ configure { called << :one }
43
+ end
44
+ dependency :two do
45
+ configure { called << :two }
46
+ end
47
+ dependency :three do
48
+ configure { called << :three }
49
+ end
50
+ end
51
+
52
+ application.configure(:three, :two)
53
+ expect(called.sort).to eq [:three, :two]
54
+ end
55
+
56
+ it 'makes helpers available in the context of configure block' do
57
+ ARGS = []
58
+
59
+ application = Class.new do
60
+ extend Deptree::DSL
61
+
62
+ dependency :one do
63
+ configure do
64
+ foo(:one)
65
+ end
66
+ end
67
+
68
+ dependency :two do
69
+ configure do
70
+ foo(:two)
71
+ end
72
+ end
73
+
74
+ helpers do
75
+ def foo(name)
76
+ ARGS << name
77
+ end
78
+ end
79
+ end
80
+
81
+ application.configure
82
+ expect(ARGS.sort).to eq [:one, :two].sort
83
+ end
84
+
85
+ it 'invokes indirect dependencies' do
86
+ called = []
87
+ application = Class.new do
88
+ extend Deptree::DSL
89
+
90
+ dependency :one => [:two] do
91
+ configure { called << :one }
92
+ end
93
+ dependency :two => :three do
94
+ configure { called << :two }
95
+ end
96
+ dependency :three do
97
+ configure { called << :three }
98
+ end
99
+ end
100
+
101
+ application.configure(:one)
102
+ expect(called.sort).to eq [:one, :three, :two]
103
+ end
104
+
105
+ it 'detects simple circular dependencies' do
106
+ called = []
107
+ application = Class.new do
108
+ extend Deptree::DSL
109
+
110
+ dependency :one =>:two do
111
+ configure { called << :one }
112
+ end
113
+ dependency :two => :one do
114
+ configure { called << :two }
115
+ end
116
+ end
117
+
118
+ expect { application.configure }.
119
+ to raise_error(Deptree::CircularDependencyError)
120
+ end
121
+
122
+ it 'handles 2 ordered dependencies ' do
123
+ called = []
124
+ application = Class.new do
125
+ extend Deptree::DSL
126
+
127
+ dependency :three => :two do
128
+ configure { called << :three }
129
+ end
130
+ dependency :two do
131
+ configure { called << :two }
132
+ end
133
+ end
134
+
135
+ application.configure
136
+ expect(called.index(:two)).to be < called.index(:three)
137
+ end
138
+
139
+ it 'handles 3 ordered dependencies ' do
140
+ called = []
141
+ application = Class.new do
142
+ extend Deptree::DSL
143
+
144
+ dependency :three => :two do
145
+ configure { called << :three }
146
+ end
147
+ dependency :four => :three do
148
+ configure { called << :four }
149
+ end
150
+ dependency :two do
151
+ configure { called << :two }
152
+ end
153
+ end
154
+
155
+ application.configure
156
+ expect(called.index(:two)).to be < called.index(:three)
157
+ expect(called.index(:three)).to be < called.index(:four)
158
+ end
159
+
160
+ it 'handles 6 ordered dependencies ' do
161
+ called = []
162
+ application = Class.new do
163
+ extend Deptree::DSL
164
+
165
+ dependency :five => [:six, :four] do
166
+ configure { called << :five }
167
+ end
168
+ dependency :six do
169
+ configure { called << :six }
170
+ end
171
+ dependency :three => :two do
172
+ configure { called << :three }
173
+ end
174
+ dependency :four => :two do
175
+ configure { called << :four }
176
+ end
177
+ dependency :two do
178
+ configure { called << :two }
179
+ end
180
+ dependency :one => :five do
181
+ configure { called << :one }
182
+ end
183
+ end
184
+
185
+ application.configure
186
+ expect(called.index(:four)).to be < called.index(:five)
187
+ expect(called.index(:five)).to be < called.index(:one)
188
+ expect(called.index(:two)).to be < called.index(:one)
189
+ end
190
+
191
+ it 'detects indirect circular dependencies' do
192
+ application = Class.new do
193
+ extend Deptree::DSL
194
+
195
+ dependency :five => :four do
196
+ configure { called << :five }
197
+ end
198
+ dependency :six => [:five] do
199
+ configure { called << :six }
200
+ end
201
+ dependency :three => :two do
202
+ configure { called << :three }
203
+ end
204
+ dependency :four => :three do
205
+ configure { called << :four }
206
+ end
207
+ dependency :two => :one do
208
+ configure { called << :two }
209
+ end
210
+ dependency :one => :five do
211
+ configure { called << :one }
212
+ end
213
+ end
214
+
215
+ expect { application.configure }.
216
+ to raise_error(Deptree::CircularDependencyError)
217
+ end
218
+
219
+ it 'detects when dependency in linked to itself' do
220
+ application = Class.new do
221
+ extend Deptree::DSL
222
+
223
+ dependency :one => :one do
224
+ configure { called << :one }
225
+ end
226
+ end
227
+
228
+ expect { application.configure }.
229
+ to raise_error(Deptree::CircularDependencyError)
230
+ end
231
+ end
@@ -0,0 +1,98 @@
1
+ require 'deptree'
2
+ # This file was generated by the `rspec --init` command. Conventionally, all
3
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
4
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
5
+ # this file to always be loaded, without a need to explicitly require it in any
6
+ # files.
7
+ #
8
+ # Given that it is always loaded, you are encouraged to keep this file as
9
+ # light-weight as possible. Requiring heavyweight dependencies from this file
10
+ # will add to the boot time of your test suite on EVERY test run, even for an
11
+ # individual file that may not need all of that loaded. Instead, consider making
12
+ # a separate helper file that requires the additional dependencies and performs
13
+ # the additional setup, and require it from the spec files that actually need
14
+ # it.
15
+ #
16
+ # The `.rspec` file also contains a few flags that are not defaults but that
17
+ # users commonly want.
18
+ #
19
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
20
+ RSpec.configure do |config|
21
+ # rspec-expectations config goes here. You can use an alternate
22
+ # assertion/expectation library such as wrong or the stdlib/minitest
23
+ # assertions if you prefer.
24
+ config.expect_with :rspec do |expectations|
25
+ # This option will default to `true` in RSpec 4. It makes the `description`
26
+ # and `failure_message` of custom matchers include text for helper methods
27
+ # defined using `chain`, e.g.:
28
+ # be_bigger_than(2).and_smaller_than(4).description
29
+ # # => "be bigger than 2 and smaller than 4"
30
+ # ...rather than:
31
+ # # => "be bigger than 2"
32
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
33
+ end
34
+
35
+ # rspec-mocks config goes here. You can use an alternate test double
36
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
37
+ config.mock_with :rspec do |mocks|
38
+ # Prevents you from mocking or stubbing a method that does not exist on
39
+ # a real object. This is generally recommended, and will default to
40
+ # `true` in RSpec 4.
41
+ mocks.verify_partial_doubles = true
42
+ end
43
+
44
+ # The settings below are suggested to provide a good initial experience
45
+ # with RSpec, but feel free to customize to your heart's content.
46
+ =begin
47
+ # These two settings work together to allow you to limit a spec run
48
+ # to individual examples or groups you care about by tagging them with
49
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
50
+ # get run.
51
+ config.filter_run :focus
52
+ config.run_all_when_everything_filtered = true
53
+
54
+ # Allows RSpec to persist some state between runs in order to support
55
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
56
+ # you configure your source control system to ignore this file.
57
+ config.example_status_persistence_file_path = "spec/examples.txt"
58
+
59
+ # Limits the available syntax to the non-monkey patched syntax that is
60
+ # recommended. For more details, see:
61
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
62
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
63
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
64
+ config.disable_monkey_patching!
65
+
66
+ # This setting enables warnings. It's recommended, but in some cases may
67
+ # be too noisy due to issues in dependencies.
68
+ config.warnings = true
69
+
70
+ # Many RSpec users commonly either run the entire suite or an individual
71
+ # file, and it's useful to allow more verbose output when running an
72
+ # individual spec file.
73
+ if config.files_to_run.one?
74
+ # Use the documentation formatter for detailed output,
75
+ # unless a formatter has already been configured
76
+ # (e.g. via a command-line flag).
77
+ config.default_formatter = 'doc'
78
+ end
79
+
80
+ # Print the 10 slowest examples and example groups at the
81
+ # end of the spec run, to help surface which specs are running
82
+ # particularly slow.
83
+ config.profile_examples = 10
84
+
85
+ # Run specs in random order to surface order dependencies. If you find an
86
+ # order dependency and want to debug it, you can fix the order by providing
87
+ # the seed, which is printed after each run.
88
+ # --seed 1234
89
+ config.order = :random
90
+
91
+ # Seed global randomization in this process using the `--seed` CLI option.
92
+ # Setting this allows you to use `--seed` to deterministically reproduce
93
+ # test failures related to randomization by passing the same `--seed` value
94
+ # as the one that triggered the failure.
95
+ Kernel.srand config.seed
96
+ =end
97
+ config.order = :random
98
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: deptree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrzej Kajetanowicz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-10 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.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
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
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.4'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.4'
55
+ description: Easly manage initialization steps in your Ruby application.
56
+ email:
57
+ - Andrzej.Kajetanowicz@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - deptree.gemspec
70
+ - lib/deptree.rb
71
+ - lib/deptree/arguments_parser.rb
72
+ - lib/deptree/definition.rb
73
+ - lib/deptree/dependency.rb
74
+ - lib/deptree/dependency/actions.rb
75
+ - lib/deptree/dependency/prerequsites_proxy.rb
76
+ - lib/deptree/dsl.rb
77
+ - lib/deptree/errors.rb
78
+ - lib/deptree/registry.rb
79
+ - lib/deptree/version.rb
80
+ - lib/deptree/visitor.rb
81
+ - lib/deptree/visitor/kahn.rb
82
+ - spec/deptree/arguments_parser_spec.rb
83
+ - spec/deptree/definition_spec.rb
84
+ - spec/deptree/dependency_spec.rb
85
+ - spec/deptree/dsl_spec.rb
86
+ - spec/deptree/registry_spec.rb
87
+ - spec/deptree_spec.rb
88
+ - spec/spec_helper.rb
89
+ homepage: https://github.com/kajetanowicz/deptree
90
+ licenses:
91
+ - MIT
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.5.1
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: Easly manage initialization steps in your Ruby application.
113
+ test_files:
114
+ - spec/deptree/arguments_parser_spec.rb
115
+ - spec/deptree/definition_spec.rb
116
+ - spec/deptree/dependency_spec.rb
117
+ - spec/deptree/dsl_spec.rb
118
+ - spec/deptree/registry_spec.rb
119
+ - spec/deptree_spec.rb
120
+ - spec/spec_helper.rb