dagwood 0.9.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: 8b26b85e1455bc22180f34ad7947adbd052c3e67f7275cbda7c64cb5267c5fa4
4
+ data.tar.gz: fdd26a9a58377513a33f7702ce66a03550e8507e88d28b6b1227e4cc0ff83008
5
+ SHA512:
6
+ metadata.gz: 6fc45a8a4600bf12007d753f25603446dfe51f921ddb10a8ba8731bae5f0cf69140bb7555b5d4b385df2c724fc568114a03d2a56b940f67e1c138e0e783a7d03
7
+ data.tar.gz: 2059b6ceb2a6f4c6b05f315590392b2482e0e85b2c2b13122bf901e23398c84b6102f3375279f69a66df3c27bd7f5086d65d52c129c2577f21611be607f71304
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,2 @@
1
+ inherit_gem:
2
+ rewind-ruby-style: rubocop.yml
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.2
7
+ before_install: gem install bundler -v 2.0.2
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## [0.9.0]
4
+
5
+ Pre-release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in dagwood.gemspec
6
+ gemspec
@@ -0,0 +1,73 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dagwood (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ansi (1.5.0)
10
+ ast (2.4.1)
11
+ diff-lcs (1.4.4)
12
+ docile (1.3.2)
13
+ parallel (1.19.2)
14
+ parser (2.7.1.4)
15
+ ast (~> 2.4.1)
16
+ rainbow (3.0.0)
17
+ rake (13.0.1)
18
+ regexp_parser (1.7.1)
19
+ rewind-ruby-style (1.0.10)
20
+ rubocop (>= 0.85, < 0.89)
21
+ rexml (3.2.4)
22
+ rspec (3.9.0)
23
+ rspec-core (~> 3.9.0)
24
+ rspec-expectations (~> 3.9.0)
25
+ rspec-mocks (~> 3.9.0)
26
+ rspec-core (3.9.2)
27
+ rspec-support (~> 3.9.3)
28
+ rspec-expectations (3.9.2)
29
+ diff-lcs (>= 1.2.0, < 2.0)
30
+ rspec-support (~> 3.9.0)
31
+ rspec-mocks (3.9.1)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.9.0)
34
+ rspec-support (3.9.3)
35
+ rubocop (0.87.1)
36
+ parallel (~> 1.10)
37
+ parser (>= 2.7.1.1)
38
+ rainbow (>= 2.2.2, < 4.0)
39
+ regexp_parser (>= 1.7)
40
+ rexml
41
+ rubocop-ast (>= 0.1.0, < 1.0)
42
+ ruby-progressbar (~> 1.7)
43
+ unicode-display_width (>= 1.4.0, < 2.0)
44
+ rubocop-ast (0.3.0)
45
+ parser (>= 2.7.1.4)
46
+ ruby-progressbar (1.10.1)
47
+ simplecov (0.19.0)
48
+ docile (~> 1.1)
49
+ simplecov-html (~> 0.11)
50
+ simplecov-console (0.7.2)
51
+ ansi
52
+ simplecov
53
+ terminal-table
54
+ simplecov-html (0.12.2)
55
+ terminal-table (1.8.0)
56
+ unicode-display_width (~> 1.1, >= 1.1.1)
57
+ unicode-display_width (1.7.0)
58
+
59
+ PLATFORMS
60
+ ruby
61
+
62
+ DEPENDENCIES
63
+ bundler (= 2.0.1)
64
+ dagwood!
65
+ rake (~> 13.0)
66
+ rewind-ruby-style
67
+ rspec (~> 3.9.0)
68
+ rubocop (~> 0.87.0)
69
+ simplecov (~> 0.19)
70
+ simplecov-console (~> 0.4)
71
+
72
+ BUNDLED WITH
73
+ 2.0.1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Rewind
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,97 @@
1
+ # 🥪 Dagwood
2
+
3
+ Dagwood allows you to determine the resolution order of dependencies, using a [topologically sorted directed acyclic graph](https://en.wikipedia.org/wiki/Topological_sorting) (DAG - get it?).
4
+
5
+ What does this mean? Let's use an example. When making a sandwich, it is important to follow a specific order of operations. We know that the bread needs to be sliced before we can put the mustard on it, just like we know there is no point closing the sandwich before putting the smoked meat in it. By stating all these dependencies (e.g. `add_mustard` depends on `slice_bread`), we can determine the full, correct order in which we should make our sandwich. We call this a `DependencyGraph`.
6
+
7
+ See the features listed below for ways in which we can use this information.
8
+
9
+ Dagwood might be useful for scheduling Sidekiq jobs to run in a specific order, determining the order in which software packages must be installed on a server, figuring out how long a project might take based on which steps need to be completed first, and many more use cases.
10
+
11
+ #### Features:
12
+ **Serial ordering of dependencies**
13
+
14
+ The basic case. Determine the order of dependencies, one item at a time. The `order` method returns an array of dependencies, in the order they need to be resolved.
15
+ ```ruby
16
+ graph = Dagwood::DependencyGraph.new(add_mustard: [:slice_bread], add_smoked_meat: [:slice_bread], close_sandwich: [:add_smoked_meat])
17
+ graph.order
18
+ => [:slice_bread, :add_mustard, :add_smoked_meat, :close_sandwich]
19
+ ```
20
+
21
+ **Parallel ordering of dependencies**
22
+
23
+ Sometimes certain dependencies can be resolved at the same time. For example, a friend is helping you make your sandwich and you can both complete certain steps at the same time. The `parallel_order` method functions very similarly to `order`, except the items in the array are "groups" of dependencies which can be resolved in parallel (in this example, `add_smoked_meat` and `add_mustard` can be done at the same time).
24
+
25
+ ```ruby
26
+ graph = Dagwood::DependencyGraph.new(add_mustard: [:slice_bread], add_smoked_meat: [:slice_bread], close_sandwich: [:add_smoked_meat])
27
+ graph.parallel_order
28
+ => [[:slice_bread], [:add_smoked_meat, :add_mustard], [:close_sandwich]]
29
+ ```
30
+
31
+ **Reverse ordering of dependencies**
32
+
33
+ The `reverse_order` method can be useful in cases where you'd like to apply the opposite order of operations.
34
+
35
+ ```ruby
36
+ graph = Dagwood::DependencyGraph.new(add_mustard: [:slice_bread], add_smoked_meat: [:slice_bread], close_sandwich: [:add_smoked_meat])
37
+ graph.reverse_order
38
+ => [:close_sandwich, :add_smoked_meat, :add_mustard, :slice_bread]
39
+ ```
40
+
41
+ **Subgraphs**
42
+
43
+ Perhaps you only care about what is needed to perform the `add_mustard` operation. The `subgraph` method allows you to grab a slice of the DependencyGraph, based on the given node.
44
+
45
+
46
+ ```ruby
47
+ graph = Dagwood::DependencyGraph.new(add_mustard: [:slice_bread], add_smoked_meat: [:slice_bread], close_sandwich: [:add_smoked_meat])
48
+ subgraph = graph.subgraph :add_mustard
49
+ subgraph.order
50
+ => [:slice_bread, :add_mustard]
51
+ ```
52
+
53
+ **Graph merging**
54
+
55
+ The `merge` method allows you to take two DependencyGraphs and merge them. If your two most beloved restaurants have really good sandwich recipes, perhaps you'd like to attempt creating the Ultimate Sandwich by combining the steps for making both?
56
+
57
+ ```ruby
58
+ recipe1 = Dagwood::DependencyGraph.new(add_mustard: [:slice_bread], add_smoked_meat: [:slice_bread], close_sandwich: [:add_smoked_meat])
59
+ recipe2 = Dagwood::DependencyGraph.new(add_mayo: [:slice_bread], add_turkey: [:slice_bread], close_sandwich: [:add_turkey, :add_pickles])
60
+
61
+ ultimate_recipe = recipe1.merge(recipe2)
62
+ ultimate_recipe.order
63
+ => [:slice_bread, :add_mustard, :add_smoked_meat, :add_pickles, :add_mayo, :add_turkey, :close_sandwich]
64
+ ```
65
+
66
+ ## Installation
67
+
68
+ Add this line to your application's Gemfile:
69
+
70
+ ```ruby
71
+ gem 'dagwood'
72
+ ```
73
+
74
+ or this line to your gem's gemspec:
75
+
76
+ ```ruby
77
+ spec.add_dependency 'dagwood'
78
+ ```
79
+
80
+ And then execute:
81
+
82
+ $ bundle
83
+
84
+ Or install it yourself as:
85
+
86
+ $ gem install dagwood
87
+
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the spec. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ To install this gem onto your local machine, run `bundle exec rake install`.
94
+
95
+ ## Contributing
96
+
97
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/rewindio/dagwood](https://github.com/rewindio/dagwood).
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ begin
6
+ require 'rspec/core/rake_task'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ rescue LoadError # rubocop:disable Lint/SuppressedException
9
+ end
10
+
11
+ task default: :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'dagwood'
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(__FILE__)
@@ -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,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'dagwood/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'dagwood'
9
+ spec.version = Dagwood::VERSION
10
+ spec.authors = ['Rewind.io']
11
+ spec.email = ['team@rewind.io']
12
+
13
+ spec.summary = 'For all your dependency graph needs'
14
+ spec.description = 'Dagwood allows you to create dependency graphs for doing work in series or in parallel, always in the right order.'
15
+ spec.homepage = 'https://github.com/rewindio/dagwood'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|.github|examples)/}) }
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '2.0.1'
24
+ spec.add_development_dependency 'rake', '~> 13.0'
25
+ spec.add_development_dependency 'rewind-ruby-style'
26
+ spec.add_development_dependency 'rspec', '~> 3.9.0'
27
+ spec.add_development_dependency 'rubocop', '~> 0.87.0'
28
+ spec.add_development_dependency 'simplecov', '~> 0.19'
29
+ spec.add_development_dependency 'simplecov-console', '~> 0.4'
30
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dagwood/version'
4
+ require 'dagwood/dependency_graph'
5
+
6
+ module Dagwood
7
+ class Error < StandardError; end
8
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tsort'
4
+
5
+ module Dagwood
6
+ class DependencyGraph
7
+ include TSort
8
+
9
+ attr_reader :dependencies
10
+
11
+ # @param dependencies [Hash]
12
+ # A hash of the form { item1: ['item2', 'item3'], item2: ['item3'], item3: []}
13
+ # would mean that "item1" depends on item2 and item3, item2 depends on item3
14
+ # and item3 has no dependencies. Nil and missing values will be converted to [].
15
+ def initialize(dependencies)
16
+ @dependencies = Hash.new([]).merge(dependencies.transform_values { |v| v.nil? ? [] : v.sort })
17
+ end
18
+
19
+ def order
20
+ @order ||= tsort
21
+ end
22
+
23
+ def reverse_order
24
+ @reverse_order ||= order.reverse
25
+ end
26
+
27
+ # Similar to #order, except this will group items that
28
+ # have the same "priority", thus indicating they can be done
29
+ # in parallel.
30
+ #
31
+ # Same priority means:
32
+ # 1) They have the same exact same sub-dependencies OR
33
+ # 2) B comes after A and all of B's dependencies have been resolved before A
34
+ def parallel_order
35
+ groups = []
36
+ ungrouped_dependencies = order.dup
37
+
38
+ until ungrouped_dependencies.empty?
39
+ # Start this group with the first dependency we haven't grouped yet
40
+ group_starter = ungrouped_dependencies.delete_at(0)
41
+ group = [group_starter]
42
+
43
+ ungrouped_dependencies.each do |ungrouped_dependency|
44
+ same_priority = @dependencies[ungrouped_dependency].all? do |sub_dependency|
45
+ groups.reduce(false) { |found, g| found || g.include?(sub_dependency) }
46
+ end
47
+
48
+ group << ungrouped_dependency if same_priority
49
+ end
50
+
51
+ # Remove depedencies we managed to group
52
+ ungrouped_dependencies -= group
53
+
54
+ groups << group.sort
55
+ end
56
+
57
+ groups
58
+ end
59
+
60
+ # Generate a subgraph starting at the given node
61
+ def subgraph(node)
62
+ return self.class.new({}) unless @dependencies.key? node
63
+
64
+ # Add the given node and its dependencies to our hash
65
+ hash = {}
66
+ hash[node] = @dependencies[node]
67
+
68
+ # For every dependency of the given node, recursively create a subgraph and merge it into our result
69
+ @dependencies[node].each { |dep| hash.merge! subgraph(dep).dependencies }
70
+
71
+ self.class.new hash
72
+ end
73
+
74
+ # Returns a new graph containing all dependencies from this graph and the given graph.
75
+ # If both graphs depend on the same item, but that item's sub-dependencies differ, the
76
+ # resulting graph will depend on the union of both.
77
+ def merge(other)
78
+ all_dependencies = {}
79
+
80
+ (dependencies.keys | other.dependencies.keys).each do |key|
81
+ all_dependencies[key] = dependencies[key] | other.dependencies[key]
82
+ end
83
+
84
+ self.class.new all_dependencies
85
+ end
86
+
87
+ private
88
+
89
+ def tsort_each_child(node, &block)
90
+ @dependencies.fetch(node, []).each(&block)
91
+ end
92
+
93
+ def tsort_each_node(&block)
94
+ @dependencies.each_key(&block)
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dagwood
4
+ VERSION = '0.9.0'
5
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dagwood
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Rewind.io
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-08-25 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: 2.0.1
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rewind-ruby-style
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '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.9.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.9.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.87.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.87.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.19'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.19'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov-console
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.4'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.4'
111
+ description: Dagwood allows you to create dependency graphs for doing work in series
112
+ or in parallel, always in the right order.
113
+ email:
114
+ - team@rewind.io
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - ".rubocop.yml"
122
+ - ".travis.yml"
123
+ - CHANGELOG.md
124
+ - Gemfile
125
+ - Gemfile.lock
126
+ - LICENSE
127
+ - README.md
128
+ - Rakefile
129
+ - bin/console
130
+ - bin/setup
131
+ - dagwood.gemspec
132
+ - lib/dagwood.rb
133
+ - lib/dagwood/dependency_graph.rb
134
+ - lib/dagwood/version.rb
135
+ homepage: https://github.com/rewindio/dagwood
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubygems_version: 3.0.3
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: For all your dependency graph needs
158
+ test_files: []