trailblazer-activity 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b06f440b4b9b94230fad09bc1b2024ee3297d3b454cae381f01053af65dfe8d
4
- data.tar.gz: 1cd2bae0d2bd7a7f18cacee0d54af07fa4e8783b75b2852816c6882bb5f17b6a
3
+ metadata.gz: cf0052816c7f4a1ed202128395c3c8324a27b139bbfcb163314848eaf5132d2d
4
+ data.tar.gz: 94f9951381191ce8f25816aa27228a17c27c7d271e246c94a8667f315ca2f885
5
5
  SHA512:
6
- metadata.gz: 4844b0b023f93fc12f218932a7d574d96e94684adaab4a186f85cb378cb7e7e92b889bec061af70bc6731d83a56a0fe5ff2651f5f250a0e4d091b454304193e5
7
- data.tar.gz: 3a41f612cecfabdc2e5d0fef50411f5db1a5c6867a8c88ce784e569bfcc2b17b62bfda7a4104b8bbbb68cb157c311961622e7f64b9e5cc811ff09ee1db133296
6
+ metadata.gz: 34b620b89186a2656671005b026f5f3f28151bcae374d058d047175553dd77b3a0c6c23557be84e03a297c9e7e02185b22ac09e4149ba843bed48db3261451a3
7
+ data.tar.gz: 512ac5bdb8af2ef4948c70c2292910e4d406f600c4d5a178afdfc02f8b70497f8b15e86ee297e4199ac4f3716be509986a21a7de33346418b60ce22501ab8930
@@ -6,7 +6,7 @@ jobs:
6
6
  fail-fast: false
7
7
  matrix:
8
8
  # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
9
- ruby: [2.5, 2.6, 2.7, '3.0', 3.1, head, jruby, jruby-head]
9
+ ruby: [2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head, jruby, jruby-head]
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
12
  - uses: actions/checkout@v3
data/CHANGES.md CHANGED
@@ -1,3 +1,68 @@
1
+ # 0.16.0
2
+
3
+ * Remove `Activity#[]`. Please use `activity.to_h[:config]`.
4
+ * Change `Activity#to_h[:nodes]`. This is now a `Schema::Nodes` "hash" that is keyed by task that
5
+ points to `Nodes::Attributes` data structures (a replacement for `Activity::NodeAttributes`).
6
+ This decision reduces logic and improves performance: it turned out that most of the time an introspect
7
+ lookup queries for a task, not ID.
8
+ * Introduce `Activity::Introspect.Nodes()` as a consistent and fast interface for introspection
9
+ and remove `Activity::Introspect::TaskMap`.
10
+ * Remove `Activity::NodeAttributes`.
11
+ * Move `Introspect::Graph` to `trailblazer-developer`. It's a data structure very specific
12
+ to rendering, which is not a part of pure runtime behavior. `Activity::Introspect.Graph()` is now deprecated.
13
+ * `TaskWrap.container_activity_for` now accepts `:id` for setting an ID for the containered activity to
14
+ anything other than `nil`.
15
+ * Re-add `:nodes` to the container activity hash as this provides a consistent way for treating all `Activity`s.
16
+ * Remove `Activity::Config`. This immutable hash interface was used in one place, only, and can easily
17
+ be replaced with `config.merge()`.
18
+ * Add `Introspect::Render`. Please consider this private.
19
+
20
+ ## Intermediate/Implementation
21
+
22
+ * Remove `Intermediate.call`, this is now done through `Intermediate::Compiler`.
23
+ * Introduce `Intermediate::Compiler` which is simplified and is 10% faster.
24
+ * A terminus ("end event") in `Schema::Intermediate` no longer has outputs but an empty array. The
25
+ `stop_event: true` option is still required to mark the `TaskRef` as a terminus.
26
+ * `Schema::Intermediate` now keeps a map `{<terminus ID> => :semantic}` instead of the flat termini ID list and
27
+ one default start event instead of an array. This looks as follows.
28
+
29
+ ```ruby
30
+ Schema::Intermediate.new(
31
+ {
32
+ # ...
33
+ Intermediate::TaskRef("End.success", stop_event: true) => [Inter::Out(:success, nil)]
34
+ },
35
+ ["End.success"],
36
+ [:a] # start
37
+ ```
38
+
39
+ Now becomes
40
+
41
+ ```ruby
42
+ Schema::Intermediate.new(
43
+ {
44
+ # ...
45
+ Intermediate::TaskRef("End.success", stop_event: true) => []
46
+ },
47
+ {"End.success" => :success},
48
+ :a # start
49
+ ```
50
+ * In line with the change in `Intermediate`, the `Implemention` termini `Task`s now don't have outputs anymore.
51
+
52
+ ```ruby
53
+ implementation = {
54
+ # ...
55
+ "End.success" => Schema::Implementation::Task(Activity::End.new(semantic: :success), [], []) # No need for outputs here.
56
+ }
57
+ ```
58
+
59
+ # 0.15.1
60
+
61
+ * Introduce `Extension.WrapStatic()` as a consistent interface for creating wrap_static extensions
62
+ exposing the friendly interface.
63
+ * Deprecate `Extension(merge: ...)` since we have `Extension.WrapStatic` now.
64
+ * Better deprecation warnings for extensions using `Insert` and not the friendly interface.
65
+
1
66
  # 0.15.0
2
67
 
3
68
  * Rename `Circuit::Run` to `Circuit::Runner` for consistency with `TaskWrap::Runner`.
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
- # gem "trailblazer-developer", path: "../trailblazer-developer"
5
- # gem "trailblazer-context", github: "trailblazer/trailblazer-context", branch: "ruby-3"
4
+ # gem "trailblazer-developer", path: "../trailblazer-developer"
5
+ gem "benchmark-ips"
6
+ gem "standard"
@@ -1,154 +1,157 @@
1
1
  module Trailblazer
2
2
  class Activity
3
- # The Adds interface are mechanics to alter sequences/pipelines.
4
- # "one" ADDS structure: {row: ..., insert: [Insert, "id"]}
5
- #
6
- # To work with the instructions provided here, the pipeline structure
7
- # needs to expose {#to_a}.
8
- module Adds
9
- module_function
10
- # @returns Sequence/Pipeline New sequence instance
11
- # @private
12
- def insert_row(pipeline, row:, insert:)
13
- insert_function, *args = insert
3
+ # Developer's docs: https://trailblazer.to/2.1/docs/internals#internals-wiring-api-adds-interface
4
+ #
5
+ # The Adds interface are mechanics to alter sequences/pipelines.
6
+ # "one" ADDS structure: {row: ..., insert: [Insert, "id"]}
7
+ #
8
+ # To work with the instructions provided here, the pipeline structure
9
+ # needs to expose {#to_a}.
10
+ module Adds
11
+ module_function
12
+
13
+ # @returns Sequence/Pipeline New sequence instance
14
+ # @private
15
+ def insert_row(pipeline, row:, insert:)
16
+ insert_function, *args = insert
17
+
18
+ insert_function.(pipeline, row, *args)
19
+ end
14
20
 
15
- insert_function.(pipeline, row, *args)
21
+ # Inserts one or more {Adds} into {pipeline}.
22
+ def apply_adds(pipeline, adds)
23
+ adds.each do |add|
24
+ pipeline = insert_row(pipeline, **add)
16
25
  end
17
26
 
18
- # Inserts one or more {Adds} into {pipeline}.
19
- def apply_adds(pipeline, adds)
20
- adds.each do |add|
21
- pipeline = insert_row(pipeline, **add)
22
- end
23
-
24
- pipeline
25
- end
27
+ pipeline
28
+ end
26
29
 
27
- # @param inserts Array of friendly interface insertions
28
- # def call(pipeline, *inserts)
29
- # adds = build_adds_for_friendly_interface(inserts)
30
-
31
- # Adds.apply_adds(pipeline, adds)
32
- # end
33
-
34
- module FriendlyInterface
35
- # @public
36
- # @return Array of ADDS
37
- #
38
- # Translate a collection of friendly interface to ADDS.
39
- # This is a mini-DSL, if you want.
40
- def self.adds_for(inserts)
41
- inserts.collect do |task, options|
42
- build_adds(task, **options)
43
- end
44
- end
30
+ # @param inserts Array of friendly interface insertions
31
+ # def call(pipeline, *inserts)
32
+ # adds = build_adds_for_friendly_interface(inserts)
45
33
 
46
- # @private
47
- def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: false)
48
- insert, insert_id =
49
- append === false ? [:Prepend, prepend] : [:Append, append]
34
+ # Adds.apply_adds(pipeline, adds)
35
+ # end
50
36
 
51
- {
52
- insert: [Activity::Adds::Insert.method(insert), insert_id],
53
- row: TaskWrap::Pipeline::Row(id, task)
54
- }
37
+ module FriendlyInterface
38
+ # @public
39
+ # @return Array of ADDS
40
+ #
41
+ # Translate a collection of friendly interface to ADDS.
42
+ # This is a mini-DSL, if you want.
43
+ def self.adds_for(inserts)
44
+ inserts.collect do |task, options|
45
+ build_adds(task, **options)
55
46
  end
56
47
  end
57
48
 
58
- # Functions to alter the Sequence/Pipeline by inserting, replacing, or deleting a row.
59
- #
60
- # they don't mutate the data structure but rebuild it, has to respond to {to_a}
61
- #
62
- # These methods are invoked via {Adds.apply_adds} and should never be called directly.
63
- module Insert
64
- module_function
49
+ # @private
50
+ def self.build_adds(task, id:, prepend: "task_wrap.call_task", append: false)
51
+ insert, insert_id =
52
+ (append === false) ? [:Prepend, prepend] : [:Append, append]
53
+
54
+ {
55
+ insert: [Activity::Adds::Insert.method(insert), insert_id],
56
+ row: TaskWrap::Pipeline::Row(id, task)
57
+ }
58
+ end
59
+ end
65
60
 
66
- # Append {new_row} after {insert_id}.
67
- def Append(pipeline, new_row, insert_id=nil)
68
- build_from_ary(pipeline, insert_id) do |ary, index|
69
- index = ary.size if index.nil? # append to end of pipeline.
61
+ # Functions to alter the Sequence/Pipeline by inserting, replacing, or deleting a row.
62
+ #
63
+ # they don't mutate the data structure but rebuild it, has to respond to {to_a}
64
+ #
65
+ # These methods are invoked via {Adds.apply_adds} and should never be called directly.
66
+ module Insert
67
+ module_function
70
68
 
71
- range_before_index(ary, index+1) + [new_row] + Array(ary[index+1..-1])
72
- end
69
+ # Append {new_row} after {insert_id}.
70
+ def Append(pipeline, new_row, insert_id = nil)
71
+ build_from_ary(pipeline, insert_id) do |ary, index|
72
+ index = ary.size if index.nil? # append to end of pipeline.
73
+
74
+ range_before_index(ary, index + 1) + [new_row] + Array(ary[index + 1..-1])
73
75
  end
76
+ end
74
77
 
75
- # Insert {new_row} before {insert_id}.
76
- def Prepend(pipeline, new_row, insert_id=nil)
77
- build_from_ary(pipeline, insert_id) do |ary, index|
78
- index = 0 if index.nil? # Prepend to beginning of pipeline.
78
+ # Insert {new_row} before {insert_id}.
79
+ def Prepend(pipeline, new_row, insert_id = nil)
80
+ build_from_ary(pipeline, insert_id) do |ary, index|
81
+ index = 0 if index.nil? # Prepend to beginning of pipeline.
79
82
 
80
- range_before_index(ary, index) + [new_row] + ary[index..-1]
81
- end
83
+ range_before_index(ary, index) + [new_row] + ary[index..-1]
82
84
  end
85
+ end
83
86
 
84
- def Replace(pipeline, new_row, insert_id)
85
- build_from_ary(pipeline, insert_id) do |ary, index|
86
- range_before_index(ary, index) + [new_row] + ary[index+1..-1]
87
- end
87
+ def Replace(pipeline, new_row, insert_id)
88
+ build_from_ary(pipeline, insert_id) do |ary, index|
89
+ range_before_index(ary, index) + [new_row] + ary[index + 1..-1]
88
90
  end
91
+ end
89
92
 
90
- def Delete(pipeline, _, insert_id)
91
- build_from_ary(pipeline, insert_id) do |ary, index|
92
- range_before_index(ary, index) + ary[index+1..-1]
93
- end
93
+ def Delete(pipeline, _, insert_id)
94
+ build_from_ary(pipeline, insert_id) do |ary, index|
95
+ range_before_index(ary, index) + ary[index + 1..-1]
94
96
  end
97
+ end
95
98
 
96
- # @private
97
- def build(sequence, rows)
98
- sequence.class.new(rows)
99
- end
99
+ # @private
100
+ def build(sequence, rows)
101
+ sequence.class.new(rows)
102
+ end
100
103
 
101
- # @private
102
- def find_index(ary, insert_id)
103
- ary.find_index { |row| row.id == insert_id }
104
- end
104
+ # @private
105
+ def find_index(ary, insert_id)
106
+ ary.find_index { |row| row.id == insert_id }
107
+ end
105
108
 
106
- # Converts the pipeline structure to an array,
107
- # automatically finds the index for {insert_id},
108
- # and calls the user block with the computed values.
109
- #
110
- # Single-entry point, could be named {#call}.
111
- # @private
112
- def apply_on_ary(pipeline, insert_id, raise_index_error: true, &block)
113
- ary = pipeline.to_a
114
-
115
- if insert_id.nil?
116
- index = nil
117
- else
118
- index = find_index(ary, insert_id) # DISCUSS: this only makes sense if there are more than {Append} using this.
119
- raise IndexError.new(pipeline, insert_id) if index.nil? && raise_index_error
120
- end
121
-
122
- _new_ary = yield(ary, index) # call the block.
109
+ # Converts the pipeline structure to an array,
110
+ # automatically finds the index for {insert_id},
111
+ # and calls the user block with the computed values.
112
+ #
113
+ # Single-entry point, could be named {#call}.
114
+ # @private
115
+ def apply_on_ary(pipeline, insert_id, raise_index_error: true, &block)
116
+ ary = pipeline.to_a
117
+
118
+ if insert_id.nil?
119
+ index = nil
120
+ else
121
+ index = find_index(ary, insert_id) # DISCUSS: this only makes sense if there are more than {Append} using this.
122
+ raise IndexError.new(pipeline, insert_id) if index.nil? && raise_index_error
123
123
  end
124
124
 
125
- def build_from_ary(pipeline, insert_id, &block)
126
- new_ary = apply_on_ary(pipeline, insert_id, &block)
125
+ _new_ary = yield(ary, index) # call the block.
126
+ end
127
127
 
128
- # Wrap the sequence/pipeline array into a concrete Sequence/Pipeline.
129
- build(pipeline, new_ary)
130
- end
128
+ def build_from_ary(pipeline, insert_id, &block)
129
+ new_ary = apply_on_ary(pipeline, insert_id, &block)
131
130
 
132
- # Always returns a valid, concat-able array for all indices
133
- # before the {index}.
134
- # @private
135
- def range_before_index(ary, index)
136
- return [] if index == 0
137
- ary[0..index-1]
138
- end
139
- end # Insert
131
+ # Wrap the sequence/pipeline array into a concrete Sequence/Pipeline.
132
+ build(pipeline, new_ary)
133
+ end
140
134
 
141
- class IndexError < ::IndexError
142
- def initialize(sequence, step_id)
143
- valid_ids = sequence.to_a.collect{ |row| row.id.inspect }
135
+ # Always returns a valid, concat-able array for all indices
136
+ # before the {index}.
137
+ # @private
138
+ def range_before_index(ary, index)
139
+ return [] if index == 0
140
+ ary[0..index - 1]
141
+ end
142
+ end # Insert
144
143
 
145
- message = "\n" \
146
- "\e[31m#{step_id.inspect} is not a valid step ID. Did you mean any of these ?\e[0m\n" \
147
- "\e[32m#{valid_ids.join("\n")}\e[0m"
144
+ class IndexError < ::IndexError
145
+ def initialize(sequence, step_id)
146
+ valid_ids = sequence.to_a.collect { |row| row.id.inspect }
148
147
 
149
- super(message)
150
- end
148
+ message = "\n" \
149
+ "\e[31m#{step_id.inspect} is not a valid step ID. Did you mean any of these ?\e[0m\n" \
150
+ "\e[32m#{valid_ids.join("\n")}\e[0m"
151
+
152
+ super(message)
151
153
  end
152
154
  end
155
+ end
153
156
  end
154
157
  end
@@ -22,8 +22,8 @@ module Trailblazer
22
22
  # and returns the return value of the user's callable. By design, it is *not* circuit-interface compatible.
23
23
  class Step
24
24
  def initialize(step, user_proc, **)
25
- @step = step
26
- @user_proc = user_proc
25
+ @step = step
26
+ @user_proc = user_proc
27
27
  end
28
28
 
29
29
  # Translate the circuit interface to the step's step-interface. However,
@@ -91,7 +91,7 @@ module Trailblazer
91
91
  def inspect # TODO: make me private!
92
92
  user_step = @circuit_step.instance_variable_get(:@user_proc) # DISCUSS: to we want Step#to_h?
93
93
 
94
- %{#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(user_step)}>}
94
+ %(#<Trailblazer::Activity::TaskBuilder::Task user_proc=#{Trailblazer::Activity::Introspect.render_task(user_step)}>)
95
95
  end
96
96
  alias_method :to_s, :inspect
97
97
  end
@@ -58,7 +58,7 @@ module Trailblazer
58
58
  task,
59
59
  signal: last_signal,
60
60
  outputs: @map[task],
61
- exec_context: circuit_options[:exec_context], # passed at run-time from DSL
61
+ exec_context: circuit_options[:exec_context] # passed at run-time from DSL
62
62
  )
63
63
  end
64
64
  end
@@ -66,7 +66,11 @@ module Trailblazer
66
66
 
67
67
  # Returns the circuit's components.
68
68
  def to_h
69
- { map: @map, end_events: @stop_events, start_task: @start_task }
69
+ {
70
+ map: @map,
71
+ end_events: @stop_events,
72
+ start_task: @start_task
73
+ }
70
74
  end
71
75
 
72
76
  private
@@ -5,16 +5,15 @@ module Trailblazer
5
5
 
6
6
  def warn(caller_location, message)
7
7
  location = caller_location ? location_for(caller_location) : nil
8
- warning = [location, message].compact.join(" ")
8
+ warning = [location, message].compact.join(" ")
9
9
 
10
- Kernel.warn %{[Trailblazer] #{warning}\n}
10
+ Kernel.warn %([Trailblazer] #{warning}\n)
11
11
  end
12
12
 
13
13
  def location_for(caller_location)
14
- caller_location = caller_location
15
- line_no = caller_location.lineno
14
+ line_no = caller_location.lineno
16
15
 
17
- %{#{caller_location.absolute_path}:#{line_no}}
16
+ %(#{caller_location.absolute_path}:#{line_no})
18
17
  end
19
18
  end
20
19
  end
@@ -0,0 +1,53 @@
1
+ module Trailblazer
2
+ class Activity
3
+ module Introspect
4
+ # @private
5
+ module Render
6
+ module_function
7
+
8
+ def call(activity, **options)
9
+ nodes = Introspect.Nodes(activity)
10
+ circuit_map = activity.to_h[:circuit].to_h[:map]
11
+
12
+ content = nodes.collect do |task, node|
13
+ outgoings = circuit_map[task]
14
+
15
+ conns = outgoings.collect do |signal, target|
16
+ " {#{signal}} => #{inspect_with_matcher(target, **options)}"
17
+ end
18
+
19
+ [
20
+ inspect_with_matcher(node.task, **options),
21
+ conns.join("\n")
22
+ ]
23
+ end
24
+
25
+ content = content.join("\n")
26
+
27
+ "\n#{content}".gsub(/0x\w+/, "0x")
28
+ end
29
+
30
+ # If Ruby had pattern matching, this function wasn't necessary.
31
+ def inspect_with_matcher(task, inspect_task: method(:inspect_task), inspect_end: method(:inspect_end))
32
+ return inspect_task.(task) unless task.is_a?(Trailblazer::Activity::End)
33
+ inspect_end.(task)
34
+ end
35
+
36
+ def inspect_task(task)
37
+ task.inspect
38
+ end
39
+
40
+ def inspect_end(task)
41
+ class_name = strip(task.class)
42
+ options = task.to_h
43
+
44
+ "#<#{class_name}/#{options[:semantic].inspect}>"
45
+ end
46
+
47
+ def strip(string)
48
+ string.to_s.sub("Trailblazer::Activity::", "")
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end