From 17604d7c876363b761f0650cbbce5515b173b37a Mon Sep 17 00:00:00 2001 From: juniper-shopify <201364921+juniper-shopify@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:34:43 -0400 Subject: [PATCH] Add a `global` config scope to set basic things that apply to all cogs of all types Some config parameters, like 'exit on error', 'async' and 'working directory', are generally applicable to all/most cogs. It makes sense to have a consistent definition for their setter/getter methods. Many of these definitions exist on the Cog::Config base class, but user still need to specify them multiple times for each cog type. Now you can set some options globally using a single block ``` config do global do # every cog of every time will run with this attribute set, unless overridden by a more specific config specification exit_on_error! end cmd do # overrides for all `cmd` cogs no_exit_on_error! end chat(:foo) do # overrides for a specifically named `chat` cog no_exit_on_error! end end ``` --- dsl/outputs.rb | 1 + dsl/targets_and_params.rb | 5 +++- lib/roast/dsl/config_manager.rb | 25 ++++++++++++++++++- .../shims/lib/roast/dsl/config_context.rbi | 13 ++++++---- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/dsl/outputs.rb b/dsl/outputs.rb index 8a9f82d5..ad79c685 100644 --- a/dsl/outputs.rb +++ b/dsl/outputs.rb @@ -4,6 +4,7 @@ #: self as Roast::DSL::Workflow config do + global { exit_on_error! } cmd { display! } cmd(/to_/) { no_display! } end diff --git a/dsl/targets_and_params.rb b/dsl/targets_and_params.rb index 168a6b07..653dda86 100644 --- a/dsl/targets_and_params.rb +++ b/dsl/targets_and_params.rb @@ -19,6 +19,9 @@ # Simple word tokens are collected as `args`. Tokens in the form `key=value` are parsed into the `kwargs` hash. # e.g., `roast execute --executor=dsl dsl/targets_and_params.rb Gemfile* -- foo=bar abc=pqr hello world` puts "workflow args: #{params.args}" # [:hello, :world] (args are parsed as symbols) - puts "workflow kwargs: #{params.kwargs}" # {abc: "pqr", foo: "bar"} (keys are parsed as symbols, values as strings) + + # {abc: "pqr", foo: "bar"} (keys are parsed as symbols, values as strings) + # (Note: using explicit formatting for compatibility with Ruby versions < 3.4) + puts "workflow kwargs: {#{params.kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")}}" end end diff --git a/lib/roast/dsl/config_manager.rb b/lib/roast/dsl/config_manager.rb index dc406897..5f0e841a 100644 --- a/lib/roast/dsl/config_manager.rb +++ b/lib/roast/dsl/config_manager.rb @@ -7,12 +7,14 @@ class ConfigManager class ConfigManagerError < Roast::Error; end class ConfigManagerNotPreparedError < ConfigManagerError; end class ConfigManagerAlreadyPreparedError < ConfigManagerError; end + class IllegalCogNameError < ConfigManagerError; end #: (Cog::Registry, Array[^() -> void]) -> void def initialize(cog_registry, config_procs) @cog_registry = cog_registry @config_procs = config_procs @config_context = ConfigContext.new #: ConfigContext + @global_config = Cog::Config.new #: Cog::Config @general_configs = {} #: Hash[singleton(Cog), Cog::Config] @regexp_scoped_configs = {} #: Hash[singleton(Cog), Hash[Regexp, Cog::Config]] @name_scoped_configs = {} #: Hash[singleton(Cog), Hash[Symbol, Cog::Config]] @@ -23,6 +25,7 @@ def prepare! raise ConfigManagerAlreadyPreparedError if preparing? || prepared? @preparing = true + bind_global bind_registered_cogs @config_procs.each { |cp| @config_context.instance_eval(&cp) } @prepared = true @@ -43,7 +46,8 @@ def config_for(cog_class, name = nil) raise ConfigManagerNotPreparedError unless prepared? # All cogs will always have a config; empty by default if the cog was never explicitly configured - config = fetch_general_config(cog_class) + config = cog_class.config_class.new(@global_config.instance_variable_get(:@values).deep_dup) + config = config.merge(fetch_general_config(cog_class)) @regexp_scoped_configs.fetch(cog_class, {}).select do |pattern, _| pattern.match?(name.to_s) unless name.nil? end.values.each { |cfg| config = config.merge(cfg) } @@ -83,6 +87,8 @@ def bind_cog(cog_method_name, cog_class) on_config_method.call(cog_class, cog_name_or_pattern, cog_config_proc) end @config_context.instance_eval do + raise IllegalCogNameError, cog_method_name if respond_to?(cog_method_name, true) + define_singleton_method(cog_method_name, cog_method) end end @@ -112,6 +118,23 @@ def on_config(cog_class, cog_name_or_pattern, cog_config_proc) config_object.instance_exec(&cog_config_proc) if cog_config_proc nil end + + def bind_global + on_global_method = method(:on_global) + method_to_bind = proc do |&global_proc| + on_global_method.call(global_proc) + end + @config_context.instance_eval do + define_singleton_method(:global, method_to_bind) + end + end + + #: (^() -> void ) -> void + def on_global(global_config_proc) + global_config_proc = global_config_proc #: as ^(untyped) -> void + @global_config.instance_exec(&global_config_proc) if global_config_proc + nil + end end end end diff --git a/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi b/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi index 3f50c8ec..0cb97234 100644 --- a/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +++ b/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi @@ -5,24 +5,27 @@ module Roast module DSL class ConfigContext + #: () {() [self: Roast::DSL::Cog::Config] -> void} -> void + def global(&block); end + ######################################## # System Cogs ######################################## - #: (?Symbol?) {() [self: Roast::DSL::SystemCogs::Call::Config] -> void} -> void + #: (?(Symbol | Regexp)?) {() [self: Roast::DSL::SystemCogs::Call::Config] -> void} -> void def call(name = nil, &block); end - #: (?Symbol?) {() [self: Roast::DSL::SystemCogs::Map::Config] -> void} -> void + #: (?(Symbol | Regexp)?) {() [self: Roast::DSL::SystemCogs::Map::Config] -> void} -> void def map(name = nil, &block); end ######################################## # Standard Cogs ######################################## - #: (?Symbol?) {() [self: Roast::DSL::Cogs::Agent::Config] -> void} -> void + #: (?(Symbol | Regexp)?) {() [self: Roast::DSL::Cogs::Agent::Config] -> void} -> void def agent(name = nil, &block); end - #: (?Symbol?) {() [self: Roast::DSL::Cogs::Chat::Config] -> void} -> void + #: (?(Symbol | Regexp)?) {() [self: Roast::DSL::Cogs::Chat::Config] -> void} -> void def chat(name = nil, &block); end # Configure the `cmd` cog @@ -55,7 +58,7 @@ module Roast #: (?(Symbol | Regexp)?) {() [self: Roast::DSL::Cogs::Cmd::Config] -> void} -> void def cmd(name_or_pattern = nil, &block); end - #: (?Symbol?) {() [self: Roast::DSL::Cogs::Ruby::Config] -> void} -> void + #: (?(Symbol | Regexp)?) {() [self: Roast::DSL::Cogs::Ruby::Config] -> void} -> void def ruby(name_or_pattern = nil, &block); end end end