wolflow 0.0.1

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.
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