wolflow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +239 -0
  4. data/README.md +393 -0
  5. data/lib/wolflow/cycle.rb +67 -0
  6. data/lib/wolflow/dsl.rb +93 -0
  7. data/lib/wolflow/errors.rb +13 -0
  8. data/lib/wolflow/exclusive_choice.rb +106 -0
  9. data/lib/wolflow/extensions.rb +20 -0
  10. data/lib/wolflow/multi_choice.rb +127 -0
  11. data/lib/wolflow/multi_merge.rb +20 -0
  12. data/lib/wolflow/operators/attribute.rb +22 -0
  13. data/lib/wolflow/operators/base.rb +67 -0
  14. data/lib/wolflow/operators/literal.rb +22 -0
  15. data/lib/wolflow/operators/operation.rb +42 -0
  16. data/lib/wolflow/recursion.rb +21 -0
  17. data/lib/wolflow/simple.rb +134 -0
  18. data/lib/wolflow/simple_merge.rb +19 -0
  19. data/lib/wolflow/start.rb +9 -0
  20. data/lib/wolflow/structured_loop.rb +106 -0
  21. data/lib/wolflow/structured_synchronized_merge.rb +45 -0
  22. data/lib/wolflow/synchronization.rb +17 -0
  23. data/lib/wolflow/task.rb +195 -0
  24. data/lib/wolflow/task_spec.rb +114 -0
  25. data/lib/wolflow/version.rb +5 -0
  26. data/lib/wolflow/workflow.rb +82 -0
  27. data/lib/wolflow/workflow_spec.rb +82 -0
  28. data/lib/wolflow.rb +25 -0
  29. data/sig/cycle.rbs +7 -0
  30. data/sig/dsl.rbs +36 -0
  31. data/sig/exclusive_choice.rbs +12 -0
  32. data/sig/multi_choice.rbs +23 -0
  33. data/sig/multi_merge.rbs +6 -0
  34. data/sig/operators/attribute.rbs +11 -0
  35. data/sig/operators/base.rbs +21 -0
  36. data/sig/operators/literal.rbs +15 -0
  37. data/sig/operators/operation.rbs +10 -0
  38. data/sig/operators.rbs +7 -0
  39. data/sig/recursion.rbs +4 -0
  40. data/sig/simple.rbs +22 -0
  41. data/sig/simple_merge.rbs +6 -0
  42. data/sig/start.rbs +4 -0
  43. data/sig/structured_loop.rbs +8 -0
  44. data/sig/structured_synchronized_merge.rbs +6 -0
  45. data/sig/synchronization.rbs +6 -0
  46. data/sig/task.rbs +66 -0
  47. data/sig/task_spec.rbs +52 -0
  48. data/sig/wolflow.rbs +18 -0
  49. data/sig/workflow.rbs +33 -0
  50. data/sig/workflow_spec.rbs +20 -0
  51. metadata +115 -0
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wolflow
4
+ class Workflow
5
+ attr_reader :id, :workflow_spec, :root, :data
6
+
7
+ def initialize(
8
+ workflow_spec:,
9
+ id: object_id,
10
+ root: Task.new(task_spec: workflow_spec.start, workflow: self),
11
+ data: {}
12
+ )
13
+ @id = id
14
+ @workflow_spec = workflow_spec
15
+ @data = data
16
+
17
+ raise Error, "#{root} has no task spec" unless root.task_spec
18
+
19
+ if root.task_spec && @workflow_spec != root.task_spec.workflow_spec
20
+ raise Error, "#{root} is from a different workflow spec"
21
+ end
22
+
23
+ @root = root
24
+ end
25
+
26
+ def each(&)
27
+ @root.each(&)
28
+ end
29
+
30
+ def complete_one
31
+ each do |task|
32
+ next if task.completed?
33
+
34
+ task.complete!
35
+
36
+ return task if task.completed?
37
+ end
38
+ nil
39
+ end
40
+
41
+ def complete_all
42
+ while complete_one do; end
43
+ end
44
+
45
+ def to_hash
46
+ {
47
+ id: @id,
48
+ workflow_spec: @workflow_spec.to_hash,
49
+ tasks: each.map(&:to_hash),
50
+ data: @data.to_hash
51
+ }
52
+ end
53
+
54
+ class << self
55
+ def from_hash(hash)
56
+ workflow_spec = WorkflowSpec.from_hash(hash[:workflow_spec])
57
+
58
+ # @type var tasks: Hash[String, Task]
59
+ tasks = {}
60
+
61
+ hash[:tasks].each do |task_hash|
62
+ task_spec_id = task_hash.delete(:task_spec_id)
63
+ task_hash[:task_spec] = workflow_spec.each.find do |task_spec|
64
+ task_spec.id == task_spec_id
65
+ end || raise(Error, "no task spec found for `#{task_spec_id}`)")
66
+ task = Task.from_hash(task_hash)
67
+ tasks[task.id] = task
68
+ end
69
+
70
+ tasks.each_value do |task|
71
+ task.connects_with(tasks)
72
+ end
73
+
74
+ start_task = tasks.each_value.find do |task|
75
+ task.parents.empty?
76
+ end or raise WorkflowError, "no start task found"
77
+
78
+ new(id: hash[:id], root: start_task, workflow_spec: workflow_spec, data: hash[:data])
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wolflow
4
+ class WorkflowSpec
5
+ attr_reader :id, :start
6
+
7
+ def initialize(id: object_id.to_s, start: Start.new(workflow_spec: self))
8
+ @id = id
9
+ @start = start
10
+ end
11
+
12
+ def connect(*task_specs)
13
+ if @start
14
+ @start.connect(*task_specs)
15
+ else
16
+ start_task = task_specs.first
17
+ if start_task.workflow_spec && start_task.workflow_spec != self
18
+ raise TaskSpecError, "#{start_task} already defined in a workflow spec"
19
+ end
20
+
21
+ start_task.workflow_spec = self
22
+ @start = start_task
23
+ end
24
+
25
+ if block_given?
26
+ yield(*task_specs)
27
+ return self
28
+ end
29
+
30
+ return task_specs.first if task_specs.size <= 1
31
+
32
+ task_specs
33
+ end
34
+
35
+ def each(&blk)
36
+ return unless @start
37
+
38
+ return enum_for(__method__) unless blk
39
+
40
+ blk.call(@start)
41
+
42
+ @start.each_successor([self], &blk)
43
+ end
44
+
45
+ def to_hash
46
+ {
47
+ id: @id,
48
+ task_specs: @start ? @start.to_hash_tree.uniq : []
49
+ }
50
+ end
51
+
52
+ class << self
53
+ def from_hash(hash)
54
+ workflow_spec = new(id: hash[:id], start: nil)
55
+
56
+ # @type var tasks: Hash[String, TaskSpec]
57
+ tasks = {}
58
+
59
+ hash[:task_specs].each do |task_hash|
60
+ raise Error, "no :name found" unless task_hash.key?(:name)
61
+
62
+ cls = TaskSpec.spec_types[task_hash[:name]]
63
+ task_hash[:workflow_spec] = workflow_spec
64
+ task_spec = cls.from_hash(task_hash)
65
+ tasks[task_spec.id] = task_spec
66
+ end
67
+
68
+ tasks.each_value do |task_spec|
69
+ task_spec.connects_with(tasks)
70
+ end
71
+
72
+ start_task = tasks.each_value.find do |spec|
73
+ spec.prev_tasks.empty?
74
+ end or raise WorkflowSpecError, "no start task found"
75
+
76
+ workflow_spec.connect(start_task)
77
+
78
+ workflow_spec
79
+ end
80
+ end
81
+ end
82
+ end
data/lib/wolflow.rb ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "wolflow/version"
4
+
5
+ module Wolflow
6
+ end
7
+
8
+ require_relative "wolflow/errors"
9
+ require_relative "wolflow/extensions"
10
+ require_relative "wolflow/operators/base"
11
+ require_relative "wolflow/workflow_spec"
12
+ require_relative "wolflow/task_spec"
13
+ require_relative "wolflow/simple"
14
+ require_relative "wolflow/simple_merge"
15
+ require_relative "wolflow/synchronization"
16
+ require_relative "wolflow/multi_choice"
17
+ require_relative "wolflow/exclusive_choice"
18
+ require_relative "wolflow/structured_synchronized_merge"
19
+ require_relative "wolflow/multi_merge"
20
+ require_relative "wolflow/cycle"
21
+ require_relative "wolflow/structured_loop"
22
+ require_relative "wolflow/recursion"
23
+ require_relative "wolflow/start"
24
+ require_relative "wolflow/task"
25
+ require_relative "wolflow/workflow"
data/sig/cycle.rbs ADDED
@@ -0,0 +1,7 @@
1
+ module Wolflow
2
+ class Cycle < Simple
3
+ private
4
+
5
+ def reset_task: (Task task) -> void
6
+ end
7
+ end
data/sig/dsl.rbs ADDED
@@ -0,0 +1,36 @@
1
+ module Wolflow
2
+ module DSL
3
+ def self.load!: () -> void
4
+
5
+ module WolflowDSL
6
+ def spec: (String id) ?{ (WorkflowSpec) -> void } -> (WorkflowSpec & WorkflowSpecDSL)
7
+ end
8
+
9
+ module WorkflowSpecDSL
10
+ def connect: (*String ids) { (*simple task_specs) -> void } -> self
11
+ | (*String ids) -> Array[simple]
12
+ | (*TaskSpec task_specs) { (*TaskSpec task_specs) -> void } -> self
13
+ | (*TaskSpec task_specs) -> Array[TaskSpec]
14
+ end
15
+
16
+ module SimpleDSL
17
+ @on_complete_callbacks: Array[^(Task task) -> void]
18
+
19
+ def on_perform: () { (Task task) -> void } -> self
20
+
21
+ def connect: (*String ids) { (*simple task_specs) -> void } -> self
22
+ | (*String ids) -> Array[simple]
23
+ | (*TaskSpec task_specs) { (*TaskSpec task_specs) -> void } -> self
24
+ | (*TaskSpec task_specs) -> Array[TaskSpec]
25
+
26
+ def choose: (*String ids, **untyped) ?{ (choice choice, *simple else_task_specs) -> void } -> Array[simple]# [choice, *simple]
27
+ | (*TaskSpec else_task_specs, **untyped) ?{ (ExclusiveChoice choice, *TaskSpec else_task_specs) -> void } -> Array[TaskSpec]# [ExclusiveChoice, *TaskSpec]
28
+ end
29
+
30
+ module ChoiceDSL
31
+ end
32
+
33
+ type simple = Simple & SimpleDSL
34
+ type choice = ExclusiveChoice & ChoiceDSL
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ module Wolflow
2
+ class ExclusiveChoice < MultiChoice
3
+
4
+ attr_reader else_tasks: Array[TaskSpec]
5
+
6
+ def connect_else: (*TaskSpec task_specs) -> (Array[TaskSpec] | TaskSpec)
7
+
8
+ private
9
+
10
+ def initialize: (?else_tasks: Array[TaskSpec], **untyped) -> void
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Wolflow
2
+ class MultiChoice < TaskSpec
3
+ interface _Call
4
+ def call: (Task task) -> untyped
5
+ end
6
+
7
+ type condition = Operators::Base | (Object & _Call)
8
+
9
+ attr_reader condition_next_tasks: Array[[condition, Array[TaskSpec]]] # Array[[condition, *TaskSpec]]
10
+
11
+ # TODO: remove :reset_task when rbs allows overriding attr_writer in subclass
12
+ attr_writer connects_to: Hash[:reset_task | :condition_next_tasks | :else_tasks, untyped]
13
+
14
+ def connect: (condition condition, *TaskSpec task_specs) { (*TaskSpec tasks) -> void } -> self
15
+ | (condition condition, *TaskSpec task_specs) -> Array[TaskSpec]# [ExclusiveChoice, *TaskSpec]
16
+
17
+ private
18
+
19
+ def initialize: (?condition_next_tasks: Array[TaskSpec], **untyped) -> void
20
+
21
+ def connect_cond: (condition condition, Array[TaskSpec]) -> void
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module Wolflow
2
+ class MultiMerge < Simple
3
+
4
+ def join: (*Simple task_specs) -> self
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ module Wolflow
2
+ module Operators
3
+ class Attribute < Base
4
+ @name: interned
5
+
6
+ private
7
+
8
+ def initialize: (name: interned, **untyped) -> void
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ module Wolflow
2
+ module Operators
3
+ class Base
4
+ @type: String
5
+
6
+ attr_reader self.op_type: String
7
+
8
+ public
9
+
10
+ def self.from_hash: (Hash[Symbol, untyped] hash) -> Hash[Symbol, untyped]
11
+
12
+ def call: (Task task) -> untyped
13
+
14
+ def to_hash: () -> Hash[Symbol, untyped]
15
+
16
+ private
17
+
18
+ def initialize: (?type: String) -> void
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,15 @@
1
+ module Wolflow
2
+ module Operators
3
+ class Literal < Base
4
+ @value: untyped
5
+
6
+ public
7
+
8
+ def call: (*untyped) -> untyped
9
+
10
+ private
11
+
12
+ def initialize: (value: untyped, **untyped) -> void
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,10 @@
1
+ module Wolflow
2
+ module Operators
3
+ class Operation < Base
4
+ @op: interned
5
+ @members: Array[Base]
6
+
7
+ def initialize: (op: interned, members: Array[Base], **untyped) -> void
8
+ end
9
+ end
10
+ end
data/sig/operators.rbs ADDED
@@ -0,0 +1,7 @@
1
+ module Wolflow
2
+ module Operators
3
+ attr_reader self.op_types: Hash[String, singleton(Base)]
4
+
5
+ def self.from_hash: (Hash[Symbol, untyped] hash) -> Base
6
+ end
7
+ end
data/sig/recursion.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Wolflow
2
+ class Recursion < Simple
3
+ end
4
+ end
data/sig/simple.rbs ADDED
@@ -0,0 +1,22 @@
1
+ module Wolflow
2
+ class Simple < TaskSpec
3
+ @next_tasks: Array[TaskSpec]
4
+
5
+ attr_writer connects_to: Array[String]
6
+
7
+ public
8
+
9
+ def choose: (*TaskSpec else_task_specs, **untyped) ?{ (ExclusiveChoice choice, *TaskSpec else_task_specs) -> void } -> Array[TaskSpec]# [ExclusiveChoice, *TaskSpec]
10
+
11
+ def connect: (*TaskSpec task_specs) { (*TaskSpec tasks) -> void } -> self
12
+ | (*TaskSpec task_specs) -> Array[TaskSpec]# [ExclusiveChoice, *TaskSpec]
13
+
14
+ def workflow_spec=: (WorkflowSpec workflow_spec) -> void
15
+
16
+ private
17
+
18
+ def connect_one: (TaskSpec task_spec) -> void
19
+
20
+ def initialize: (?next_tasks: Array[TaskSpec], **untyped) -> void
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ module Wolflow
2
+ class SimpleMerge < Simple
3
+
4
+ def join: (*Simple task_specs) -> self
5
+ end
6
+ end
data/sig/start.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Wolflow
2
+ class Start < Simple
3
+ end
4
+ end
@@ -0,0 +1,8 @@
1
+ module Wolflow
2
+ class StructuredLoop < ExclusiveChoice
3
+
4
+ private
5
+
6
+ def reset_task: (Task task) -> void
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module Wolflow
2
+ class StructuredSynchronizedMerge < Simple
3
+
4
+ def join: (*Simple task_specs) -> self
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Wolflow
2
+ class Synchronization < Simple
3
+
4
+ def join: (*Simple task_specs) -> self
5
+ end
6
+ end
data/sig/task.rbs ADDED
@@ -0,0 +1,66 @@
1
+ module Wolflow
2
+ class Task
3
+ type record = {
4
+ id: String,
5
+ parents: Array[String],
6
+ children: Array[String],
7
+ completed: bool,
8
+ task_spec_id: String?
9
+ }
10
+
11
+ def self.from_hash: (untyped hash) -> instance
12
+
13
+ attr_reader id: String
14
+
15
+ attr_reader task_spec: TaskSpec
16
+
17
+ attr_reader workflow: Workflow?
18
+
19
+ attr_reader completed: bool
20
+
21
+ attr_reader root: Task
22
+
23
+ attr_reader parents: Array[Task]
24
+
25
+ attr_reader children: Array[Task]
26
+
27
+ attr_reader data: Hash[untyped, untyped]
28
+
29
+ public
30
+
31
+ def build_task_tree: () -> void
32
+
33
+ def complete!: () -> void
34
+
35
+ def completed?: () -> bool
36
+
37
+ def connect: (*Task tasks) { (*Task tasks) -> void } -> self
38
+ | (*Task tasks) -> Array[Task]
39
+
40
+ def copy_task_tree: (Integer i) -> void
41
+
42
+ def disconnect: (Task task) -> void
43
+
44
+ def each: (?Array[Task] visited) { (Task task) -> void } -> self
45
+ | (?Array[Task] visited) -> Enumerator[Task, self]
46
+
47
+ def each_parent: (?Array[Task] visited) { (Task task) -> void } -> self
48
+ | (?Array[Task] visited) -> Enumerator[Task, self]
49
+
50
+ def mark_as_complete!: () -> void
51
+
52
+ def reset!: () -> void
53
+
54
+ def to_hash: () -> record
55
+
56
+ def root=: (Task rt) -> void
57
+
58
+ def connects_with: (Hash[String, Task] tasks) -> void
59
+
60
+ private
61
+
62
+ def connect_one: (Task task) -> void
63
+
64
+ def initialize: (?id: String, ?task_spec: TaskSpec, ?workflow: Workflow, ?completed: bool, ?root: Task, ?parents: Array[Task], ?children: Array[Task], ?data: Hash[untyped, untyped]) -> void
65
+ end
66
+ end
data/sig/task_spec.rbs ADDED
@@ -0,0 +1,52 @@
1
+ module Wolflow
2
+ class TaskSpec
3
+ def self.from_hash: (Hash[Symbol, untyped] hash) -> TaskSpec
4
+
5
+ def self.spec_type: () -> String
6
+
7
+ def self.spec_types: () -> Hash[String, singleton(TaskSpec)]
8
+
9
+ attr_reader id: String
10
+ attr_reader name: String
11
+ attr_reader workflow_spec: WorkflowSpec
12
+ attr_reader prev_tasks: Array[TaskSpec]
13
+
14
+ public
15
+
16
+ def on_complete: (Task task) -> void
17
+
18
+ def next_tasks: () -> Array[TaskSpec]
19
+
20
+ def child_of?: (TaskSpec spec) -> bool
21
+
22
+ def each_ancestor: (?Array[TaskSpec] visited) { (TaskSpec spec) -> void } -> void
23
+ | (?Array[TaskSpec] visited) -> ::Enumerator[TaskSpec, void]
24
+
25
+ def each_successor: (?Array[TaskSpec] visited) { (TaskSpec spec) -> void } -> void
26
+ | (?Array[TaskSpec] visited) -> ::Enumerator[TaskSpec, void]
27
+
28
+ def each_child: (Task task) { (Task task) -> void } -> void
29
+ | (Task task) -> ::Enumerator[Task, void]
30
+
31
+ def each_parent: (Task task) { (Task task) -> void } -> void
32
+ | (Task task) -> ::Enumerator[Task, void]
33
+
34
+ def to_hash: () -> Hash[Symbol, untyped]
35
+
36
+ def to_hash_tree: () -> Array[Hash[Symbol, untyped]]
37
+
38
+ def precedes?: (TaskSpec task_spec) -> void
39
+
40
+ def workflow_spec=: (untyped workflow_spec) -> untyped
41
+
42
+ def connects_with: (Hash[String, TaskSpec] tasks) -> void
43
+
44
+ def build_next_tasks: (Task task, ?i: Integer, ?next_tasks: Array[TaskSpec]) -> void
45
+
46
+ private
47
+
48
+ def initialize: (?id: String, ?name: String, ?workflow_spec: WorkflowSpec, ?prev_tasks: Array[TaskSpec]) -> void
49
+
50
+ def predict: (Task task, Array[TaskSpec] next_tasks) -> void
51
+ end
52
+ end
data/sig/wolflow.rbs ADDED
@@ -0,0 +1,18 @@
1
+ module Wolflow
2
+ VERSION: String
3
+
4
+ class Error < StandardError
5
+ end
6
+
7
+ class TaskError < Error
8
+ end
9
+
10
+ class TaskSpecError < Error
11
+ end
12
+
13
+ class WorkflowError < Error
14
+ end
15
+
16
+ class WorkflowSpecError < Error
17
+ end
18
+ end
data/sig/workflow.rbs ADDED
@@ -0,0 +1,33 @@
1
+ module Wolflow
2
+ class Workflow
3
+ type record = {
4
+ id: String,
5
+ workflow_spec: WorkflowSpec::record,
6
+ tasks: Array[Task::record],
7
+ data: Hash[untyped, untyped]
8
+ }
9
+
10
+ attr_reader id: String
11
+
12
+ attr_reader workflow_spec: WorkflowSpec
13
+
14
+ attr_reader root: Task
15
+
16
+ attr_reader data: Hash[untyped, untyped]
17
+
18
+ def self.from_hash: (untyped hash) -> instance
19
+
20
+ def complete_all: () -> untyped
21
+
22
+ def complete_one: () -> Task?
23
+
24
+ def each: () { (Task task) -> void } -> self
25
+ | () -> Enumerator[Task, self]
26
+
27
+ def to_hash: () -> record
28
+
29
+ private
30
+
31
+ def initialize: (workflow_spec: WorkflowSpec, ?id: String, ?root: Task, ?data: Hash[untyped, untyped]) -> void
32
+ end
33
+ end
@@ -0,0 +1,20 @@
1
+ module Wolflow
2
+ class WorkflowSpec
3
+ type record = { id: String, task_specs: Array[Array[Hash[Symbol, untyped]]] }
4
+
5
+ attr_reader id: String
6
+
7
+ attr_reader start: Simple?
8
+
9
+ def self.from_hash: (untyped hash) -> instance
10
+
11
+ def connect: (*TaskSpec task_specs) { (*TaskSpec task_specs) -> void } -> self
12
+ | (*TaskSpec task_specs) -> Array[TaskSpec]
13
+
14
+ def to_hash: () -> record
15
+
16
+ private
17
+
18
+ def initialize: (?id: String, ?start: Simple?) -> void
19
+ end
20
+ end