roast-ai 0.4.10 → 0.5.0
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.
- checksums.yaml +4 -4
- data/.claude/commands/docs/write-comments.md +36 -0
- data/.github/CODEOWNERS +1 -1
- data/.github/workflows/ci.yaml +10 -6
- data/.gitignore +0 -1
- data/.rubocop.yml +7 -1
- data/CLAUDE.md +2 -2
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +19 -18
- data/Gemfile.lock +35 -58
- data/README.md +118 -1432
- data/README_LEGACY.md +1464 -0
- data/Rakefile +39 -4
- data/dev.yml +29 -0
- data/dsl/agent_sessions.rb +20 -0
- data/dsl/async_cogs.rb +49 -0
- data/dsl/async_cogs_complex.rb +67 -0
- data/dsl/call.rb +44 -0
- data/dsl/collect_from.rb +72 -0
- data/dsl/json_output.rb +28 -0
- data/dsl/map.rb +55 -0
- data/dsl/map_reduce.rb +37 -0
- data/dsl/map_with_index.rb +49 -0
- data/dsl/next_break.rb +40 -0
- data/dsl/next_break_parallel.rb +44 -0
- data/dsl/outputs.rb +39 -0
- data/dsl/outputs_bang.rb +36 -0
- data/dsl/parallel_map.rb +37 -0
- data/dsl/prompts/simple_prompt.md.erb +3 -0
- data/dsl/prototype.rb +5 -7
- data/dsl/repeat_loop_results.rb +53 -0
- data/dsl/ruby_cog.rb +72 -0
- data/dsl/simple_agent.rb +18 -0
- data/dsl/simple_chat.rb +15 -1
- data/dsl/simple_repeat.rb +29 -0
- data/dsl/skip.rb +36 -0
- data/dsl/step_communication.rb +2 -3
- data/dsl/targets_and_params.rb +57 -0
- data/dsl/temperature.rb +17 -0
- data/dsl/temporary_directory.rb +22 -0
- data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
- data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
- data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
- data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
- data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
- data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
- data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
- data/dsl/tutorial/03_targets_and_params/README.md +230 -0
- data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
- data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
- data/dsl/tutorial/04_configuration_options/README.md +209 -0
- data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
- data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
- data/dsl/tutorial/05_control_flow/README.md +156 -0
- data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
- data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
- data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
- data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
- data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
- data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
- data/dsl/tutorial/07_processing_collections/README.md +152 -0
- data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
- data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
- data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
- data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
- data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
- data/dsl/tutorial/09_async_cogs/README.md +197 -0
- data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
- data/dsl/tutorial/README.md +222 -0
- data/dsl/working_directory.rb +16 -0
- data/exe/roast +1 -1
- data/internal/documentation/architectural-notes.md +115 -0
- data/internal/documentation/doc-comments-external.md +686 -0
- data/internal/documentation/doc-comments-internal.md +342 -0
- data/internal/documentation/doc-comments.md +211 -0
- data/lib/roast/dsl/cog/config.rb +274 -3
- data/lib/roast/dsl/cog/input.rb +53 -10
- data/lib/roast/dsl/cog/output.rb +297 -8
- data/lib/roast/dsl/cog/registry.rb +35 -3
- data/lib/roast/dsl/cog/stack.rb +1 -1
- data/lib/roast/dsl/cog/store.rb +5 -5
- data/lib/roast/dsl/cog.rb +70 -14
- data/lib/roast/dsl/cog_input_context.rb +36 -1
- data/lib/roast/dsl/cog_input_manager.rb +116 -7
- data/lib/roast/dsl/cogs/agent/config.rb +465 -0
- data/lib/roast/dsl/cogs/agent/input.rb +81 -0
- data/lib/roast/dsl/cogs/agent/output.rb +59 -0
- data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
- data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
- data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
- data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
- data/lib/roast/dsl/cogs/agent.rb +75 -0
- data/lib/roast/dsl/cogs/chat/config.rb +453 -0
- data/lib/roast/dsl/cogs/chat/input.rb +92 -0
- data/lib/roast/dsl/cogs/chat/output.rb +64 -0
- data/lib/roast/dsl/cogs/chat/session.rb +68 -0
- data/lib/roast/dsl/cogs/chat.rb +59 -56
- data/lib/roast/dsl/cogs/cmd.rb +248 -61
- data/lib/roast/dsl/cogs/ruby.rb +171 -0
- data/lib/roast/dsl/command_runner.rb +191 -0
- data/lib/roast/dsl/config_manager.rb +58 -11
- data/lib/roast/dsl/control_flow.rb +41 -0
- data/lib/roast/dsl/execution_manager.rb +162 -32
- data/lib/roast/dsl/nil_assertions.rb +23 -0
- data/lib/roast/dsl/system_cog/params.rb +32 -0
- data/lib/roast/dsl/system_cog.rb +36 -0
- data/lib/roast/dsl/system_cogs/call.rb +162 -0
- data/lib/roast/dsl/system_cogs/map.rb +448 -0
- data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
- data/lib/roast/dsl/workflow.rb +26 -16
- data/lib/roast/dsl/workflow_context.rb +20 -0
- data/lib/roast/dsl/workflow_params.rb +24 -0
- data/lib/roast/sorbet_runtime_stub.rb +154 -0
- data/lib/roast/tools/apply_diff.rb +1 -3
- data/lib/roast/tools/cmd.rb +4 -3
- data/lib/roast/tools/read_file.rb +1 -1
- data/lib/roast/tools/update_files.rb +1 -1
- data/lib/roast/tools/write_file.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_workflow.rb +4 -0
- data/lib/roast/workflow/step_loader.rb +14 -2
- data/lib/roast-ai.rb +4 -0
- data/lib/roast.rb +58 -21
- data/{roast.gemspec → roast-ai.gemspec} +9 -13
- data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
- data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
- data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
- data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
- data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
- data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
- data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
- data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1185 -5
- data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +311 -5
- data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +486 -5
- data/sorbet/tapioca/config.yml +6 -0
- data/sorbet/tapioca/require.rb +2 -0
- metadata +157 -30
- data/dsl/less_simple.rb +0 -112
- data/dsl/scoped_executors.rb +0 -28
- data/dsl/simple.rb +0 -8
- data/lib/roast/dsl/cogs/execute.rb +0 -46
- data/lib/roast/dsl/cogs/graph.rb +0 -53
- data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
- data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
- data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
- data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
- data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
- data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
- data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
- data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
- data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
- data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
- data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
- data/sorbet/rbi/gems/rack@2.2.19.rbi +0 -5676
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- 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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
data/dsl/collect_from.rb
ADDED
|
@@ -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
|
data/dsl/json_output.rb
ADDED
|
@@ -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,40 @@
|
|
|
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
|
+
end
|
|
21
|
+
|
|
22
|
+
execute(:loop_body) do
|
|
23
|
+
ruby(:one) do |_, _, idx|
|
|
24
|
+
skip! if idx == 0
|
|
25
|
+
s = "[#{idx}] beginning"
|
|
26
|
+
puts s
|
|
27
|
+
s
|
|
28
|
+
end
|
|
29
|
+
ruby(:two) do |_, _, idx|
|
|
30
|
+
break! if idx == 1
|
|
31
|
+
s = "[#{idx}] middle"
|
|
32
|
+
puts s
|
|
33
|
+
s
|
|
34
|
+
end
|
|
35
|
+
ruby(:three) do |_, _, idx|
|
|
36
|
+
s = "[#{idx}] end"
|
|
37
|
+
puts s
|
|
38
|
+
s
|
|
39
|
+
end
|
|
40
|
+
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
|
data/dsl/outputs_bang.rb
ADDED
|
@@ -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
|