scout-essentials 1.8.7 → 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 (69) 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/digest.rb +6 -5
  34. data/lib/scout/misc/format.rb +24 -0
  35. data/lib/scout/named_array.rb +1 -1
  36. data/lib/scout/open/stream.rb +2 -2
  37. data/lib/scout/open/util.rb +8 -4
  38. data/lib/scout/open.rb +3 -3
  39. data/lib/scout/path/find.rb +3 -2
  40. data/lib/scout/persist.rb +14 -10
  41. data/lib/scout/resource/produce.rb +9 -1
  42. data/research/annotations-data-analysis.md +206 -0
  43. data/research/behavior-probes.md +1925 -0
  44. data/research/commands-streaming-analysis.md +272 -0
  45. data/research/design-philosophy-analysis.md +383 -0
  46. data/research/doc-audit-findings.md +294 -0
  47. data/research/ecosystem-attribution.md +118 -0
  48. data/research/implementation-inventory-core.md +1029 -0
  49. data/research/implementation-inventory-open.md +417 -0
  50. data/research/implementation-inventory-path-persist-resource.md +774 -0
  51. data/research/io-paths-analysis.md +228 -0
  52. data/research/persistence-resources-analysis.md +244 -0
  53. data/research/synthesis-report.md +80 -0
  54. data/scout-essentials.gemspec +37 -15
  55. data/test/scout/open/test_remote.rb +1 -2
  56. data/test/scout/test_cmd.rb +411 -0
  57. metadata +36 -14
  58. data/doc/Annotation.md +0 -352
  59. data/doc/CMD.md +0 -363
  60. data/doc/ConcurrentStream.md +0 -163
  61. data/doc/IndiferentHash.md +0 -240
  62. data/doc/Log.md +0 -235
  63. data/doc/NamedArray.md +0 -174
  64. data/doc/Open.md +0 -331
  65. data/doc/Path.md +0 -217
  66. data/doc/Persist.md +0 -214
  67. data/doc/Resource.md +0 -229
  68. data/doc/SimpleOPT.md +0 -236
  69. data/doc/TmpFile.md +0 -154
@@ -0,0 +1,228 @@
1
+ # Investigation: File I/O, Paths, and Temporary Files
2
+
3
+ **Status:** Non-normative investigation artifact. May be outdated.
4
+
5
+ ## Scope
6
+ Open, Path, TmpFile, and their interactions.
7
+
8
+ ---
9
+
10
+ ## TmpFile
11
+
12
+ ### What it is
13
+ A minimal module providing temporary file/directory paths and scoped helpers.
14
+
15
+ ### Key methods
16
+ - `TmpFile.tmpdir` — base directory (default `~/tmp/scout/tmpfiles`).
17
+ - `TmpFile.user_tmp(subdir)` — user-scoped tmp directory.
18
+ - `TmpFile.random_name(prefix, max)` — prefix + random integer.
19
+ - `TmpFile.tmp_file(prefix, max, dir)` — path inside dir (defaults to tmpdir).
20
+
21
+ ### Scoped helpers (create-then-cleanup)
22
+ - `with_file(content=nil, erase=true, options={})` — create a temporary file,
23
+ optionally pre-populate it with content (String, IO, or StringIO), yield the
24
+ path, then delete it.
25
+ - IO content is read in chunks via `readpartial`.
26
+ - Options: `:prefix`, `:max`, `:tmpdir`, `:extension`.
27
+ - `with_dir(erase=true, options={})` — create a temp directory, yield, cleanup.
28
+ - `in_dir(*args)` — create temp dir, `chdir` into it, yield, cleanup.
29
+
30
+ ### Persistence path helper
31
+ - `tmp_for_file(file, tmp_options={}, other_options={})` — generates a stable
32
+ cache filename from a logical filename + options.
33
+ - Replaces `/` with `·` (SLASH_REPLACE) to make a single filename.
34
+ - Truncates names over 150 chars, appends a digest.
35
+ - Appends a digest of `other_options` for parameter-variant uniqueness.
36
+ - Used pervasively by Persist to generate cache paths.
37
+
38
+ ### Design insight
39
+ TmpFile is the **fundamental cache-path primitive** — it turns logical names
40
+ into stable filesystem paths. It does not itself manage caching or locking;
41
+ it just builds deterministic paths.
42
+
43
+ ---
44
+
45
+ ## Open
46
+
47
+ ### What it is
48
+ A unified, high-level file/stream/remote I/O module. It wraps plain File I/O,
49
+ streaming helpers, remote fetching (wget/ssh), atomic writes, locking,
50
+ compression auto-handling, and filesystem utilities (grep, sort, collapse,
51
+ wc, head, tail, mv, cp, rm).
52
+
53
+ ### Sub-modules
54
+ - `Open/stream.rb` — streaming, tee streams, `sensible_write`, monitor streams.
55
+ - `Open/util.rb` — existence checks, remote detection, compression detection,
56
+ realpath, gzip/bgzip/zip helpers, grep, sort, wc, head, tail, mv, cp, rm,
57
+ is_gzip?/is_bgzip?/is_zip? detection.
58
+ - `Open/remote.rb` — remote detection, `wget` download, `ssh` file operations
59
+ (exists, open, read, write, mv, cp, upload, download), scp.
60
+ - `Open/lock/lockfile.rb` — cross-process locking via lock files (a port of
61
+ the Lockfile gem). Supports `Open.lock(file) { ... }`.
62
+ - `Open/final.rb` — streaming wrappers with guaranteed resource cleanup
63
+ (`stream_without_close`, etc.).
64
+
65
+ ### Key patterns
66
+
67
+ #### Auto-decompression
68
+ `Open.read("data.tsv.gz")` automatically detects `.gz`, `.bgz`, `.zip` and
69
+ decompresses transparently. `Open.open` also auto-detects compression on the
70
+ fly via magic byte detection.
71
+
72
+ #### Remote access
73
+ - `Open.remote?(path)` — detects `http://`, `https://`, `ssh:` prefixes.
74
+ - `Open.open(remote_url)` — downloads via `wget` to a cache, then opens.
75
+ - SSH-style: `Open.exists?("server:file")`, `Open.read("server:file")` etc.
76
+
77
+ #### Atomic writes (sensible_write)
78
+ `Open.sensible_write(file, content)` writes to a temporary file then renames
79
+ atomically. Uses a lock to prevent concurrent writes to the same target.
80
+ This is the safe way to write files that might be read concurrently.
81
+
82
+ #### Cross-process locking
83
+ `Open.lock(lockfile) { ... }` — file-based locks using `flock` + lock files.
84
+ Used by Persist and Resource for concurrent-safe resource production.
85
+
86
+ #### Stream handling
87
+ - `Open.open(path) { |io| ... }` — streaming file open with auto-decompression.
88
+ - `Open.stream(io)` — wraps an IO into a ConcurrentStream for lifecycle safety.
89
+ - `Open.tee_stream(io)` — splits a stream into multiple consumers.
90
+ - `Open.consume_stream(io, close=true)` — reads to end and closes.
91
+
92
+ #### Grep/sort/wc utilities
93
+ `Open.grep(stream, pattern)`, `Open.sort(stream)`, `Open.wc(stream)`,
94
+ `Open.head(stream, n)`, `Open.collapse_stream(stream)` — stream-processing
95
+ utilities that work on file paths or IO objects.
96
+
97
+ ### Compression detection
98
+ - `Open.is_gzip?(file)` — magic bytes `\x1f\x8b`.
99
+ - `Open.is_bgzip?(file)` — magic bytes `\x1f\x8b` + extra field in header.
100
+ - `Open.is_zip?(file)` — magic bytes `PK\x03\x04`.
101
+ - `Open.gzip?(file)` — file extension `.gz`.
102
+ - `Open.bgzip?(file)` — file extension `.bgz`.
103
+ - `Open.zip?(file)` — file extension `.zip`.
104
+
105
+ ---
106
+
107
+ ## Path
108
+
109
+ ### What it is
110
+ A path abstraction layered on top of Annotation. Paths are annotated Strings
111
+ that know how to **find** themselves across a configurable set of locations
112
+ (path maps).
113
+
114
+ ### Annotations
115
+ - `pkgdir` — logical package name (default 'scout').
116
+ - `libdir` — library directory for resolving `lib` maps.
117
+ - `path_maps` — hash of named location templates.
118
+ - `map_order` — ordered list of map names to try.
119
+ - `where` — which map resolved successfully during `find`.
120
+ - `original` — the path before resolution.
121
+
122
+ ### Setup
123
+ - `Path.setup(string, pkgdir, libdir, path_maps, map_order)` — extends a plain
124
+ String with Path annotations.
125
+ - A Path **is** a String; it responds to all String methods plus path-specific
126
+ ones.
127
+
128
+ ### Path maps system
129
+ Path maps are templates with placeholders:
130
+ - `{PKGDIR}`, `{HOME}`, `{PWD}`, `{TOPLEVEL}`, `{SUBPATH}`, `{PATH}`,
131
+ `{BASENAME}`, `{LIBDIR}`, `{RESOURCE}`, `{MAPNAME}`.
132
+
133
+ Default maps:
134
+ ```ruby
135
+ :current => "{PWD}/{TOPLEVEL}/{SUBPATH}"
136
+ :home => "{HOME}/{TOPLEVEL}/{PKGDIR}/{SUBPATH}"
137
+ :user => "{HOME}/.{PKGDIR}/{TOPLEVEL}/{SUBPATH}"
138
+ :global => '/{TOPLEVEL}/{PKGDIR}/{SUBPATH}'
139
+ :lib => '{LIBDIR}/{TOPLEVEL}/{SUBPATH}'
140
+ # etc.
141
+ ```
142
+
143
+ ### Resolution: `find`
144
+ ```ruby
145
+ path = Path.setup("data/file.tsv", "mypkg")
146
+ path.find # tries each map in map_order, returns first existing location
147
+ ```
148
+
149
+ - If the path is already located (absolute, ~/, ./), `find` returns immediately.
150
+ - Otherwise, it iterates `map_order`, applies each map template, and checks
151
+ existence (including `.gz`/`.bgz` alternatives).
152
+ - The first match wins; `where` annotation records which map resolved.
153
+
154
+ ### Method-missing as path builder
155
+ ```ruby
156
+ path = Path.setup("data", "mypkg")
157
+ path.file # → "data/file" (as annotated Path)
158
+ path.data.tsv # → "data/data/tsv"
159
+ ```
160
+ This works because `method_missing(name, prev)` calls `join(name, prev)`,
161
+ which returns an annotated Path. This is the idiomatic Scout way to build
162
+ nested paths.
163
+
164
+ ### Operators
165
+ - `path / "subpath"` — alias for `join`.
166
+ - `path["subpath"]` — alias for `join`.
167
+ - `path.join("subpath")` — returns a new annotated Path.
168
+
169
+ ### located? check
170
+ `Path.located?(path)` — returns true if path starts with `/`, `~/`, or `./`.
171
+ These paths are treated as already resolved and are not subject to map
172
+ resolution.
173
+
174
+ ### Path manipulation
175
+ - `_parts` — split by `/`.
176
+ - `_toplevel` — first path component.
177
+ - `_subpath` — everything after the first `/`.
178
+ - `set_extension(ext)` — swap file extension.
179
+ - `set_extension` — used by `find_with_extension`.
180
+
181
+ ### find_with_extension
182
+ ```ruby
183
+ path.find_with_extension(['tsv', 'csv'], produce: true)
184
+ ```
185
+ Tries the path as-is, then tries each extension. Useful when the exact
186
+ extension is not known.
187
+
188
+ ---
189
+
190
+ ## Cross-module interactions
191
+
192
+ - **Path depends on Annotation** — extends Annotation; paths are annotated strings.
193
+ - **Path depends on IndiferentHash** — path_maps is an IndiferentHash.
194
+ - **Open depends on Path** — for path resolution in read/write operations.
195
+ - **Open depends on ConcurrentStream** — for streaming safety.
196
+ - **TmpFile depends on Open** — uses `Open.mkdir` to ensure directories exist.
197
+ - **Resource depends on Path and Open** — resources are annotated paths that
198
+ can be produced on demand.
199
+ - **Persist depends on TmpFile** — uses `tmp_for_file` to generate cache paths.
200
+
201
+ ---
202
+
203
+ ## Gotchas and warnings
204
+
205
+ 1. **Path.map_order is a class variable (@@map_order)** — modifying it globally
206
+ affects all Path instances. Use instance-level `@map_order` for per-resource
207
+ customization.
208
+ 2. **Path.method_missing sends `to_*` methods to super** — `path.to_s` works
209
+ normally, but any custom method starting with `to_` is passed through.
210
+ 3. **Open.remote? requires the full URL prefix** — bare hostnames without
211
+ `http://` or `ssh:` are treated as local paths.
212
+ 3. **Open.sensible_write lock files are not always cleaned up** — if the
213
+ process crashes during write, lock files may persist in
214
+ `tmp/sensible_write_locks`.
215
+ 4. **Path.find may return a path that doesn't exist** — if no map resolves to
216
+ an existing file, `find` returns the `:default` map result (which may not
217
+ exist). Always check `exist?` or `File.exist?` after `find`.
218
+ 5. **TmpFile.tmp_for_file generates Path-annotated strings** — the return value
219
+ is a Path, not a plain String, so it responds to `find`, `produce`, etc.
220
+ 6. **Open.open auto-decompresses based on file content, not just extension** —
221
+ if a file named `.tsv` actually contains gzipped data, Open.open will
222
+ still decompress it.
223
+ 7. **ConcurrentStream.setup on streams returned by Open** — when you use
224
+ `Open.open` with a block, the stream is automatically set up with
225
+ ConcurrentStream. When using `Open.read`, the stream is consumed and
226
+ closed.
227
+ 8. **Open.read on a directory** — returns an empty string or raises, depending
228
+ on the Ruby version.
@@ -0,0 +1,244 @@
1
+ # Investigation: Persistence, Resources, and Concurrency
2
+
3
+ **Status:** Non-normative investigation artifact. May be outdated.
4
+
5
+ ## Scope
6
+ Persist, Resource, ConcurrentStream, and their interactions.
7
+
8
+ ---
9
+
10
+ ## Persist
11
+
12
+ ### What it is
13
+ A caching module that saves the result of expensive computations to disk (or
14
+ memory), keyed by a deterministic cache path. It provides type-aware
15
+ serialization and deserialization, multiple serialization drivers, and
16
+ concurrent-safe production via file locking.
17
+
18
+ ### Core pattern: `persist` / `persist`-with-block
19
+ ```ruby
20
+ result = Persist.persist("my_data", :string, :path => "/cache/my_data") do
21
+ expensive_computation()
22
+ end
23
+ ```
24
+ - If `/cache/my_data` exists, the stored value is loaded.
25
+ - If not, the block is executed, and the result is saved.
26
+ - The `:path` determines where on disk.
27
+ - The type (`:string`, `:marshal`, `:array`, etc.) determines serialization.
28
+
29
+ ### Serialization types
30
+ Supported by `serialize`/`deserialize`:
31
+ - `:string`, `:text`, `:integer`, `:float`, `:boolean` — basic type coercion.
32
+ - `:array` — newline-joined.
33
+ - `:yaml`, `:json`, `:marshal` — standard serializers.
34
+ - `:binary` — raw bytes.
35
+ - `:file` — stores a path to another file (pointer).
36
+ - `:annotation`, `:annotations` — via `Annotation.tsv`.
37
+ - `<type>_array` — array of the inner type (e.g., `:integer_array`).
38
+ - `:memory` — uses the in-memory `Persist::MEMORY` hash.
39
+ - Custom drivers — registered via `save_drivers`/`load_drivers` hashes.
40
+
41
+ ### `persist` lifecycle
42
+ 1. Generate a cache path (via `persist_path` if not given).
43
+ 2. Check if the cache file exists.
44
+ 3. If it exists: load and return.
45
+ 4. If not: acquire a lock (`Open.lock`), check again (double-check locking),
46
+ execute the block, save the result, release the lock, return.
47
+
48
+ ### `persist_path` generation
49
+ When `:path` is not explicitly provided, Persist builds one from the name and
50
+ options using `TmpFile.tmp_for_file`:
51
+ ```ruby
52
+ Persist.persist_path("my_data", :some_option => "value")
53
+ # => ~/tmp/scout/tmpfiles/my_data·SOME_OPTION=VALUE
54
+ ```
55
+ This means the cache path is deterministic for the same name + options,
56
+ enabling automatic cache reuse.
57
+
58
+ ### Concurrency model
59
+ - `Open.lock(cache_path + '.lock')` — uses lockfiles for cross-process safety.
60
+ - Double-check: checks existence again inside the lock.
61
+ - Multiple processes attempting to produce the same cache will block on the
62
+ lock, and the second will find the file already present.
63
+
64
+ ### `persist_tsv` (legacy)
65
+ `Persist.persist_tsv(database, file, data, type)` is a convenience for caching
66
+ TSV-style data. It wraps the persist pattern with TSV-specific serialization.
67
+ In scout-essentials, this exists but the main TSV class is not included —
68
+ this method is for integration with downstream TSV-using code.
69
+
70
+ ---
71
+
72
+ ## Resource
73
+
74
+ ### What it is
75
+ A module for declaring **named resources** that can be produced on demand.
76
+ A Resource module acts as a namespace: it defines a root path (where files
77
+ live), claims (how to produce each file), and a path resolution scheme.
78
+
79
+ ### Anatomy of a Resource module
80
+ ```ruby
81
+ module MyResource
82
+ extend Resource
83
+ self.claim Path.setup("data/file.tsv"), :proc do |path|
84
+ # produce the file at `path`
85
+ end
86
+ end
87
+ ```
88
+ - `extend Resource` — becomes a resource namespace.
89
+ - `claim(path, type, content = nil, &block)` — declare how to produce a path.
90
+ - `self.root` — the filesystem root for this resource.
91
+ - `self.pkgdir` — package name for path resolution.
92
+ - `self.subdir` — subdirectory under the root.
93
+
94
+ ### Claim types
95
+ 1. `:string` — write the string content to the path.
96
+ 2. `:url` — download the URL to the path.
97
+ 3. `:proc` — call the block; it returns String/IO/Array/TSV to write.
98
+ - `arity == 0` → block takes no args, returns data.
99
+ - `arity == 1` → block receives the output path.
100
+ 4. `:rake` — run a Rake task (with `ScoutRake.run`).
101
+ 5. `:install` — install software (uses `Resource.install`).
102
+ 6. `:csv` — (declared but not implemented — raises).
103
+
104
+ ### Production lifecycle (`produce`)
105
+ 1. Resolve the path to find its current location (`path.find`).
106
+ 2. If the file exists at the resolved location, return.
107
+ 3. If a claim exists for this path:
108
+ - Acquire a lock (`Open.lock`).
109
+ - Re-check existence (double-check).
110
+ - Execute the claim (write string, download URL, run proc, run rake, etc.).
111
+ - If the claim type is missing, try `.gz` / `.bgz` variants.
112
+ 4. On error: remove the partial file and re-raise.
113
+ 5. After production, reset the path's location cache.
114
+
115
+ ### Resource → Path integration
116
+ The `Resource/path.rb` file adds `produce`, `produce_and_find`, `produce_with_extension`,
117
+ `relocate`, `identify`, `open`, `read`, `write`, `list`, `exists?`, and
118
+ `find_with_extension` methods to all Path objects.
119
+
120
+ This means **any Path can be produced on-demand** if its `pkgdir` is a
121
+ Resource module. This is the key integration: `path.produce` checks existence,
122
+ and if the file is missing, triggers production.
123
+
124
+ ### Software installation (`Resource/software.rb`)
125
+ - `Resource.install(url_or_spec, name, software_dir)` — downloads and extracts
126
+ software packages.
127
+ - `set_software_env(software_dir)` — adds installed software to PATH and
128
+ LD_LIBRARY_PATH.
129
+ - Used by the `:install` claim type.
130
+
131
+ ### Resource relocation and identification
132
+ - `Resource.relocate(path)` — moves a resource to a better location based on
133
+ resource identification.
134
+ - `Resource.identify(path)` — determines the canonical identity of a path.
135
+
136
+ ---
137
+
138
+ ## ConcurrentStream
139
+
140
+ ### What it is
141
+ A mixin that augments IO-like objects (pipes from `CMD`, file streams from
142
+ `Open`, in-memory streams) with concurrency-aware lifecycle management:
143
+ thread/pid tracking, join/abort semantics, callback chains, and error
144
+ propagation.
145
+
146
+ ### Core attributes
147
+ - `threads` — threads that produce the stream's data.
148
+ - `pids` — subprocess PIDs that produce the stream's data.
149
+ - `callback` — Proc chain called on successful `join`.
150
+ - `abort_callback` — Proc chain called on `abort`.
151
+ - `autojoin` — if true, the stream auto-joins on close/eof.
152
+ - `no_fail` — if true, suppress process failures (log instead of raise).
153
+ - `pair` — a paired stream (e.g., stdout/stderr pair) that is aborted together.
154
+ - `lock` — an associated lock released on join/abort.
155
+ - `stream_exception` — exception captured during streaming; raised on join.
156
+ - `joined`, `aborted` — state flags.
157
+
158
+ ### Lifecycle: join
159
+ 1. `join_threads` — wait for all producer threads; check `Process::Status` for
160
+ failures; raise `ConcurrentStreamProcessFailed` on failure (unless `no_fail`).
161
+ 2. `join_pids` — `Process.waitpid` for each PID; check exit status.
162
+ 3. Check `stream_exception` — raise if set.
163
+ 4. `join_callback` — call the callback chain.
164
+ 5. `close` — close the underlying IO.
165
+ 6. Release the lock.
166
+
167
+ ### Lifecycle: abort
168
+ 1. Set `stream_exception` if not already set.
169
+ 2. Call `abort_callback` chain.
170
+ 3. `abort_threads` — raise `Aborted` exception in each producer thread.
171
+ 4. `abort_pids` — send `SIGINT` to each PID.
172
+ 5. Abort the pair stream if present.
173
+ 6. Close the IO.
174
+ 7. Release the lock.
175
+
176
+ ### Callback chaining
177
+ `ConcurrentStream.setup(stream, :callback => proc1)` then
178
+ `ConcurrentStream.setup(stream, :callback => proc2)` results in a chained
179
+ callback that calls `proc1` then `proc2`. This allows multiple subsystems to
180
+ attach cleanup logic to the same stream without coordination.
181
+
182
+ ### Annotate (propagation)
183
+ `ConcurrentStream#annotate(stream)` copies threads, pids, callback, etc. to
184
+ another stream. Used by CMD when transforming streams (e.g., decompressing).
185
+
186
+ ### `process_stream` class method
187
+ `ConcurrentStream.process_stream(stream, **kwargs) { ... }` — a convenience
188
+ wrapper that sets up the stream, runs the block, and ensures proper
189
+ join/abort in case of exception. Used by stream-consuming code for
190
+ guaranteed cleanup.
191
+
192
+ ### AbortedStream
193
+ `AbortedStream.setup(obj, exception)` — marks a stream as aborted, attaching
194
+ the exception. Consumers can check `AbortedStream === stream` and access
195
+ `stream.exception`.
196
+
197
+ ---
198
+
199
+ ## Cross-module interactions
200
+
201
+ - **Persist depends on Open** — for file I/O, locking, and atomic writes.
202
+ - **Persist depends on TmpFile** — for cache path generation.
203
+ - **Resource depends on Open, Path, TmpFile** — for production, path
204
+ resolution, and locking.
205
+ - **CMD depends on ConcurrentStream** — CMD.cmd returns ConcurrentStream-enhanced
206
+ IO objects that track the subprocess PID and threads.
207
+ - **Open depends on ConcurrentStream** — Open.stream wraps IO objects with
208
+ ConcurrentStream for lifecycle safety.
209
+ - **ConcurrentStream depends on IndiferentHash** — for `process_options`.
210
+ - **Log::ProgressBar depends on CMD** — `guess_obj_max` uses `CMD.cmd("wc -l")`
211
+ to estimate total lines in a file.
212
+
213
+ ---
214
+
215
+ ## Gotchas and warnings
216
+
217
+ 1. **Persist.persist silently returns nil on failure** — if the block returns
218
+ `nil`, `Persist.save` returns immediately without writing. The cache file
219
+ is not created, so the next call will re-execute the block.
220
+ 2. **Persist MEMORY type is process-local** — `:memory` persistence uses a
221
+ global `Persist::MEMORY` hash, which is lost when the process exits. It is
222
+ suitable only for within-process caching.
223
+ 3. **Resource.produce fallback to .gz/.bgz** — if no claim matches, produce
224
+ tries `path + '.gz'` then `path + '.bgz'`. This is usually desired but can
225
+ cause confusing errors if you expected a specific extension.
226
+ 4. **ConcurrentStream.join is idempotent** — calling `join` on an already-joined
227
+ stream is safe (returns without re-joining), but the `@joined` flag is set
228
+ even if the join raised an exception.
229
+ 5. **ConcurrentStream callback chains can grow** — each `setup` call with a
230
+ `:callback` prepends to the chain. Long chains from repeated `setup` calls
231
+ may cause unexpected ordering.
232
+ 6. **CMD no_fail streams** — a stream with `no_fail: true` will log errors but
233
+ not raise. Reading from it may produce truncated output. Check
234
+ `stream.exit_status` if you need to verify success.
235
+ 7. **Open.lock requires the lock directory to exist** — if the directory does
236
+ not exist, the lock file cannot be created and the lock fails silently.
237
+ 8. **Resource :csv claim type raises NotImplementedError** — declared but
238
+ not implemented. If you encounter this, use `:proc` with manual CSV parsing.
239
+ 9. **Persist caching of nil results** — if the computation block returns `nil`,
240
+ no cache file is written, leading to re-computation on every call. Use a
241
+ sentinel value if nil is a valid result.
242
+ 10. **ConcurrentStream.pair abort propagation** — aborting a stream also aborts
243
+ its pair. If you only want to abort one side of a stdout/stderr pair, you
244
+ must detach the pair first (set `pair = nil`).
@@ -0,0 +1,80 @@
1
+ # Synthesis Report
2
+
3
+ **Status:** Non-normative investigation artifact. May be outdated.
4
+
5
+ ## Purpose
6
+
7
+ Cross-check all investigation artifacts for consistency, overlaps, and gaps.
8
+ Produce a concrete file mapping from investigation content to target
9
+ documentation files.
10
+
11
+ ---
12
+
13
+ ## Module → Concept → Documentation mapping
14
+
15
+ | Module | Concept | User doc | Developer doc |
16
+ |--------|---------|----------|---------------|
17
+ | Annotation | Annotating objects | AnnotatingData.md | AnnotationSystem.md |
18
+ | NamedArray | Named arrays | AnnotatingData.md | AnnotationSystem.md |
19
+ | IndiferentHash | Key-indifferent hashes | AnnotatingData.md | AnnotationSystem.md |
20
+ | Open | File I/O | WorkingWithFiles.md | PathResolution.md |
21
+ | Path | Path resolution | WorkingWithFiles.md | PathResolution.md |
22
+ | TmpFile | Temp files | WorkingWithFiles.md | PathResolution.md |
23
+ | CMD | Running commands | RunningCommands.md | (covered in user doc) |
24
+ | Log | Logging | LoggingAndProgress.md | (covered in user doc) |
25
+ | ConcurrentStream | Stream lifecycle | HandlingStreams.md | StreamingModel.md |
26
+ | Persist | Caching | CachingResults.md | PersistenceAndResources.md |
27
+ | Resource | On-demand resources | ProducingResources.md | PersistenceAndResources.md |
28
+ | SOPT | CLI options | CommandLineOptions.md | (covered in user doc) |
29
+ | Misc | Utility functions | (cross-cutting) | DesignPrinciples.md |
30
+
31
+ ---
32
+
33
+ ## Target documentation structure
34
+
35
+ ```
36
+ doc/
37
+ StartHere.md
38
+ Improvements.md
39
+ user/
40
+ AnnotatingData.md
41
+ WorkingWithFiles.md
42
+ RunningCommands.md
43
+ LoggingAndProgress.md
44
+ HandlingStreams.md
45
+ CachingResults.md
46
+ ProducingResources.md
47
+ CommandLineOptions.md
48
+ Cookbook.md
49
+ developer/
50
+ Architecture.md
51
+ DesignPrinciples.md
52
+ AnnotationSystem.md
53
+ PathResolution.md
54
+ PersistenceAndResources.md
55
+ StreamingModel.md
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Writing order (by priority)
61
+
62
+ 1. **StartHere.md** — entry point
63
+ 2. **developer/Architecture.md** — mental map
64
+ 3. **developer/DesignPrinciples.md** — coding style
65
+ 4. **user/AnnotatingData.md** — Annotation foundation
66
+ 5. **user/WorkingWithFiles.md** — most used
67
+ 6. **user/RunningCommands.md** — CMD
68
+ 7. **user/LoggingAndProgress.md** — Log
69
+ 8. **user/HandlingStreams.md** — ConcurrentStream
70
+ 9. **user/CachingResults.md** — Persist
71
+ 10. **user/ProducingResources.md** — Resource
72
+ 11. **user/CommandLineOptions.md** — SOPT
73
+ 12. **user/Cookbook.md** — recipes
74
+ 13. **developer/AnnotationSystem.md** — internals
75
+ 14. **developer/PathResolution.md** — path maps
76
+ 15. **developer/PersistenceAndResources.md** — caching internals
77
+ 16. **developer/StreamingModel.md** — stream internals
78
+ 17. **Improvements.md** — recommendations
79
+ 18. **research/** — curate SHARED artifacts
80
+ 19. **Validation** — check links, coverage
@@ -2,11 +2,11 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: scout-essentials 1.8.7 ruby lib
5
+ # stub: scout-essentials 1.9.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "scout-essentials".freeze
9
- s.version = "1.8.7".freeze
9
+ s.version = "1.9.0".freeze
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
@@ -26,18 +26,28 @@ Gem::Specification.new do |s|
26
26
  "README.md",
27
27
  "Rakefile",
28
28
  "VERSION",
29
- "doc/Annotation.md",
30
- "doc/CMD.md",
31
- "doc/ConcurrentStream.md",
32
- "doc/IndiferentHash.md",
33
- "doc/Log.md",
34
- "doc/NamedArray.md",
35
- "doc/Open.md",
36
- "doc/Path.md",
37
- "doc/Persist.md",
38
- "doc/Resource.md",
39
- "doc/SimpleOPT.md",
40
- "doc/TmpFile.md",
29
+ "doc/Improvements.md",
30
+ "doc/StartHere.md",
31
+ "doc/developer/AnnotationSystem.md",
32
+ "doc/developer/Architecture.md",
33
+ "doc/developer/Configuration.md",
34
+ "doc/developer/CoreUtilities.md",
35
+ "doc/developer/DesignPrinciples.md",
36
+ "doc/developer/ErrorHandling.md",
37
+ "doc/developer/LockingAndConcurrency.md",
38
+ "doc/developer/PathResolution.md",
39
+ "doc/developer/PersistenceAndResources.md",
40
+ "doc/developer/StreamingModel.md",
41
+ "doc/user/AnnotatingData.md",
42
+ "doc/user/CachingResults.md",
43
+ "doc/user/CommandLineOptions.md",
44
+ "doc/user/Cookbook.md",
45
+ "doc/user/HandlingStreams.md",
46
+ "doc/user/LoggingAndProgress.md",
47
+ "doc/user/ProducingResources.md",
48
+ "doc/user/RemoteData.md",
49
+ "doc/user/RunningCommands.md",
50
+ "doc/user/WorkingWithFiles.md",
41
51
  "lib/scout-essentials.rb",
42
52
  "lib/scout/annotation.rb",
43
53
  "lib/scout/annotation/annotated_object.rb",
@@ -104,6 +114,18 @@ Gem::Specification.new do |s|
104
114
  "lib/scout/simple_opt/parse.rb",
105
115
  "lib/scout/simple_opt/setup.rb",
106
116
  "lib/scout/tmpfile.rb",
117
+ "research/annotations-data-analysis.md",
118
+ "research/behavior-probes.md",
119
+ "research/commands-streaming-analysis.md",
120
+ "research/design-philosophy-analysis.md",
121
+ "research/doc-audit-findings.md",
122
+ "research/ecosystem-attribution.md",
123
+ "research/implementation-inventory-core.md",
124
+ "research/implementation-inventory-open.md",
125
+ "research/implementation-inventory-path-persist-resource.md",
126
+ "research/io-paths-analysis.md",
127
+ "research/persistence-resources-analysis.md",
128
+ "research/synthesis-report.md",
107
129
  "scout-essentials.gemspec",
108
130
  "share/color/color_names",
109
131
  "share/color/diverging_colors.hex",
@@ -159,7 +181,7 @@ Gem::Specification.new do |s|
159
181
  ]
160
182
  s.homepage = "http://github.com/mikisvaz/scout-essentials".freeze
161
183
  s.licenses = ["MIT".freeze]
162
- s.rubygems_version = "3.7.0.dev".freeze
184
+ s.rubygems_version = "3.7.2".freeze
163
185
  s.summary = "Scout essential tools".freeze
164
186
 
165
187
  s.specification_version = 4
@@ -26,10 +26,9 @@ class TestOpenRemote < Test::Unit::TestCase
26
26
  teardown
27
27
  sss 0
28
28
  assert_raises ConcurrentStreamProcessFailed do
29
- stream = Open.open("ftp://ftp.ncbi.nlm.nih.gov/pub/geo/DATA/SOFT/GDS/GDS3148.soft.gz", nocache: true)
29
+ stream = Open.open("ftp://ftp.ncbi.nlm.nih.gov/pub/geo/DATA/SOFT/GDS/GDS3148.soft.gz", nocache: true, no_fail: false)
30
30
  stream.read
31
31
  end
32
32
  end
33
-
34
33
  end
35
34