Class: Roby::TaskArguments
- Includes:
- Enumerable, DRoby::V5::TaskArgumentsDumper
- Defined in:
- lib/roby/task_arguments.rb,
lib/roby/droby/enable.rb
Overview
Management of task arguments
This class essentially behaves like a Hash, but with two important caveats:
-
already set values cannot be modified
-
some special values (“delayed arguments”) allow to provide arguments whose evaluation is delayed until the value is needed.
Write-Once Values
Once set to a plain object (not a delayed argument, see below), a value cannot be changed. Some methods allow to bypass the relevant checks, but these methods must be considered internal
Delayed Arguments
Objects that respond to a #evaluate_delayed_argument method are handled differently by TaskArguments. They are considered “delayed arguments”, that is arguments that are essentially not set yet, but can be evaluated later (usually at execution time) to determine the argument value.
These objects must follow the DelayedArgument interface
These delayed arguments are handled differently than “plain” arguments in a few ways:
-
standard hash access methods such as #[] will “hide” them, that is return nil instead of the object
-
they can be overriden once set using e.g. #[]= or #merge!, unlike “plain arguments”
In addition, Roby provides a mechanism to fine-tune the way delayed arguments are merged. The #semantic_merge! call delegates the merge to the delayed arguments, allowing for instance two default argument to be merged if they have the same value.
Defined Under Namespace
Classes: StaticArgumentWrapper
Instance Attribute Summary collapse
-
#task ⇒ Object
readonly
Returns the value of attribute task.
-
#values ⇒ Object
readonly
Returns the value of attribute values.
Class Method Summary collapse
-
.delayed_argument?(obj) ⇒ Boolean
Checks whether the given object is a delayed argument object.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
True if the arguments are equal.
-
#[](key) ⇒ Object?
Return a set value for the given key.
-
#[]=(key, value) ⇒ Object
Assigns a value to a given argument name.
-
#assigned?(key) ⇒ Boolean
Tests if a given argument has been assigned, that is either has a static value or has a delayed value object.
-
#assigned_arguments ⇒ Hash
Returns the set of arguments for which a proper value has been assigned.
-
#can_semantic_merge?(other_args) ⇒ Boolean
Checks whether self can be merged with other_args through #semantic_merge!.
- #dup ⇒ Object
- #each(&block) ⇒ Object
-
#each_assigned_argument {|name, arg| ... } ⇒ Object
Enumerates assigned arguments that are not delayed arguments.
-
#each_delayed_argument {|name, arg| ... } ⇒ Object
Enumerates delayed arguments.
-
#evaluate_delayed_arguments ⇒ Hash
Returns this argument set, but with the delayed arguments evaluated.
-
#force_merge!(hash) ⇒ Object
Merge a hash into the arguments, updating existing values.
-
#has_key?(key) ⇒ Boolean
deprecated
Deprecated.
use #key? instead
-
#initialize(task) ⇒ TaskArguments
constructor
A new instance of TaskArguments.
-
#key?(key) ⇒ Boolean
True if an argument with that name is assigned, be it a proper value or a delayed value object.
-
#keys ⇒ Object
The set of argument names that have been assigned so far, either with a proper object or a delayed value object.
-
#merge!(hash) ⇒ Object
Merge a hash into the arguments.
-
#pretty_print(pp) ⇒ Object
Pretty-prints this argument set.
-
#raw_get(key) ⇒ Object
Return the value stored for the given key as-is.
-
#semantic_merge!(other_args) ⇒ Object
Merging method that takes delayed arguments into account.
-
#semantic_merge_blockers(other_args) ⇒ {Symbol => [Object, Object]}
Return the set of arguments that won’t be merged by #semantic_merge!.
-
#set?(key) ⇒ Boolean
Tests if a given argument has been set with a proper value (not a delayed value object).
-
#slice(*args) ⇒ Object
Returns the listed set of arguments.
-
#static? ⇒ Boolean
True if all the set arguments are plain (not delayed) arguments.
- #to_hash ⇒ Object
- #to_s ⇒ Object
-
#update!(key, value) ⇒ Object
Updates the given argument, regardless of whether it is allowed or not.
-
#writable?(key, value) ⇒ Boolean
True if it is possible to write the given value to the given argument.
Methods included from DRoby::V5::TaskArgumentsDumper
Methods included from Enumerable
Constructor Details
#initialize(task) ⇒ TaskArguments
Returns a new instance of TaskArguments.
43 44 45 46 47 48 |
# File 'lib/roby/task_arguments.rb', line 43 def initialize(task) @task = task @static = true @values = {} super() end |
Instance Attribute Details
#task ⇒ Object (readonly)
Returns the value of attribute task.
41 42 43 |
# File 'lib/roby/task_arguments.rb', line 41 def task @task end |
#values ⇒ Object (readonly)
Returns the value of attribute values.
41 42 43 |
# File 'lib/roby/task_arguments.rb', line 41 def values @values end |
Class Method Details
.delayed_argument?(obj) ⇒ Boolean
Checks whether the given object is a delayed argument object
53 54 55 |
# File 'lib/roby/task_arguments.rb', line 53 def self.delayed_argument?(obj) obj.respond_to?(:evaluate_delayed_argument) end |
Instance Method Details
#==(other) ⇒ Boolean
True if the arguments are equal
Both proper values and delayed values have to be equal
146 147 148 |
# File 'lib/roby/task_arguments.rb', line 146 def ==(other) to_hash == other.to_hash end |
#[](key) ⇒ Object?
Return a set value for the given key
390 391 392 393 394 |
# File 'lib/roby/task_arguments.rb', line 390 def [](key) key = warn_deprecated_non_symbol_key(key) value = values[key] value unless TaskArguments.delayed_argument?(value) end |
#[]=(key, value) ⇒ Object
Assigns a value to a given argument name
The method validates that writing this argument value is allowed. Only values that have not been set, or have been set with a delayed argument, canb e updated
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/roby/task_arguments.rb', line 352 def []=(key, value) key = warn_deprecated_non_symbol_key(key) if writable?(key, value) if !value.droby_marshallable? raise NotMarshallable, "values used as task arguments must be "\ "marshallable, attempting to set #{key} to #{value} of "\ "class #{value.class}, which is not" elsif !task.read_write? raise OwnershipError, "cannot change the argument set of a task "\ "which is not owned #{task} is owned by #{task.owners} and "\ "#{task.plan} by #{task.plan.owners}" end if TaskArguments.delayed_argument?(value) @static = false elsif values.key?(key) && TaskArguments.delayed_argument?(values[key]) update_static = true end values[key] = value task.plan.log(:task_arguments_updated, task, key, value) if update_static @static = values.all? { |k, v| !TaskArguments.delayed_argument?(v) } end value else raise ArgumentError, "cannot override task argument #{key} as it is "\ "already set to #{values[key]}" end end |
#assigned?(key) ⇒ Boolean
Tests if a given argument has been assigned, that is either has a static value or has a delayed value object
131 132 133 |
# File 'lib/roby/task_arguments.rb', line 131 def assigned?(key) key?(key) end |
#assigned_arguments ⇒ Hash
Returns the set of arguments for which a proper value has been assigned
168 169 170 171 172 173 174 |
# File 'lib/roby/task_arguments.rb', line 168 def assigned_arguments result = {} each_assigned_argument do |k, v| result[k] = v end result end |
#can_semantic_merge?(other_args) ⇒ Boolean
Checks whether self can be merged with other_args through #semantic_merge!
259 260 261 |
# File 'lib/roby/task_arguments.rb', line 259 def can_semantic_merge?(other_args) semantic_merge_blockers(other_args).empty? end |
#dup ⇒ Object
121 122 123 |
# File 'lib/roby/task_arguments.rb', line 121 def dup self.to_hash end |
#each(&block) ⇒ Object
201 202 203 |
# File 'lib/roby/task_arguments.rb', line 201 def each(&block) values.each(&block) end |
#each_assigned_argument {|name, arg| ... } ⇒ Object
Enumerates assigned arguments that are not delayed arguments
181 182 183 184 185 186 187 |
# File 'lib/roby/task_arguments.rb', line 181 def each_assigned_argument return assigned_arguments unless block_given? each do |key, value| yield(key, value) unless TaskArguments.delayed_argument?(value) end end |
#each_delayed_argument {|name, arg| ... } ⇒ Object
Enumerates delayed arguments
193 194 195 196 197 198 199 |
# File 'lib/roby/task_arguments.rb', line 193 def each_delayed_argument return enum_for(__method__) unless block_given? each do |key, value| yield(key, value) if TaskArguments.delayed_argument?(value) end end |
#evaluate_delayed_arguments ⇒ Hash
Returns this argument set, but with the delayed arguments evaluated
399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'lib/roby/task_arguments.rb', line 399 def evaluate_delayed_arguments result = {} values.each do |key, val| if TaskArguments.delayed_argument?(val) catch(:no_value) do result[key] = val.evaluate_delayed_argument(task) end else result[key] = val end end result end |
#force_merge!(hash) ⇒ Object
Merge a hash into the arguments, updating existing values
Unlike #merge!, this will update existing values. You should not do it, unless you know what you’re doing.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 |
# File 'lib/roby/task_arguments.rb', line 420 def force_merge!(hash) hash.each do |key, value| unless value.droby_marshallable? raise NotMarshallable, "values used as task arguments must "\ "be marshallable, attempting to set #{key} to #{value}, "\ "which is not" end end if task.plan&.executable? values.merge!(hash) do |k, _, v| task.plan.log(:task_arguments_updated, task, k, v) v end else values.merge!(hash) end @static = values.all? { |k, v| !TaskArguments.delayed_argument?(v) } end |
#has_key?(key) ⇒ Boolean
use #key? instead
79 80 81 |
# File 'lib/roby/task_arguments.rb', line 79 def has_key?(key) values.key?(key) end |
#key?(key) ⇒ Boolean
True if an argument with that name is assigned, be it a proper value or a delayed value object. This is an alias to #assigned?
This is implemented to be consistent with the Hash API. However, because of the semantics of delayed value objects, prefer #set? and #assigned?
89 90 91 |
# File 'lib/roby/task_arguments.rb', line 89 def key?(key) values.key?(key) end |
#keys ⇒ Object
The set of argument names that have been assigned so far, either with a proper object or a delayed value object
95 96 97 |
# File 'lib/roby/task_arguments.rb', line 95 def keys values.keys end |
#merge!(hash) ⇒ Object
Merge a hash into the arguments
Only arguments that are unset or are currently delayed arguments (such as default arguments) can be updated. If the caller tries ot update other arguments, the method will raise
449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 |
# File 'lib/roby/task_arguments.rb', line 449 def merge!(hash) hash.each do |key, value| unless value.droby_marshallable? raise NotMarshallable, "values used as task arguments must "\ "be marshallable, attempting to set #{key} to #{value}, "\ "which is not" end end values.merge!(hash) do |key, old, new| if old == new then old elsif writable?(key, new) task.plan.log(:task_arguments_updated, task, key, new) new else raise ArgumentError, "cannot override task argument #{key}: "\ "trying to replace #{old} by #{new}" end end @static = values.all? { |k, v| !TaskArguments.delayed_argument?(v) } self end |
#pretty_print(pp) ⇒ Object
Pretty-prints this argument set
151 152 153 154 155 156 157 |
# File 'lib/roby/task_arguments.rb', line 151 def pretty_print(pp) pp.seplist(values) do |keyvalue| key, value = *keyvalue pp.text "#{key}: " value.pretty_print(pp) end end |
#raw_get(key) ⇒ Object
Return the value stored for the given key as-is
Unlike #[], it does not filter out delayed arguments
74 75 76 |
# File 'lib/roby/task_arguments.rb', line 74 def raw_get(key) values[key] end |
#semantic_merge!(other_args) ⇒ Object
Merging method that takes delayed arguments into account
Unlike #merge, this method will let delayed arguments “merge themselves”, by delegating to their #merge method. It allows to e.g. propagating default arguments in the merge chain if they are the same.
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/roby/task_arguments.rb', line 271 def semantic_merge!(other_args) current_values = values.dup other_task = other_args.task values.merge!(other_args.values) do |name, arg, other_arg| self_delayed = TaskArguments.delayed_argument?(arg) other_delayed = TaskArguments.delayed_argument?(other_arg) if self_delayed && other_delayed if !(arg.strong? ^ other_arg.strong?) arg.merge(task, other_task, other_arg) elsif arg.strong? arg else other_arg end elsif self_delayed if arg.strong? arg.merge(task, other_task, StaticArgumentWrapper.new(other_arg)) else other_arg end elsif other_delayed if other_arg.strong? other_arg.merge(other_task, task, StaticArgumentWrapper.new(arg)) else arg end else arg end end current_values.each do |k, v| if (new_value = values[k]) != v task.plan.log(:task_arguments_updated, task, k, new_value) end end (values.keys - current_values.keys).each do |new_k| task.plan.log(:task_arguments_updated, task, new_k, values[new_k]) end @static = values.each_value.none? { |v| TaskArguments.delayed_argument?(v) } self end |
#semantic_merge_blockers(other_args) ⇒ {Symbol => [Object, Object]}
Return the set of arguments that won’t be merged by #semantic_merge!
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/roby/task_arguments.rb', line 217 def semantic_merge_blockers(other_args) blockers = values.find_all do |name, arg| next(false) unless other_args.key?(name) other_arg = other_args.values[name] self_delayed = TaskArguments.delayed_argument?(arg) other_delayed = TaskArguments.delayed_argument?(other_arg) next(arg != other_arg) unless self_delayed || other_delayed if self_delayed && other_delayed if !(arg.strong? ^ other_arg.strong?) !arg.can_merge?(task, other_args.task, other_arg) else false end elsif self_delayed if arg.strong? !arg.can_merge?(task, other_args.task, StaticArgumentWrapper.new(other_arg)) else false end elsif other_delayed if other_arg.strong? !other_arg.can_merge?(other_args.task, task, StaticArgumentWrapper.new(arg)) else false end end end blockers.each_with_object({}) do |(name, self_obj), h| h[name] = [self_obj, other_args[name]] end end |
#set?(key) ⇒ Boolean
Tests if a given argument has been set with a proper value (not a delayed value object)
137 138 139 |
# File 'lib/roby/task_arguments.rb', line 137 def set?(key) key?(key) && !TaskArguments.delayed_argument?(values.fetch(key)) end |
#slice(*args) ⇒ Object
Returns the listed set of arguments
Delayed arguments are evaluated before it is sliced
117 118 119 |
# File 'lib/roby/task_arguments.rb', line 117 def slice(*args) evaluate_delayed_arguments.slice(*args) end |
#static? ⇒ Boolean
True if all the set arguments are plain (not delayed) arguments
58 59 60 |
# File 'lib/roby/task_arguments.rb', line 58 def static? @static end |
#to_hash ⇒ Object
125 126 127 |
# File 'lib/roby/task_arguments.rb', line 125 def to_hash values.dup end |
#to_s ⇒ Object
159 160 161 |
# File 'lib/roby/task_arguments.rb', line 159 def to_s values.sort_by(&:first).map { |k, v| "#{k}: #{v}" }.join(", ") end |
#update!(key, value) ⇒ Object
Updates the given argument, regardless of whether it is allowed or not
322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 |
# File 'lib/roby/task_arguments.rb', line 322 def update!(key, value) if values.key?(key) current_value = values[key] is_updated = (current_value != value) update_static = TaskArguments.delayed_argument?(current_value) else is_updated = true end values[key] = value if is_updated task.plan.log(:task_arguments_updated, task, key, value) end if TaskArguments.delayed_argument?(value) @static = false elsif update_static @static = values.all? { |k, v| !TaskArguments.delayed_argument?(v) } end end |
#writable?(key, value) ⇒ Boolean
True if it is possible to write the given value to the given argument
103 104 105 106 107 108 109 110 |
# File 'lib/roby/task_arguments.rb', line 103 def writable?(key, value) if key?(key) !task.model.arguments.include?(key) || TaskArguments.delayed_argument?(values[key]) else true end end |