roast-ai 0.4.10 → 0.5.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 (175) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/docs/write-comments.md +36 -0
  3. data/.github/CODEOWNERS +1 -1
  4. data/.github/workflows/ci.yaml +10 -6
  5. data/.gitignore +0 -1
  6. data/.rubocop.yml +7 -1
  7. data/.ruby-version +1 -1
  8. data/CLAUDE.md +2 -2
  9. data/CONTRIBUTING.md +2 -0
  10. data/Gemfile +19 -18
  11. data/Gemfile.lock +35 -58
  12. data/README.md +118 -1432
  13. data/README_LEGACY.md +1464 -0
  14. data/Rakefile +39 -4
  15. data/dev.yml +29 -0
  16. data/dsl/agent_sessions.rb +20 -0
  17. data/dsl/async_cogs.rb +49 -0
  18. data/dsl/async_cogs_complex.rb +67 -0
  19. data/dsl/call.rb +44 -0
  20. data/dsl/collect_from.rb +72 -0
  21. data/dsl/json_output.rb +28 -0
  22. data/dsl/map.rb +55 -0
  23. data/dsl/map_reduce.rb +37 -0
  24. data/dsl/map_with_index.rb +49 -0
  25. data/dsl/next_break.rb +45 -0
  26. data/dsl/next_break_parallel.rb +44 -0
  27. data/dsl/outputs.rb +39 -0
  28. data/dsl/outputs_bang.rb +36 -0
  29. data/dsl/parallel_map.rb +37 -0
  30. data/dsl/prompts/simple_prompt.md.erb +3 -0
  31. data/dsl/prototype.rb +5 -7
  32. data/dsl/repeat_loop_results.rb +53 -0
  33. data/dsl/ruby_cog.rb +72 -0
  34. data/dsl/simple_agent.rb +18 -0
  35. data/dsl/simple_chat.rb +15 -1
  36. data/dsl/simple_repeat.rb +29 -0
  37. data/dsl/skip.rb +36 -0
  38. data/dsl/step_communication.rb +2 -3
  39. data/dsl/targets_and_params.rb +57 -0
  40. data/dsl/temperature.rb +17 -0
  41. data/dsl/temporary_directory.rb +22 -0
  42. data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
  43. data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
  44. data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
  45. data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
  46. data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
  47. data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
  48. data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
  49. data/dsl/tutorial/03_targets_and_params/README.md +230 -0
  50. data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
  51. data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
  52. data/dsl/tutorial/04_configuration_options/README.md +209 -0
  53. data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
  54. data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
  55. data/dsl/tutorial/05_control_flow/README.md +156 -0
  56. data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
  57. data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
  58. data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
  59. data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
  60. data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
  61. data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
  62. data/dsl/tutorial/07_processing_collections/README.md +152 -0
  63. data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
  64. data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
  65. data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
  66. data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
  67. data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
  68. data/dsl/tutorial/09_async_cogs/README.md +197 -0
  69. data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
  70. data/dsl/tutorial/README.md +222 -0
  71. data/dsl/working_directory.rb +16 -0
  72. data/exe/roast +1 -1
  73. data/internal/documentation/architectural-notes.md +115 -0
  74. data/internal/documentation/doc-comments-external.md +686 -0
  75. data/internal/documentation/doc-comments-internal.md +342 -0
  76. data/internal/documentation/doc-comments.md +211 -0
  77. data/lib/roast/dsl/cog/config.rb +274 -3
  78. data/lib/roast/dsl/cog/input.rb +53 -10
  79. data/lib/roast/dsl/cog/output.rb +297 -8
  80. data/lib/roast/dsl/cog/registry.rb +35 -3
  81. data/lib/roast/dsl/cog/stack.rb +1 -1
  82. data/lib/roast/dsl/cog/store.rb +5 -5
  83. data/lib/roast/dsl/cog.rb +70 -14
  84. data/lib/roast/dsl/cog_input_context.rb +36 -1
  85. data/lib/roast/dsl/cog_input_manager.rb +116 -7
  86. data/lib/roast/dsl/cogs/agent/config.rb +465 -0
  87. data/lib/roast/dsl/cogs/agent/input.rb +81 -0
  88. data/lib/roast/dsl/cogs/agent/output.rb +59 -0
  89. data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
  90. data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
  91. data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
  92. data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
  93. data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
  94. data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
  95. data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
  96. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
  97. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
  98. data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
  99. data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
  100. data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
  101. data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
  102. data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
  103. data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
  104. data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
  105. data/lib/roast/dsl/cogs/agent.rb +75 -0
  106. data/lib/roast/dsl/cogs/chat/config.rb +453 -0
  107. data/lib/roast/dsl/cogs/chat/input.rb +92 -0
  108. data/lib/roast/dsl/cogs/chat/output.rb +64 -0
  109. data/lib/roast/dsl/cogs/chat/session.rb +68 -0
  110. data/lib/roast/dsl/cogs/chat.rb +59 -56
  111. data/lib/roast/dsl/cogs/cmd.rb +251 -61
  112. data/lib/roast/dsl/cogs/ruby.rb +171 -0
  113. data/lib/roast/dsl/command_runner.rb +191 -0
  114. data/lib/roast/dsl/config_manager.rb +58 -11
  115. data/lib/roast/dsl/control_flow.rb +41 -0
  116. data/lib/roast/dsl/execution_manager.rb +162 -32
  117. data/lib/roast/dsl/nil_assertions.rb +23 -0
  118. data/lib/roast/dsl/system_cog/params.rb +32 -0
  119. data/lib/roast/dsl/system_cog.rb +36 -0
  120. data/lib/roast/dsl/system_cogs/call.rb +163 -0
  121. data/lib/roast/dsl/system_cogs/map.rb +454 -0
  122. data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
  123. data/lib/roast/dsl/workflow.rb +26 -16
  124. data/lib/roast/dsl/workflow_context.rb +20 -0
  125. data/lib/roast/dsl/workflow_params.rb +24 -0
  126. data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
  127. data/lib/roast/sorbet_runtime_stub.rb +154 -0
  128. data/lib/roast/tools/apply_diff.rb +1 -3
  129. data/lib/roast/tools/cmd.rb +4 -3
  130. data/lib/roast/tools/read_file.rb +1 -1
  131. data/lib/roast/tools/update_files.rb +1 -1
  132. data/lib/roast/tools/write_file.rb +1 -1
  133. data/lib/roast/version.rb +1 -1
  134. data/lib/roast/workflow/base_workflow.rb +4 -0
  135. data/lib/roast/workflow/step_loader.rb +14 -2
  136. data/lib/roast-ai.rb +4 -0
  137. data/lib/roast.rb +58 -21
  138. data/{roast.gemspec → roast-ai.gemspec} +9 -13
  139. data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
  140. data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
  141. data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
  142. data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
  143. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
  144. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
  145. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
  146. data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
  147. data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
  148. data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
  149. data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1185 -5
  150. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +311 -5
  151. data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +486 -5
  152. data/sorbet/tapioca/config.yml +6 -0
  153. data/sorbet/tapioca/require.rb +2 -0
  154. metadata +157 -30
  155. data/dsl/less_simple.rb +0 -112
  156. data/dsl/scoped_executors.rb +0 -28
  157. data/dsl/simple.rb +0 -8
  158. data/lib/roast/dsl/cogs/execute.rb +0 -46
  159. data/lib/roast/dsl/cogs/graph.rb +0 -53
  160. data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
  161. data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
  162. data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
  163. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
  164. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
  165. data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
  166. data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
  167. data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
  168. data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
  169. data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
  170. data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
  171. data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
  172. data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
  173. data/sorbet/rbi/gems/rack@2.2.19.rbi +0 -5676
  174. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
  175. data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
data/Rakefile CHANGED
@@ -4,6 +4,8 @@ require "bundler/gem_tasks"
4
4
  require "rubocop/rake_task"
5
5
  require "rake/testtask"
6
6
 
7
+ ### Test Tasks
8
+
7
9
  Rake::TestTask.new(:minitest_all) do |t|
8
10
  t.libs << "test"
9
11
  t.libs << "lib"
@@ -19,10 +21,32 @@ end
19
21
  Rake::TestTask.new(:minitest_old) do |t|
20
22
  t.libs << "test"
21
23
  t.libs << "lib"
22
- t.test_files = FileList["test/functional/**/*_test.rb"]
24
+ t.test_files = FileList["test/**/*_test.rb"].exclude(
25
+ "test/functional/**/*_test.rb",
26
+ "test/dsl/**/*_test.rb",
27
+ "test/roast/dsl/**/*_test.rb",
28
+ )
29
+ end
30
+
31
+ Rake::TestTask.new(:minitest_dsl_fast) do |t|
32
+ t.libs << "test"
33
+ t.libs << "lib"
34
+ t.test_files = FileList["test/dsl/**/*_test.rb", "test/roast/dsl/**/*_test.rb"]
35
+ end
36
+
37
+ Rake::TestTask.new(:minitest_dsl_slow) do |t|
38
+ t.libs << "test"
39
+ t.libs << "lib"
40
+ t.test_files = FileList["test/dsl/**/*_test.rb", "test/roast/dsl/**/*_test.rb"]
23
41
  end
42
+ task :set_slow_env do
43
+ ENV["ROAST_RUN_SLOW_TESTS"] = "true"
44
+ end
45
+ task minitest_dsl_slow: :set_slow_env
46
+
47
+ task test: [:minitest_dsl_fast, :minitest_dsl_slow, :minitest_functional, :minitest_old]
24
48
 
25
- task test: [:minitest_all]
49
+ ### Rubocop Tasks
26
50
 
27
51
  RuboCop::RakeTask.new(:rubocop_ci)
28
52
 
@@ -30,6 +54,17 @@ RuboCop::RakeTask.new(:rubocop) do |task|
30
54
  task.options = ["--autocorrect"]
31
55
  end
32
56
 
33
- task default: [:test, :rubocop]
57
+ ### Sorbet Tasks
58
+
59
+ desc "Run Sorbet type checker"
60
+ task :sorbet do
61
+ sh "bin/srb tc" do |ok, _|
62
+ abort "Sorbet type checking failed" unless ok
63
+ end
64
+ end
65
+
66
+ ### Task Groups
67
+
68
+ task default: [:sorbet, :rubocop, :minitest_dsl_fast]
34
69
 
35
- task lint: [:rubocop]
70
+ task check: [:sorbet, :rubocop]
data/dev.yml ADDED
@@ -0,0 +1,29 @@
1
+ name: roast
2
+
3
+ nix: true
4
+
5
+ type: ruby
6
+
7
+ up:
8
+ - ruby
9
+ - bundler
10
+
11
+ check:
12
+ style: bundle exec rake rubocop
13
+ tc: bundle exec rake sorbet
14
+ test: bundle exec rake minitest_dsl_fast
15
+
16
+ commands:
17
+ __default__: execute
18
+ execute:
19
+ desc: Execute a roast workflow
20
+ run: bundle exec roast execute --executor=dsl
21
+ style:
22
+ desc: Run Rubocop style checks (and autocorrect)
23
+ run: bundle exec rake rubocop:autocorrect
24
+ tc:
25
+ desc: Run Sorbet type checks
26
+ run: bundle exec rake sorbet
27
+ test:
28
+ desc: Run all tests
29
+ run: bundle exec rake minitest_dsl_fast
@@ -0,0 +1,20 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ agent do
8
+ provider :claude
9
+ model "haiku"
10
+ dump_raw_agent_messages_to "tmp/claude-messages.log"
11
+ end
12
+ end
13
+
14
+ execute do
15
+ agent(:one) { "The magic word is 'pomegranate'" }
16
+ agent(:two) do |my|
17
+ my.session = agent!(:one).session
18
+ "What is the magic word?"
19
+ end
20
+ end
data/dsl/async_cogs.rb ADDED
@@ -0,0 +1,49 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+
9
+ # Any type of cog can be configured to run asynchronously in the background.
10
+ # The cog that follows it in the workflow will be able to begin running immediately.
11
+ cmd(/slow/) { async! }
12
+ end
13
+
14
+ execute do
15
+ # 'first' is synchronous; nothing else runs until it completes.
16
+ cmd(:first) { "echo first" }
17
+
18
+ # These cogs are async and slow, so we kick them off in the background (via the 'async!' config option)
19
+ # Other cogs can continue while these cog runs in the background
20
+ cmd(:slow_background_task_1) do
21
+ sleep 0.1
22
+ "echo slow background task 1"
23
+ end
24
+ cmd(:slow_background_task_2) do
25
+ sleep 0.2
26
+ "echo slow background task 2"
27
+ end
28
+
29
+ cmd(:second) { "echo second" }
30
+ cmd(:third) { "echo third" }
31
+
32
+ # 'fourth' accesses the output of 'slow_background_task_1'.
33
+ # It will block until 'slow_background_task_1' completes.
34
+ # 'fourth' is synchronous, so cogs that follow it will also be forced to wait (whether they are async or not)
35
+ cmd(:fourth) do
36
+ "echo \"fourth <-- '#{cmd!(:slow_background_task_1).text}'\""
37
+ end
38
+
39
+ cmd(:fifth) { "echo fifth" }
40
+
41
+ # The overall completion order of these cogs is:
42
+ # first
43
+ # second : run right after first; does not have to wait for the slow background tasks to complete
44
+ # third
45
+ # slow_background_task_1
46
+ # fourth : forced to wait for slow_background_task_1 to complete to access its input
47
+ # fifth
48
+ # slow_background_task_2 : the workflow will not complete until all async cogs have completed
49
+ end
@@ -0,0 +1,67 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+
9
+ # Any type of cog can be configured to run asynchronously.
10
+ # The cog that follows it in the workflow will be able to begin running immediately, before the async cog has finished.
11
+ cmd(:second) { async! }
12
+
13
+ # If there are a mix of sync and async cogs in a workflow, any synchronous cog may begin running before any
14
+ # async cogs above it have completed, but no cogs below it -- sync or async -- will start until it has completed.
15
+ cmd(:third) { no_async! } # NOTE: `no_async!` is redundant here (it's the default); but included here for extra clarity.
16
+ cmd(:fourth) { async! }
17
+ cmd(:fifth) { async! }
18
+ cmd(:sixth) { async! }
19
+ end
20
+
21
+ execute do
22
+ # 'first' is synchronous; nothing else runs until it completes.
23
+ cmd(:first) { "echo first" }
24
+
25
+ # 'second' is async and slow; it will not complete until after 'third' is done,
26
+ # but 'third' will be allowed to start right away.
27
+ cmd(:second) do
28
+ sleep 0.2
29
+ "echo second"
30
+ end
31
+
32
+ # 'third is synchronous and medium-speed; it will not wait for the async cogs above it,
33
+ # so it will complete before the slow, async 'second' cog.
34
+ cmd(:third) do
35
+ sleep 0.1
36
+ "echo third"
37
+ end
38
+
39
+ # 'fourth' is async, but it will not be allowed to start until the synchronous 'third' cog about it has completed.
40
+ # It will still complete before the slow-running 'second' cog, though.
41
+ cmd(:fourth) { "echo fourth" }
42
+
43
+ # 'fifth' is async, and fast, and will start before the slow-running 'second' cog.
44
+ # However, its input depends on the output of 'second', so it will automatically wait for 'second' to complete.
45
+ cmd(:fifth) do
46
+ # All the output accessors -- cmd?(:name), cmd(:name), cmd!(:name) -- will block
47
+ # until the named async cog is complete IF AND ONLY IF it has already started.
48
+ # If the named cog is defined later in the workflow and thus not already started,
49
+ # these methods will return immediately.
50
+ "echo \"fifth (second said: '#{cmd!(:second).text}')\""
51
+ end
52
+
53
+ # 'sixth' is async as well, and it will complete before the slow-running 'second' cog AND before the 'fifth' cog
54
+ # that is blocking on the output of 'second'
55
+ cmd(:sixth) do
56
+ sleep 0.01 # just to make sure this runs slightly slower than 'fourth' to maintain deterministic ordering
57
+ "echo sixth"
58
+ end
59
+
60
+ # The overall completion order of these cogs is:
61
+ # first : synchronous; nothing else started until it was done
62
+ # third : synchronous; did not block on the async 'second' cog
63
+ # fourth : async; could not start until after 'third'
64
+ # sixth : async; started at the same time as 'fourth', ran very slightly slower
65
+ # second : async, slow; started right after 'first', but didn't complete until now
66
+ # fifth : async; faster than 'second' but depended on its output, so it blocked until 'second' was complete
67
+ end
data/dsl/call.rb ADDED
@@ -0,0 +1,44 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd do
8
+ display!
9
+ end
10
+ end
11
+
12
+ execute(:capitalize_a_random_word) do
13
+ cmd(:word) { "shuf /usr/share/dict/words -n 1" }
14
+ cmd(:capitalize) do |my, value_from_call|
15
+ # Optional second block argument lets you use a value passed to the sub-executor by `call`
16
+ # from withing any cog in the sub-executor
17
+ word = value_from_call || cmd!(:word).text
18
+ my.command = "/bin/sh"
19
+ my.args << "-c"
20
+ my.args << "/bin/echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
21
+ end
22
+ end
23
+
24
+ execute do
25
+ cmd(:before) { "echo '--> before'" }
26
+
27
+ # First positional argument is always the (optional) cog name
28
+ # Keyword argument "run" is the (required) scope name of the executor to run
29
+ call(:first_call, run: :capitalize_a_random_word)
30
+ call(:other_named_call, run: :capitalize_a_random_word)
31
+ call(run: :capitalize_a_random_word) # anonymous call cog
32
+
33
+ cmd { "echo '---'" }
34
+
35
+ # Can invoke a sub-executor with an executor-scoped value, that will be passed to each cog's input proc in that scope
36
+ call(run: :capitalize_a_random_word) do |my|
37
+ my.value = "scope value: roast"
38
+ end
39
+
40
+ # Shorthand input coercion for passing a scope value
41
+ call(run: :capitalize_a_random_word) { "scope value: other" }
42
+
43
+ cmd(:after) { "echo '--> after'" }
44
+ end
@@ -0,0 +1,72 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ cmd(/to_/) { no_display! }
9
+ end
10
+
11
+ execute(:capitalize_a_word) do
12
+ cmd(:to_original) { |_, word| "echo \"#{word}\"" }
13
+ cmd(:to_upper) do |my, word|
14
+ my.command = "sh"
15
+ my.args << "-c"
16
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
17
+ end
18
+ cmd(:to_lower) do |my, word|
19
+ my.command = "sh"
20
+ my.args << "-c"
21
+ my.args << "echo \"#{word}\" | tr '[:upper:]' '[:lower:]'"
22
+ end
23
+ end
24
+
25
+ execute do
26
+ # Call a subroutine with `call` or `map`
27
+ call(:hello, run: :capitalize_a_word) { "Hello" }
28
+ call(:world, run: :capitalize_a_word) { "World" }
29
+ map(:other_words, run: :capitalize_a_word) { ["Goodnight", "Moon"] }
30
+
31
+ cmd do
32
+ # Normally, you can only reference the output of cogs that run in the same executor scope.
33
+ begin
34
+ # There is no :to_upper cog that this could sensibly be referring to
35
+ cmd(:to_upper)
36
+ rescue Roast::DSL::CogInputManager::CogDoesNotExistError
37
+ puts "Could not access :to_upper directly"
38
+ end
39
+
40
+ # Using `from`, you can access cogs from the executor scope that was run by a specific named `call`.
41
+ # The block you pass to `from` runs in the input context of the specified scope, rather than the current scope.
42
+ original = from(call!(:hello)) { cmd!(:to_original).text }
43
+ # The type of the return value of `from` is the same type as the return value of the block.
44
+ upper = from(call!(:hello)) { cmd!(:to_upper) }.text
45
+ # You can also use this shorthand `from` syntax to get the output of the last cog in the call's executor scope.
46
+ # In this syntax, the return value of `from` is untyped
47
+ lower = from(call!(:hello)).text
48
+ "echo \"#{original} --> #{upper} --> #{lower}\""
49
+ end
50
+
51
+ cmd do
52
+ # You can also grab the `call`'s output once and pass it to multiple `from` invocations.
53
+ my_scope = call!(:world)
54
+ original = from(my_scope) { cmd!(:to_original).text }
55
+ upper = from(my_scope) { cmd!(:to_upper).text }
56
+ lower = from(my_scope) { cmd!(:to_lower).text }
57
+ "echo \"#{original} --> #{upper} --> #{lower}\""
58
+ end
59
+
60
+ cmd do
61
+ # Using `collect`, you can access cogs from the executor scopes that were run by a specific named `map`.
62
+ # The block you pass to `collect` runs in the input context of each specified scope.
63
+ # `collect` returns an array containing the output of each invocation of that block.
64
+ originals = collect(map!(:other_words)) { cmd!(:to_original).text }
65
+ # The type of the return value of `call` is an Array of the same type as the return value of the block.
66
+ uppers = collect(map!(:other_words)) { cmd!(:to_upper) }.map(&:text)
67
+ # You can also use this shorthand `collect` syntax to get the output of the last cog in each of the map's executor scopes.
68
+ # In this syntax, the return value of `collect` is Array[untyped]
69
+ lowers = collect(map!(:other_words)).map(&:text)
70
+ "echo \"#{originals.join(",")} --> #{uppers.join(",")} --> #{lowers.join(",")}\""
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ end
9
+
10
+ execute do
11
+ cmd(:json) do |my|
12
+ my.command = "echo"
13
+ my.args << <<~JSON
14
+ {
15
+ "hello": "world",
16
+ "letters": [
17
+ "aaa",
18
+ "bbb"
19
+ ]
20
+ }
21
+ JSON
22
+ end
23
+
24
+ ruby do
25
+ puts "RAW OUTPUT: #{cmd!(:json).text}"
26
+ puts "SOME VALUE FROM PARSED OUTPUT: #{cmd!(:json).json![:letters].first}"
27
+ end
28
+ end
data/dsl/map.rb ADDED
@@ -0,0 +1,55 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd do
8
+ display!
9
+ end
10
+ end
11
+
12
+ execute(:capitalize_a_word) do
13
+ cmd(:capitalize) do |my, word|
14
+ # Use the `fail!` method in a cog's input proc to terminate the cog with a failure state
15
+ # e.g., if a condition prevents its successful execution.
16
+ # If `fail!` is called, the input proc will immediately terminate.
17
+ # The entire workflow may or may not terminate as a result, based on its configuration and the cog's configuration.
18
+ fail! unless word.present?
19
+ my.command = "sh"
20
+ my.args << "-c"
21
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
22
+ end
23
+ end
24
+
25
+ execute do
26
+ words = ["hello", "world", "goodnight", "moon"]
27
+
28
+ # WITHOUT MAP COG
29
+ # You can use plain Ruby to generate a collection of cogs programmatically
30
+ # This is equivalent to simply defining each of the cogs explicitly, one by one
31
+ # In this case, four copies of the `call` cog invoking :capitalize_a_word
32
+ words.each { |word| call(run: :capitalize_a_word) { word } }
33
+
34
+ cmd(:foo) { "echo" }
35
+
36
+ # USING MAP COG
37
+ # You can use the `map` cog to apply a scoped executor to each item in a collection of values
38
+ map(:some_name, run: :capitalize_a_word) do |my|
39
+ my.items = words
40
+ end
41
+
42
+ cmd { "echo" }
43
+
44
+ # USING MAP COG (SHORTHAND)
45
+ # - name of execute scope is required
46
+ # - name of the map cog itself can be omitted (anonymous cog)
47
+ # - items over which to map coerced from return value of input proc
48
+ map(run: :capitalize_a_word) { words.reverse }
49
+
50
+ # ACCESSING THE OUTPUT OF A SPECIFIC MAP ITERATION
51
+ ruby do
52
+ puts ""
53
+ puts "#{words[2]} -> #{from(map!(:some_name).iteration(2)) { cmd!(:capitalize).text }}"
54
+ end
55
+ end
data/dsl/map_reduce.rb ADDED
@@ -0,0 +1,37 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ cmd(/to_/) { no_display! }
9
+ end
10
+
11
+ execute(:capitalize_a_word) do
12
+ cmd(:to_original) { |_, word| "echo \"#{word}\"" }
13
+ cmd(:to_upper) do |my, word|
14
+ my.command = "sh"
15
+ my.args << "-c"
16
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
17
+ end
18
+ cmd(:to_lower) do |my, word|
19
+ my.command = "sh"
20
+ my.args << "-c"
21
+ my.args << "echo \"#{word}\" | tr '[:upper:]' '[:lower:]'"
22
+ end
23
+ end
24
+
25
+ execute do
26
+ # Call a subroutine with `map`
27
+ map(:words, run: :capitalize_a_word) { ["Hello", "World"] }
28
+
29
+ cmd do
30
+ # Use `reduce` to apply a block to the input context of each executor scope run by `map` in turn.
31
+ # The return value of each invocation of the block will be passed as the 'accumulator' to the next invocation.
32
+ # You can provide an optional initial value for the accumulator as the second argument to `reduce`.
33
+ # If an initial value is present, the return type is non-nilable. If absent, the return type is nilable.
34
+ words = reduce(map!(:words), "lower case words:") { |acc| acc + " " + cmd!(:to_lower).text }
35
+ "echo \"#{words}\""
36
+ end
37
+ end
@@ -0,0 +1,49 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd do
8
+ display!
9
+ end
10
+ end
11
+
12
+ execute(:capitalize_a_word) do
13
+ cmd(:capitalize) do |my, word, index|
14
+ fail! unless word.present?
15
+ my.command = "sh"
16
+ my.args << "-c"
17
+ # When this execution scope is called by the `map` cog, the optional 'index' argument to this block
18
+ # will contain the index of its element in the enumerable on which `map` is invoked
19
+ my.args << "echo \"[#{index}] #{word}\" | tr '[:lower:]' '[:upper:]'"
20
+ end
21
+ end
22
+
23
+ execute do
24
+ words = ["hello", "world", "goodnight", "moon"]
25
+
26
+ # When calling an execute scope with `map`, the index of the item in the enumerable
27
+ # will be provided to the cogs in that scope
28
+ map(:some_name, run: :capitalize_a_word) { words }
29
+
30
+ cmd { "echo" }
31
+
32
+ # When calling an execute scope with `call`, the cogs in that scope will receive 0 as the index value by default
33
+ call(run: :capitalize_a_word) { "default" }
34
+
35
+ call(run: :capitalize_a_word) do |my|
36
+ my.value = "specific"
37
+ # It is possible to specify a custom index value when invoking `call`
38
+ my.index = 23
39
+ end
40
+
41
+ cmd { "echo" }
42
+
43
+ # `map` will also take a custom 'initial_index' value, at which to start the value of 'index' passed
44
+ # to the executors it invokes. This parallels the syntax of Ruby's `items.map.with_index(initial_value) { ... }`
45
+ map(run: :capitalize_a_word) do |my|
46
+ my.items = words[1, 2]
47
+ my.initial_index = 8
48
+ end
49
+ end
data/dsl/next_break.rb ADDED
@@ -0,0 +1,45 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ end
8
+
9
+ execute do
10
+ map(:loop, run: :loop_body) { 1..3 }
11
+
12
+ ruby do
13
+ results = collect(map!(:loop)) do
14
+ [ruby?(:one), ruby?(:two), ruby?(:three)]
15
+ end
16
+ results.each_with_index do |iteration_results, index|
17
+ puts "Iteration #{index}: #{iteration_results.presence || "did not run at all"}"
18
+ end
19
+ end
20
+
21
+ call(:once, run: :loop_body) do |my|
22
+ my.value = 1
23
+ my.index = 1
24
+ end
25
+ end
26
+
27
+ execute(:loop_body) do
28
+ ruby(:one) do |_, _, idx|
29
+ skip! if idx == 0
30
+ s = "[#{idx}] beginning"
31
+ puts s
32
+ s
33
+ end
34
+ ruby(:two) do |_, _, idx|
35
+ break! if idx == 1
36
+ s = "[#{idx}] middle"
37
+ puts s
38
+ s
39
+ end
40
+ ruby(:three) do |_, _, idx|
41
+ s = "[#{idx}] end"
42
+ puts s
43
+ s
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ map { parallel! }
8
+ end
9
+
10
+ execute do
11
+ map(:loop, run: :loop_body) { 1..3 }
12
+
13
+ ruby do
14
+ results = collect(map!(:loop)) do
15
+ [ruby?(:one), ruby?(:two), ruby?(:three)]
16
+ end
17
+ results.each_with_index do |iteration_results, index|
18
+ puts "Iteration #{index}: #{iteration_results.presence || "did not run at all"}"
19
+ end
20
+ end
21
+ end
22
+
23
+ execute(:loop_body) do
24
+ ruby(:one) do |_, _, idx|
25
+ sleep(0.1) if idx == 0
26
+ sleep(0.2) if idx == 1
27
+ skip! if idx == 0
28
+ s = "[#{idx}] beginning"
29
+ puts s
30
+ s
31
+ end
32
+ ruby(:two) do |_, _, idx|
33
+ break! if idx == 1
34
+ s = "[#{idx}] middle"
35
+ puts s
36
+ s
37
+ end
38
+ ruby(:three) do |_, _, idx|
39
+ sleep(0.3) if idx == 2
40
+ s = "[#{idx}] end"
41
+ puts s
42
+ s
43
+ end
44
+ end
data/dsl/outputs.rb ADDED
@@ -0,0 +1,39 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ cmd(/to_/) { no_display! }
9
+ end
10
+
11
+ execute(:capitalize_a_word) do
12
+ cmd(:to_original) { |_, word| "echo \"#{word}\"" }
13
+ cmd(:to_upper) do |my, word|
14
+ my.command = "sh"
15
+ my.args << "-c"
16
+ my.args << "echo \"#{word}\" | tr '[:lower:]' '[:upper:]'"
17
+ end
18
+ cmd(:to_lower) do |my, word|
19
+ break! # the `outputs` will always be evaluated even if a cog breaks out of the execution scope
20
+ my.command = "sh"
21
+ my.args << "-c"
22
+ my.args << "echo \"#{word}\" | tr '[:upper:]' '[:lower:]'"
23
+ end
24
+ outputs do |word|
25
+ "Upper: #{cmd!(:to_upper).text} - Original: #{word}"
26
+ # `outputs` can return any kind of value
27
+ end
28
+ end
29
+
30
+ execute do
31
+ # Call a subroutine with `call` or `map`
32
+ call(:hello, run: :capitalize_a_word) { "Hello" }
33
+
34
+ cmd do
35
+ from_outputs = from(call!(:hello))
36
+ explicit_value_access = from(call!(:hello)) { cmd!(:to_upper).text }
37
+ "echo From Outputs: '\"#{from_outputs}\"\nExplicit Value Access: \"#{explicit_value_access}\"'"
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ #: self as Roast::DSL::Workflow
5
+
6
+ config do
7
+ cmd { display! }
8
+ cmd(/to_/) { no_display! }
9
+ end
10
+
11
+ execute(:subroutine1) do
12
+ ruby(:one) { "one" }
13
+ ruby(:two) { skip! }
14
+ # Attempting to access the output of a cog that did not run will not raise an exception inside the `outputs` block
15
+ # Instead, it will just return `nil`
16
+ outputs { ruby!(:two).value }
17
+ end
18
+
19
+ execute(:subroutine2) do
20
+ ruby(:one) { "one" }
21
+ ruby(:two) { skip! }
22
+ # Attempting to access the output of a cog that did not run *will* raise an exception inside the `outputs!` block
23
+ outputs! do
24
+ puts "❗️ This block is expected to raise an exception ❗️"
25
+ ruby!(:two).value
26
+ end
27
+ end
28
+
29
+ execute do
30
+ call(:one, run: :subroutine1) {}
31
+
32
+ ruby { puts "Using the `outputs` block should return `nil`: #{from(call!(:one)) || "nil"}" }
33
+
34
+ # The `outputs!` block in subroutine2 will raise an exception as soon as it is evaluated
35
+ call(:two, run: :subroutine2) {}
36
+ end