Class: Tap::Support::Audit
Overview
Audit provides a way to track the values (inputs and results) passed among tasks or, more generally, any Executable. Audits allow you to track inputs as they make their way through a workflow, and have great utility in debugging and record keeping.
During execution, the inputs to a task are used to initialize an Audit. These inputs are the original value of the audit and mark the begining of an audit trail; every task adds to the trail by recording it’s result and itself as the ‘source’ of the result.
Audits can take any object as a source, so for illustration lets use some symbols:
# initialize a new audit
a = Audit.new(1, nil)
# record some values
a._record(:A, 2)
a._record(:B, 3)
Now you can pull up the source and value trails, as well as the current and original values:
a._source_trail # => [nil, :A, :B]
a._value_trail # => [1, 2, 3]
a._original # => 1
a._original_source # => nil
a._current # => 3
a._current_source # => :B
Merges are supported by using an array of the merged trails (actually an AuditMerge) as the source, and an array of the merged values as the original value.
b = Audit.new(10, nil)
b._record(:C, 11)
b._record(:D, 12)
c = Audit.merge(a, b)
c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]] ]
c._value_trail # => [ [[1,2,3], [10, 11, 12]] ]
c._current # => [3, 12]
c._record(:E, "a string value")
c._record(:F, {'a' => 'hash value'})
c._record(:G, ['an', 'array', 'value'])
c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]], :E, :F, :G]
c._value_trail # => [ [[1,2,3], [10, 11, 12]], "a string value", {'a' => 'hash value'}, ['an', 'array', 'value']]
Audit supports forks by duplicating the source and value trails. Forks can be developed independently. Audits are also forked during a merge; notice the additional record in ‘a’ doesn’t change the source trail for ‘c’:
a1 = a._fork
a._record(:X, -1)
a1._record(:Y, -2)
a._source_trail # => [nil, :A, :B, :X]
a1._source_trail # => [nil, :A, :B, :Y]
c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]], :E, :F, :G]
The data structure for an audit gets nasty after a few merges because the lead array gets more and more nested. Audit provides iterators to help gain access, as well as a printing method to visualize the audit trail:
c._to_s
# =>
# o-[] 1
# o-[A] 2
# o-[B] 3
# |
# | o-[] 10
# | o-[C] 11
# | o-[D] 12
# | |
# `-`-o-[E] "a string value"
# o-[F] {"a"=>"hash value"}
# o-[G] ["an", "array", "value"]
In practice, tasks are recored as sources. Thus source trails can be used
to access task configurations and other information that may be useful when creating reports or making workflow decisions.
– TODO: Track nesting level of ams; see if you can hook this into the _to_s process to make extraction/presentation of audits more managable.
Create a FirstLastArray to minimize the audit data collected. Allow different audit modes:
-
full ([] both)
-
source_only (fl value)
-
minimal (fl source and value)
Try to work a _to_s that doesn’t repeat the same audit twice. Think about a format like:
|
------|-----+
| |
------|-----|-----+
| | |
`-----`-----`-o-[j] j5
Constant Summary collapse
- AUDIT_NIL =
An arbitrary object used to identify when no inputs have been provided to Audit.new. (nil cannot be used since nil is a valid initial value)
Object.new
Instance Attribute Summary collapse
-
#_sources ⇒ Object
readonly
An array of the sources in self.
-
#_values ⇒ Object
readonly
An array of the values in self.
Class Method Summary collapse
-
.merge(*audits) ⇒ Object
Creates a new Audit by merging the input audits.
Instance Method Summary collapse
-
#==(another) ⇒ Object
Returns true if the _sources and _values for self are equal to those of another.
-
#_collect_records(&block) ⇒ Object
:yields: source, value.
-
#_current ⇒ Object
The current (ie last) value recorded in the Audit.
-
#_current_source ⇒ Object
The current (ie last) source recorded in the Audit.
-
#_each_record(merge_level = 0, merge_index = 0, &block) ⇒ Object
:yields: source, value, merge_level, merge_index, index.
-
#_fork ⇒ Object
Produces a new Audit with duplicate sources and values, suitable for independent development.
-
#_iterate ⇒ Object
Produces a fork of self for each item in the current value (_current).
-
#_merge(*audits) ⇒ Object
Creates a new Audit by merging self and the input audits, using Audit#merge.
-
#_original ⇒ Object
The original value used to initialize the Audit.
-
#_original_source ⇒ Object
The original source used to initialize the Audit.
-
#_record(source, value) ⇒ Object
Records the next value produced by the source.
-
#_source_trail ⇒ Object
Searches back and recursively (if the source is an audit) collects all sources for the current value.
-
#_to_s ⇒ Object
A kind of pretty-print for Audits.
-
#_value_trail ⇒ Object
Searches back and recursively (if the source is an audit) collects all values leading to the current value.
-
#initialize(value = AUDIT_NIL, source = nil) ⇒ Audit
constructor
A new audit takes a value and/or source.
Constructor Details
#initialize(value = AUDIT_NIL, source = nil) ⇒ Audit
A new audit takes a value and/or source. A nil source is typically given for the original value.
187 188 189 190 191 192 |
# File 'lib/tap/support/audit.rb', line 187 def initialize(value=AUDIT_NIL, source=nil) @_sources = [] @_values = [] _record(source, value) unless value == AUDIT_NIL end |
Instance Attribute Details
#_sources ⇒ Object
An array of the sources in self
175 176 177 |
# File 'lib/tap/support/audit.rb', line 175 def _sources @_sources end |
#_values ⇒ Object
An array of the values in self
178 179 180 |
# File 'lib/tap/support/audit.rb', line 178 def _values @_values end |
Class Method Details
.merge(*audits) ⇒ Object
Creates a new Audit by merging the input audits. The value of the new Audit will be an array of the _current values of the inputs. The source will be an AuditMerge whose values are forks of the inputs. Non-Audit sources may be provided; they are initialized to Audits before merging.
a = Audit.new
a._record(:a, 'a')
b = Audit.new
b._record(:b, 'b')
c = Audit.merge(a, b, 1)
c._record(:c, 'c')
c._values # => [['a','b', 1], 'c']
c._sources # => [AuditMerge[a, b, Audit.new(1)], :c]
If no audits are provided, merge returns a new Audit. If only one audit is provided, merge returns a fork of that audit.
160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/tap/support/audit.rb', line 160 def merge(*audits) case audits.length when 0 then Audit.new when 1 then audits[0]._fork else sources = AuditMerge.new audits.each {|a| sources << (a.kind_of?(Audit) ? a._fork : Audit.new(a)) } values = audits.collect {|a| a.kind_of?(Audit) ? a._current : a} Audit.new(values, sources) end end |
Instance Method Details
#==(another) ⇒ Object
Returns true if the _sources and _values for self are equal to those of another.
293 294 295 |
# File 'lib/tap/support/audit.rb', line 293 def ==(another) another.kind_of?(Audit) && self._sources == another._sources && self._values == another._values end |
#_collect_records(&block) ⇒ Object
:yields: source, value
249 250 251 252 253 254 255 |
# File 'lib/tap/support/audit.rb', line 249 def _collect_records(&block) # :yields: source, value collection = [] 0.upto(_sources.length-1) do |i| collection << collect_records(_sources[i], _values[i], &block) end collection end |
#_current ⇒ Object
The current (ie last) value recorded in the Audit
223 224 225 |
# File 'lib/tap/support/audit.rb', line 223 def _current _values.last end |
#_current_source ⇒ Object
The current (ie last) source recorded in the Audit
233 234 235 |
# File 'lib/tap/support/audit.rb', line 233 def _current_source _sources.last end |
#_each_record(merge_level = 0, merge_index = 0, &block) ⇒ Object
:yields: source, value, merge_level, merge_index, index
257 258 259 260 261 |
# File 'lib/tap/support/audit.rb', line 257 def _each_record(merge_level=0, merge_index=0, &block) # :yields: source, value, merge_level, merge_index, index 0.upto(_sources.length-1) do |i| each_record(_sources[i], _values[i], merge_level, merge_index, i, &block) end end |
#_fork ⇒ Object
Produces a new Audit with duplicate sources and values, suitable for independent development.
270 271 272 273 274 275 |
# File 'lib/tap/support/audit.rb', line 270 def _fork a = Audit.new a._sources = _sources.dup a._values = _values.dup a end |
#_iterate ⇒ Object
Produces a fork of self for each item in the current value (_current). Iterate is useful for developing each item of (say) an array along different paths.
Records the next value of each fork as [item, AuditIterate.new(<index of item>)].
Raises an error if _current does not respond to each.
283 284 285 286 287 288 289 |
# File 'lib/tap/support/audit.rb', line 283 def _iterate = [] _current.each do |value| << _fork._record(AuditIterate.new(.length), value) end end |
#_merge(*audits) ⇒ Object
Creates a new Audit by merging self and the input audits, using Audit#merge.
264 265 266 |
# File 'lib/tap/support/audit.rb', line 264 def _merge(*audits) Audit.merge(self, *audits) end |
#_original ⇒ Object
The original value used to initialize the Audit
218 219 220 |
# File 'lib/tap/support/audit.rb', line 218 def _original _values.first end |
#_original_source ⇒ Object
The original source used to initialize the Audit
228 229 230 |
# File 'lib/tap/support/audit.rb', line 228 def _original_source _sources.first end |
#_record(source, value) ⇒ Object
Records the next value produced by the source. When an audit is passed as a value, record will record the current value of the audit. Record will similarly resolve every audit in an array containing audits.
Example:
a = Audit.new(1)
b = Audit.new(2)
c = Audit.new(3)
c.record(:a, a)
c.sources # => [:a]
c.values # => [1]
c.record(:ab, [a,b])
c.sources # => [:a, :ab]
c.values # => [1, [1, 2]]
211 212 213 214 215 |
# File 'lib/tap/support/audit.rb', line 211 def _record(source, value) _sources << source _values << value self end |
#_source_trail ⇒ Object
Searches back and recursively (if the source is an audit) collects all sources for the current value.
239 240 241 |
# File 'lib/tap/support/audit.rb', line 239 def _source_trail _collect_records {|source, value| source} end |
#_to_s ⇒ Object
A kind of pretty-print for Audits. See the example in the overview.
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# File 'lib/tap/support/audit.rb', line 298 def _to_s # TODO -- find a way to avoid repeating groups group = [] groups = [group] extended_groups = [groups] group_merges = [] extended_group_merges = [] current_level = nil current_index = nil _each_record do |source, value, merge_level, merge_index, index| source_str, value_str = if block_given? yield(source, value) else [source, value == nil ? '' : PP.singleline_pp(value, '')] end if !group.empty? && (merge_level != current_level || index == 0) unless merge_level <= current_level groups = [] extended_groups << groups end group = [] groups << group if merge_level < current_level if merge_index == 0 extended_group_merges << group.object_id end unless index == 0 group_merges << group.object_id end end end group << "o-[#{source_str}] #{value_str}" current_level = merge_level current_index = merge_index end lines = [] group_prefix = "" extended_groups.each do |ext_groups| indentation = 0 ext_groups.each_with_index do |ext_group, group_num| ext_group.each_with_index do |line, line_num| if line_num == 0 unless lines.empty? lines << group_prefix + " " * indentation + "| " * (group_num-indentation) end if group_merges.include?(ext_group.object_id) lines << group_prefix + " " * indentation + "`-" * (group_num-indentation) + line indentation = group_num if extended_group_merges.include?(ext_group.object_id) lines.last.gsub!(/\| \s*/) {|match| "`-" + "-" * (match.length - 2)} group_prefix.gsub!(/\| /, " ") end next end end lines << group_prefix + " " * indentation + "| " * (group_num-indentation) + line end end group_prefix += " " * (ext_groups.length-1) + "| " end lines.join("\n") + "\n" end |
#_value_trail ⇒ Object
Searches back and recursively (if the source is an audit) collects all values leading to the current value.
245 246 247 |
# File 'lib/tap/support/audit.rb', line 245 def _value_trail _collect_records {|source, value| value} end |