scout-essentials 1.8.8 → 1.9.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +26 -12
  3. data/README.md +83 -112
  4. data/VERSION +1 -1
  5. data/doc/Improvements.md +226 -0
  6. data/doc/StartHere.md +122 -0
  7. data/doc/developer/AnnotationSystem.md +184 -0
  8. data/doc/developer/Architecture.md +147 -0
  9. data/doc/developer/Configuration.md +238 -0
  10. data/doc/developer/CoreUtilities.md +265 -0
  11. data/doc/developer/DesignPrinciples.md +129 -0
  12. data/doc/developer/ErrorHandling.md +203 -0
  13. data/doc/developer/LockingAndConcurrency.md +157 -0
  14. data/doc/developer/PathResolution.md +200 -0
  15. data/doc/developer/PersistenceAndResources.md +119 -0
  16. data/doc/developer/StreamingModel.md +236 -0
  17. data/doc/user/AnnotatingData.md +202 -0
  18. data/doc/user/CachingResults.md +183 -0
  19. data/doc/user/CommandLineOptions.md +189 -0
  20. data/doc/user/Cookbook.md +211 -0
  21. data/doc/user/HandlingStreams.md +236 -0
  22. data/doc/user/LoggingAndProgress.md +158 -0
  23. data/doc/user/ProducingResources.md +177 -0
  24. data/doc/user/RemoteData.md +157 -0
  25. data/doc/user/RunningCommands.md +218 -0
  26. data/doc/user/WorkingWithFiles.md +217 -0
  27. data/lib/scout/cmd.rb +343 -40
  28. data/lib/scout/concurrent_stream.rb +14 -1
  29. data/lib/scout/indiferent_hash.rb +1 -1
  30. data/lib/scout/log/fingerprint.rb +13 -8
  31. data/lib/scout/log/progress/report.rb +1 -1
  32. data/lib/scout/log.rb +4 -1
  33. data/lib/scout/misc/format.rb +24 -0
  34. data/lib/scout/named_array.rb +1 -1
  35. data/lib/scout/open/stream.rb +2 -2
  36. data/lib/scout/open/util.rb +4 -0
  37. data/lib/scout/path/find.rb +3 -2
  38. data/lib/scout/persist.rb +14 -10
  39. data/research/annotations-data-analysis.md +206 -0
  40. data/research/behavior-probes.md +1925 -0
  41. data/research/commands-streaming-analysis.md +272 -0
  42. data/research/design-philosophy-analysis.md +383 -0
  43. data/research/doc-audit-findings.md +294 -0
  44. data/research/ecosystem-attribution.md +118 -0
  45. data/research/implementation-inventory-core.md +1029 -0
  46. data/research/implementation-inventory-open.md +417 -0
  47. data/research/implementation-inventory-path-persist-resource.md +774 -0
  48. data/research/io-paths-analysis.md +228 -0
  49. data/research/persistence-resources-analysis.md +244 -0
  50. data/research/synthesis-report.md +80 -0
  51. data/scout-essentials.gemspec +37 -15
  52. data/test/scout/test_cmd.rb +411 -0
  53. metadata +36 -14
  54. data/doc/Annotation.md +0 -352
  55. data/doc/CMD.md +0 -363
  56. data/doc/ConcurrentStream.md +0 -163
  57. data/doc/IndiferentHash.md +0 -240
  58. data/doc/Log.md +0 -235
  59. data/doc/NamedArray.md +0 -174
  60. data/doc/Open.md +0 -331
  61. data/doc/Path.md +0 -217
  62. data/doc/Persist.md +0 -214
  63. data/doc/Resource.md +0 -229
  64. data/doc/SimpleOPT.md +0 -236
  65. data/doc/TmpFile.md +0 -154
@@ -21,7 +21,7 @@ module Path
21
21
  file
22
22
  end
23
23
 
24
- def self.caller_lib_dir(file = nil, relative_to = ['lib', 'bin'])
24
+ def self.caller_lib_dir(file = nil, relative_to = ['lib', 'bin', 'README.md'])
25
25
 
26
26
  file = caller_file(file)
27
27
 
@@ -85,6 +85,7 @@ module Path
85
85
  def self.path_maps
86
86
  @@path_maps ||= IndiferentHash.setup({
87
87
  :current => "{PWD}/{TOPLEVEL}/{SUBPATH}",
88
+ :home => "{HOME}/{TOPLEVEL}/{PKGDIR}/{SUBPATH}",
88
89
  :user => "{HOME}/.{PKGDIR}/{TOPLEVEL}/{SUBPATH}",
89
90
  :global => '/{TOPLEVEL}/{PKGDIR}/{SUBPATH}',
90
91
  :usr => '/usr/{TOPLEVEL}/{PKGDIR}/{SUBPATH}',
@@ -100,7 +101,7 @@ module Path
100
101
  end
101
102
 
102
103
  def self.basic_map_order
103
- @@basic_map_order ||= %w(current workflow user local global usr lib fast cache bulk).collect{|m| m.to_sym }
104
+ @@basic_map_order ||= %w(current workflow user home local global usr lib fast cache bulk).collect{|m| m.to_sym }
104
105
  end
105
106
 
106
107
  def self.map_order
data/lib/scout/persist.rb CHANGED
@@ -118,16 +118,20 @@ module Persist
118
118
  pres = Persist.save(res, file, type)
119
119
  res = pres unless pres.nil?
120
120
  end
121
- rescue Exception
122
- Thread.handle_interrupt(Exception => :never) do
123
- if Open.exist?(file)
124
- Log.debug "Failed persistence #{file} - erasing"
125
- Open.rm_rf file
126
- else
127
- Log.debug "Failed persistence #{file}"
128
- end
129
- end unless DontPersist === $!
130
- raise $! unless options[:canfail]
121
+ rescue Exception => e
122
+ begin
123
+ Thread.handle_interrupt(Exception => :never) do
124
+ if Open.exist?(file)
125
+ Log.debug "Failed persistence #{file} - erasing"
126
+ Open.rm_rf file
127
+ else
128
+ Log.debug "Failed persistence #{file}"
129
+ end
130
+ end unless DontPersist === e
131
+ rescue
132
+ ensure
133
+ raise e unless options[:canfail]
134
+ end
131
135
  end
132
136
 
133
137
  if TrueClass === no_load
@@ -0,0 +1,206 @@
1
+ # Investigation: Annotations, Data Structures, and Options
2
+
3
+ **Status:** Non-normative investigation artifact. May be outdated.
4
+
5
+ ## Scope
6
+ Annotation, NamedArray, IndiferentHash, CaseInsensitiveHash, SimpleOPT (SOPT).
7
+
8
+ ---
9
+
10
+ ## Annotation system
11
+
12
+ ### What it is
13
+ A lightweight, non-invasive system for attaching typed, named metadata
14
+ (instance variables with accessors) to **any** Ruby object — strings, arrays,
15
+ hashes — without subclassing or wrapping. The object's class never changes.
16
+
17
+ ### Core mechanism
18
+ 1. A module calls `extend Annotation` to become an "annotation module."
19
+ 2. `extend Annotation` triggers `Annotation.extended(base)`, which:
20
+ - Initializes `@annotations = []` on the module.
21
+ - `include`s `AnnotatedObject` into the module (providing `annotate`,
22
+ `purge`, `annotation_hash`, etc.).
23
+ - `extend`s `AnnotationModule` onto the module (providing `annotation`,
24
+ `setup`, `annotations`).
25
+ 3. `annotation :attr1, :attr2` calls `attr_accessor` on each and records them
26
+ in `@annotations`.
27
+ 4. `MyMod.setup(obj, ...)` extends `obj` with `MyMod`, copies annotation types
28
+ into `obj.annotation_types`, and sets instance variables for each annotation
29
+ attribute.
30
+
31
+ ### setup() parameter resolution
32
+ `setup` is flexible about arguments:
33
+ - Positional: `MyMod.setup(obj, val1, val2)` → `@attr1 = val1, @attr2 = val2`
34
+ (via `attrs.zip(rest)`).
35
+ - Hash: `MyMod.setup(obj, {attr1: val1, attr2: val2})` → used directly as pairs
36
+ when keys match annotation names.
37
+ - Block: `setup(*args) { |obj| ... }` — block receives the object, args are rest.
38
+ - Frozen objects are dup'd before extending.
39
+
40
+ ### AnnotatedObject mixin
41
+ Provides:
42
+ - `annotation_types` — array of modules applied to this object.
43
+ - `base_type` — last annotation type applied (the "primary" type).
44
+ - `annotation_hash` — `{ name => value }` for all annotated attributes.
45
+ - `annotate(other)` — copies all annotations to another object.
46
+ - `purge` — returns a clean duplicate with all annotation ivars removed.
47
+ - `annotation_id` / `id` — digest of `[self, annotation_info]`.
48
+ - `make_array` — wraps self in a single-element array and extends with AnnotatedArray.
49
+
50
+ ### AnnotatedArray mixin
51
+ When an annotated object is itself an Array, `AnnotatedArray` provides:
52
+ - Automatic annotation propagation: `[]`, `first`, `last`, `each`, `each_with_index`,
53
+ `select`, `collect`, `compact`, `uniq`, `flatten`, `reverse`, `sort_by` —
54
+ each returns results annotated with the parent's types.
55
+ - `annotate_item(obj, pos)` — sets `container` and `container_index` on each
56
+ item (via `AnnotatedArrayItem`), then annotates.
57
+ - `subset(list)`, `remove(list)` — set operations that preserve annotations.
58
+
59
+ ### Key insight: the propagate-through-enumeration pattern
60
+ AnnotatedArray overrides Array enumeration methods so that **every element
61
+ extracted from an annotated array inherits the parent's annotations**. This
62
+ is why you can do:
63
+ ```ruby
64
+ list = Gene.setup(["BRCA1", "TP53"], :organism, "Hsa")
65
+ list.first.organism # => "Hsa" (inherited from parent)
66
+ ```
67
+
68
+ ---
69
+
70
+ ## NamedArray
71
+
72
+ ### What it is
73
+ An extension of Annotation for Arrays that adds **named fields** and name-based
74
+ accessors. Built directly on the Annotation system.
75
+
76
+ ### Annotations
77
+ - `fields` — ordered list of field names for each array position.
78
+ - `key` — optional primary key field name.
79
+
80
+ ### Key methods
81
+ - `[](name_or_index)` — resolves a name to position via `identify_name`, then
82
+ delegates to `Array#[]`.
83
+ - `[]=(name_or_index, value)` — same resolution for assignment.
84
+ - `to_hash` — returns an `IndiferentHash` mapping field names → values.
85
+ - `concat(hash_or_array)` — if given a Hash, appends its values and adds keys
86
+ to fields.
87
+ - `method_missing` — provides getter accessors for field names (`a.foo`).
88
+
89
+ ### Name resolution (identify_name)
90
+ - `nil` → 0
91
+ - Integer/Range → returned as-is
92
+ - Symbol `:key` → returns `:key` sentinel
93
+ - Otherwise: exact match, then numeric-as-index, then fuzzy match
94
+ (parentheses containment, space-prefix).
95
+ - `strict: true` disables fuzzy matching.
96
+
97
+ ### Class helpers
98
+ - `zip_fields(array)` — transpose a list-of-lists.
99
+ - `add_zipped(source, new)` — incrementally merge zipped lists.
100
+ - `field_match(field, name)` — fuzzy matching predicate.
101
+
102
+ ---
103
+
104
+ ## IndiferentHash
105
+
106
+ ### What it is
107
+ A module mixin for Hash instances that makes key access indifferent to
108
+ String vs Symbol. Also includes a rich set of options-processing utilities.
109
+
110
+ ### Core behavior
111
+ - `setup(hash)` extends a single hash instance.
112
+ - `[](key)` — tries exact key, then alternate form (symbol ↔ string).
113
+ - `[]=(key, value)` — deletes any existing variant first, then sets.
114
+ - `include?(key)` — checks both forms.
115
+ - Nested hashes are auto-extended on read.
116
+ - `merge`, `deep_merge`, `slice`, `except`, `values_at`, `delete` — all
117
+ form-indifferent.
118
+ - `clean_version` — returns a plain Hash with stringified keys.
119
+
120
+ ### Options utilities (IndiferentHash::Options)
121
+ - `add_defaults(options, defaults)` — adds missing keys.
122
+ - `process_options(hash, *keys)` — extract-and-remove keys, with defaults.
123
+ - `pull_keys(hash, prefix)` — extract `prefix_*` keys into a new hash.
124
+ - `zip2hash(list1, list2)` — zip two lists into an IndiferentHash.
125
+ - `positional2hash(keys, *values)` — convert positional args to a hash.
126
+ - `hash2string` / `string2hash` — serialize/parse simple hashes.
127
+ - `parse_options(str)` — parse shell-like `key=value` strings.
128
+ - `print_options(options)` — serialize to space-separated string.
129
+
130
+ ### CaseInsensitiveHash
131
+ Separate mixin: `setup(hash)` makes string key lookup case-insensitive
132
+ by maintaining a `original_key_by_downcase` map.
133
+
134
+ ---
135
+
136
+ ## SimpleOPT (SOPT)
137
+
138
+ ### What it is
139
+ A lightweight command-line option parser with:
140
+ - A compact DSL for declaring options.
141
+ - `--long` / `-short` / `--key=value` parsing.
142
+ - Boolean and string-typed options.
143
+ - Usage/help text generation.
144
+
145
+ ### Declaration styles
146
+ 1. `SOPT.parse("-f--first* first arg")` — compact definition string.
147
+ 2. `SOPT.register(short, long, asterisk, description)` — explicit.
148
+ 3. `SOPT.setup(heredoc)` — parse a help-text heredoc, register options,
149
+ and auto-consume ARGV.
150
+
151
+ ### Option format
152
+ `-short--long[*] description`
153
+ - `*` marks string-valued; absence marks boolean.
154
+ - Short is optional (auto-generated if nil/true).
155
+
156
+ ### Parsing (consume)
157
+ - `SOPT.consume(args = ARGV)` scans tokens, removes recognized options.
158
+ - Returns an IndiferentHash with symbol keys.
159
+ - Accumulates into `SOPT.GOT_OPTIONS`.
160
+
161
+ ### Documentation generation
162
+ - `SOPT.doc` — full manpage-style help.
163
+ - `SOPT.input_format` — single-option usage fragment.
164
+ - `SOPT.usage` — print doc and exit.
165
+
166
+ ### Gotchas
167
+ - `input_defaults` is **documentation-only**; parser does not apply defaults.
168
+ - Shortcut auto-generation skips punctuation (`.`, `-`, `_`).
169
+ - Boolean false must be `--flag=false` or a `false`/`F`/`no` token.
170
+
171
+ ---
172
+
173
+ ## Cross-module interactions
174
+
175
+ - **NamedArray depends on Annotation** — extends Annotation, declares
176
+ `:fields` and `:key`.
177
+ - **NamedArray.to_hash depends on IndiferentHash** — returns IndiferentHash.
178
+ - **SOPT depends on IndiferentHash** — parsed options are IndiferentHash;
179
+ `parse_options` and `print_options` from IndiferentHash::Options are used.
180
+ - **SOPT uses Log** — for colored output in doc generation.
181
+ - **Annotation.purge recursively purges** — handles nested Arrays and Hashes.
182
+
183
+ ---
184
+
185
+ ## Gotchas and warnings
186
+
187
+ 1. **Annotation.setup with a single Hash arg and multiple attributes**: the
188
+ condition `attrs.length != 1` is buggy — it should be `attrs.length == 1`
189
+ (line in annotation_module.rb). This can cause unexpected behavior when
190
+ setting up with a hash where keys don't match attribute names.
191
+ 2. **IndiferentHash#[] with default/default_proc**: if the hash has a default
192
+ and the key is not in `keys`, the default is returned **without** trying
193
+ the alternate form. This can silently mask symbol/string mismatches.
194
+ 3. **SOPT fix_shortcut may return nil** — if no unique shortcut can be found,
195
+ the option is registered with no shortcut. No error is raised.
196
+ 4. **AnnotatedArray collect vs map**: `collect` is overridden but `map` is
197
+ not explicitly handled. In Ruby `map` is an alias for `collect`, so it
198
+ should work, but the override uses `inject` rather than `super`, which
199
+ loses any block-passing nuance.
200
+ 5. **NamedArray#method_missing provides getters only** — no setters. Use `[]=`.
201
+ 6. **IndiferentHash#keys_to_sym!** uses `rescue` for failed conversions,
202
+ silently skipping unconvertible keys.
203
+ 7. **Annotation on frozen objects**: setup dup's frozen objects, but callers
204
+ may not expect the returned object to be a different instance.
205
+ 8. **Pretty print misspelling**: the method is `prety_print` (one 't') in
206
+ some places — check carefully.