scout-essentials 1.8.5 → 1.8.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bf37bb130a8c4d14dd4560df43a3e98f6ea43ea7ad38c9b44525cf113e84b7b4
4
- data.tar.gz: 504f47bbf850cae9fa91b898b460ec632fe6a64df77a8ab89f9d7b6c1abc67b7
3
+ metadata.gz: 3193beb1b7024505d5a238bb6d9caa280ddab034b23431652138697ebc9ac7f0
4
+ data.tar.gz: 29ef13d1a7862228eabc8f153f602ba728eda98cf4907b23770a8fb65cb36b98
5
5
  SHA512:
6
- metadata.gz: b977260d1670a0a391583c2f20cd5a0780fba2b6d5a8a6685370664fb8bf66e91a7cac5f9f77e01de3dffe89cf218581bf02df95a5ae4350789881adfbd11273
7
- data.tar.gz: 124cd1a4e62ca11e516144e8e37c49808440d0e67904e7756eabc8c3f71da053bd3856b8f21e7ed2a8e5fb5372bd43e6510d17ae5a70b538d23cf0e9e0cab683
6
+ metadata.gz: c074200ead16664e4ecdcd262315f389500cd59391b533bcdd1f4a84bdf9ab3fcf96152698f643393d198d9cc1d552707144fd4fbc9c962badbb33d57c79d364
7
+ data.tar.gz: 57663384b0258537a626eb867e8dd209c9e84bc477dbc462c6096af539f54b176936b1cd4ace7cd47525b5e791a6b13ac41a000fdf2709a9add3a830eb1a33ab
data/.vimproject CHANGED
@@ -1,144 +1,11 @@
1
1
  scout-essentials=/$PWD filter="*.rb *.txt *.md *.conf *.yaml" {
2
2
  LICENSE.txt
3
3
  chats=chats filter="*"{
4
+ improve_doc
5
+ intro
6
+
4
7
  review
5
8
  document.rb
6
- docs=docs{
7
- Analyze Annotation tests
8
- Analyze CMD and related tests
9
- Analyze CMD tests
10
- Analyze log tests
11
- Analyze Open tests
12
- Analyze Path tests
13
- Analyze Persist tests
14
- Analyze Resource tests
15
- Analyze selected tests
16
- simple_opt, log, open, persist, cmd, tmpfile
17
-
18
- Analyze SimpleOpt tests
19
- Analyze simple_opt tests
20
- Analyze tests: simple_opt, log, open, persist, cmd, tmpfile
21
- Analyze tmpfile tests
22
- Analyze Utilities tests
23
- Assemble Annotation docs
24
- Assemble CMD docs
25
- Assemble CMD docs
26
- update
27
-
28
- Assemble Open docs
29
- Assemble Open docs
30
- update
31
-
32
- Assemble Path docs
33
- Assemble Persist docs
34
- Assemble Persist docs
35
- update
36
-
37
- Assemble Resource docs
38
- Assemble Resource docs
39
- update
40
-
41
- Assemble Top-level README
42
- Assemble Top-level README
43
- update
44
-
45
- Assemble Top-level README draft
46
- Assemble Utilities docs
47
- Assemble Utilities docs
48
- update
49
-
50
- Confirm·Explore Utilities dirs
51
- Explore Annotation dirs
52
- Explore CMD dirs
53
- Explore Log dir
54
- Explore Log lib dir
55
- Explore Log lib dir
56
- refresh
57
-
58
- Explore Log test dir
59
- Explore Log test dir
60
- refresh
61
-
62
- Explore Open dirs
63
- Explore Path dirs
64
- Explore Persist dirs
65
- Explore Resource dirs
66
- Explore Utilities dirs
67
- Finalize README step
68
- refresh
69
-
70
- Finalize README.md into memory key
71
- .md
72
-
73
- conditional
74
-
75
- NEXT_STEPS
76
- QA Annotation docs
77
- QA CMD docs
78
- QA Integrated README
79
- QA Integrated README
80
- post-update
81
-
82
- QA Integrated README
83
- post-update
84
-
85
- refresh
86
-
87
- QA Open docs
88
- QA Path docs
89
- QA Persist docs
90
- QA Resource docs
91
- QA Utilities docs
92
- Read Annotation source files
93
- Read CMD source files
94
- Read CMD source files
95
- refresh
96
-
97
- Read Log source files
98
- Read Log source files
99
- iterate
100
-
101
- Read Log source files
102
- refresh
103
-
104
- Read Log source files
105
- use explorer output
106
-
107
- Read Open source files
108
- Read Open stream and lockfile
109
- Read Path source files
110
- Read Persist serialize and open
111
- Read Persist source files
112
- Read Resource scout.rb
113
- Read Resource source files
114
- Read SimpleOpt source files
115
- Read TmpFile and IndiferentHash source files
116
- Read Utilities source files
117
- Run QA Integrator on assembled docs
118
- Update CMD docs
119
- assemble
120
-
121
- Update Open docs
122
- assemble
123
-
124
- Update Persist docs
125
- assemble
126
-
127
- Update Resource docs
128
- assemble
129
-
130
- Update Utilities docs
131
- assemble
132
-
133
- final=final{
134
- annotation.md
135
- cmd.md
136
- open.md
137
- path.md
138
- persist.md
139
- resource.md
140
- }
141
- }
142
9
  }
143
10
  doc=doc{
144
11
  Annotation.md
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.8.5
1
+ 1.8.7
data/doc/CMD.md CHANGED
@@ -32,12 +32,72 @@ puts stream.read # streaming consumption
32
32
  stream.join # wait for producers and check exit status
33
33
  ```
34
34
 
35
+ ## Common gotchas
36
+
37
+ These are the most common sources of confusion when using `CMD.cmd`:
38
+
39
+ - **String vs Symbol as the first argument**:
40
+ - `CMD.cmd('mytool ...')` runs exactly that shell command.
41
+ - `CMD.cmd(:mytool, ...)` goes through `CMD.get_tool(:mytool)` first (tool registry / bootstrap). This is useful when you want
42
+ tool discovery/installation behavior.
43
+ - If you do *not* rely on the tool registry, prefer passing the command as a String.
44
+
45
+ - **Pipe mode needs a `join` (for correct error detection)**:
46
+ - With `:pipe => true`, you typically do `io = CMD.cmd(..., pipe: true)` and then `io.read`.
47
+ - To reliably detect failures (non-zero exit), call `io.join` (unless you deliberately set `no_fail: true`).
48
+
49
+ - **Non-pipe mode returns a `StringIO`**:
50
+ - With `:pipe => false` (default), CMD collects stdout and returns a `StringIO` *after* the process completes.
51
+ - This is convenient, but can be memory-heavy for large outputs.
52
+
53
+ - **`save_stderr` vs logging**:
54
+ - `log: true` (default in many contexts) logs stderr lines as they arrive.
55
+ - `save_stderr: true` additionally accumulates stderr into `io.std_err` (useful for raising helpful exceptions).
56
+
57
+ - **`no_fail` suppresses exceptions**:
58
+ - If you pass `no_fail: true`, CMD will not raise `ProcessFailed` / `ConcurrentStreamProcessFailed` on non-zero exits.
59
+ - This is useful for "try it" probes, but make sure you explicitly check `io.exit_status` (or parse output) when you need correctness.
60
+
61
+ - **`{opt}` placeholder**:
62
+ - If the command string contains the exact substring `'{opt}'`, CMD replaces it with the processed options string.
63
+ - Otherwise, options are appended to the end of the command.
64
+
65
+ - **Second argument ambiguity**:
66
+ - `CMD.cmd(tool, {...})` means the second argument is treated as options and `cmd_fragment_or_options` becomes nil.
67
+ - If you intended to pass a command fragment, pass it as a String, e.g. `CMD.cmd('cut', "-f 2", in: "a b")`.
68
+
35
69
  ---
36
70
 
37
71
  ## Important options
38
72
 
39
73
  All options are passed as an options Hash (converted with IndiferentHash), and many are special keys:
40
74
 
75
+ ### Understanding `CMD.cmd` arguments
76
+
77
+ `CMD.cmd` has a flexible signature:
78
+
79
+ - `CMD.cmd(tool_or_cmd, cmd_fragment_or_options = nil, options = {})`
80
+
81
+ Common calling styles:
82
+
83
+ ```ruby
84
+ # 1) Single string command
85
+ io = CMD.cmd("echo hello")
86
+
87
+ # 2) Tool + command fragment
88
+ io = CMD.cmd("cut", "-f 2 -d ' '", in: "a b")
89
+
90
+ # 3) Tool + options hash (options are converted to CLI flags)
91
+ io = CMD.cmd("cut", {"-f" => 2, "-d" => " "}, in: "a b")
92
+
93
+ # 4) Tool registry symbol (uses CMD.get_tool first)
94
+ io = CMD.cmd(:python, "--version")
95
+ ```
96
+
97
+ Notes:
98
+ - If the *second* argument is a Hash, it is treated as option flags and `cmd_fragment_or_options` becomes nil.
99
+ - Options are shell-quoted; values containing single quotes are escaped.
100
+
41
101
  - :pipe (boolean) — if true, return a stream you can read from; otherwise CMD returns a StringIO after the process completes.
42
102
  - :in — input to feed to the command:
43
103
  - String will be wrapped by StringIO and streamed to process stdin.
@@ -177,6 +237,39 @@ CMD.cmd('grep . NONEXISTINGFILE', :pipe => true).join
177
237
 
178
238
  ## Recommendations & patterns
179
239
 
240
+ ### Robust error-handling pattern
241
+
242
+ A common robust pattern is:
243
+
244
+ ```ruby
245
+ io = CMD.cmd("SomeTool", "--flag value", log: true, save_stderr: true, no_fail: true)
246
+
247
+ # Decide how to handle failure
248
+ if io.exit_status != 0
249
+ raise ScoutException, io.read + "
250
+ " + io.std_err.to_s
251
+ end
252
+ ```
253
+
254
+ - `no_fail: true` prevents immediate exceptions (useful so you can include stderr in your own error message).
255
+ - If you want CMD to raise automatically, omit `no_fail` and rely on `ProcessFailed` / `ConcurrentStreamProcessFailed`.
256
+
257
+ ### Large outputs
258
+
259
+ - Prefer `:pipe => true` when you want to stream and transform output without buffering everything in memory.
260
+ - If you need to keep the full output, write it to a file as you consume it, and return/keep only a small summary in memory.
261
+
262
+ ### Pipelines
263
+
264
+ When composing multiple commands, use pipe mode and pass the upstream stream as `:in`:
265
+
266
+ ```ruby
267
+ io1 = CMD.cmd("tool1", "--emit", pipe: true)
268
+ io2 = CMD.cmd("tool2", "--filter", in: io1, pipe: true)
269
+ out = io2.read
270
+ io2.join
271
+ ```
272
+
180
273
  - Prefer `:pipe => true` + ConcurrentStream when you want streaming processing without waiting for full output in memory.
181
274
  - Provide `:in` as an IO to stream large inputs into a subprocess.
182
275
  - Use `:autojoin => true` to automatically join producers on EOF/close (useful for simple consumers).
@@ -200,4 +293,71 @@ CMD.cmd('grep . NONEXISTINGFILE', :pipe => true).join
200
293
 
201
294
  ---
202
295
 
203
- CMD centralizes robust process execution patterns needed throughout the framework: streaming, joining, logging, error detection and tool bootstrap. Use its options to control behavior for production-grade command invocation.
296
+ CMD centralizes robust process execution patterns needed throughout the framework: streaming, joining, logging, error detection and tool bootstrap. Use its options to control behavior for production-grade command invocation.
297
+
298
+ ---
299
+
300
+ ## Designing command wrappers for workflows and agents
301
+
302
+ When you wrap external CLI tools inside workflow tasks (or expose them to agents), use a pattern that maximizes reproducibility and keeps outputs small for downstream consumers:
303
+
304
+ - Backend task (tool runner)
305
+ - Run the binary, write all full outputs to `step.files_dir` (use `file('name')` helpers).
306
+ - Return a compact JSON summary with:
307
+ - `files`: list of important output file paths (full paths inside `.files/`).
308
+ - `params`: what parameters were used (seeds, time, sample_count, fixed clamps, etc.).
309
+ - optional small parsed snippets (e.g. final probabilities) if tiny.
310
+ - Use `CMD.cmd('Binary', ...)` (string) unless the tool is registered; include `save_stderr: true` so errors are captured.
311
+
312
+ - Analysis task (summary)
313
+ - `dep` on the backend task and parse only the necessary outputs into a compact summary suitable for the interactive/LLM context (JSON, small tables, phenotype probs).
314
+ - Echo the backend `params` in the returned result so cached runs are auditable.
315
+
316
+ Benefits:
317
+ - Clear cache boundaries: the backend is the expensive, cacheable step; the analysis is cheap and reproducible given the backend outputs.
318
+ - Agents never have to load entire trace files; they get a compact summary.
319
+
320
+
321
+ ## Quick checklist when using CMD in workflows
322
+
323
+ - Prefer `CMD.cmd('Binary', ...)` string invocation unless you intentionally want the tool registry/install behavior via symbol form.
324
+ - For long-running streaming tasks use `:pipe => true` and **always** `join` the returned stream (or rely on `autojoin`) to detect failures.
325
+ - If you must ignore process failures for probing, use `no_fail: true` but explicitly check exit code or outputs later.
326
+ - Use `save_stderr: true` when you will raise on error: include `io.std_err` in exception messages.
327
+ - Avoid returning raw `StringIO` of huge outputs from tasks — write to `step.files_dir` instead and return a small summary.
328
+
329
+
330
+ ## Minimal patterns/examples
331
+
332
+ Read-only capture (small output):
333
+ ```ruby
334
+ out = CMD.cmd("echo hello").read # synchronous, small stdout
335
+ ```
336
+
337
+ Streaming safe consumption:
338
+ ```ruby
339
+ stream = CMD.cmd("tail -f /some/log", pipe: true)
340
+ begin
341
+ data = stream.read
342
+ ensure
343
+ stream.join # ensure you detect non-zero exit and collect stderr
344
+ end
345
+ ```
346
+
347
+ Pass an IO to stdin safely:
348
+ ```ruby
349
+ f = Open.open('input.txt')
350
+ io = CMD.cmd('someprog', :in => f, pipe: true)
351
+ puts io.read
352
+ io.join
353
+ ```
354
+
355
+ Tool registry vs direct call:
356
+ ```ruby
357
+ # use tool registry (only if the tool is registered in CMD.tool)
358
+ CMD.cmd(:my_registered_tool, '--version')
359
+ # prefer direct call when portability is desired
360
+ CMD.cmd('my_tool --version')
361
+ ```
362
+
363
+ For more advanced patterns and examples see the `CMD` implementation and tests in the codebase.
@@ -64,6 +64,11 @@ module Annotation
64
64
  new.remove_instance_variable(:@container_index)
65
65
  end
66
66
 
67
+ new.instance_variables.each do |var|
68
+ value = new.instance_variable_get(var)
69
+ new.instance_variable_set(var, Annotation.purge(value))
70
+ end
71
+
67
72
  new
68
73
  end
69
74
 
@@ -26,7 +26,7 @@ module Annotation
26
26
  end
27
27
 
28
28
  def self.is_annotated?(obj)
29
- obj.instance_variables.include?(:@annotation_types)
29
+ obj.instance_variables.include?(:@annotation_types) && obj.respond_to?(:purge)
30
30
  end
31
31
 
32
32
  def self.purge(obj)
data/lib/scout/cmd.rb CHANGED
@@ -212,8 +212,10 @@ module CMD
212
212
  in_content.close unless in_content.closed?
213
213
  in_content.join if in_content.respond_to? :join
214
214
  end
215
- rescue
215
+ rescue Exception
216
216
  Log.error "Error in CMD [#{pid}] #{cmd}: #{$!.message}" unless no_fail
217
+ sin.close unless sin.closed?
218
+ sin.join if sin.respond_to? :join
217
219
  raise $!
218
220
  end
219
221
  end
@@ -222,7 +222,7 @@ module ConcurrentStream
222
222
  if autojoin
223
223
  begin
224
224
  super(*args)
225
- rescue
225
+ rescue Exception
226
226
  self.abort
227
227
  self.join
228
228
  stream_raise_exception $!
@@ -16,6 +16,7 @@ module Log
16
16
  when Symbol
17
17
  ":" + obj.to_s
18
18
  when String
19
+ obj = obj.gsub("\n", '\n')
19
20
  if obj.length > FP_MAX_STRING
20
21
  digest = Digest::MD5.hexdigest(obj)
21
22
  middle = "<...#{obj.length} - #{digest[0..4]}...>"
@@ -67,6 +68,8 @@ module Log
67
68
  else
68
69
  obj.inspect
69
70
  end
71
+ when Set
72
+ fingerprint(obj.to_a)
70
73
  else
71
74
  obj.to_s
72
75
  end
@@ -89,7 +89,7 @@ module Log
89
89
  percent = self.percent
90
90
  time = Time.now
91
91
 
92
- indicator = ""
92
+ indicator = "".dup
93
93
  10.times{|i|
94
94
  if i < percent / 10 then
95
95
  indicator << Log.color(:yellow, ".")
@@ -90,7 +90,7 @@ module Log
90
90
  yield bar
91
91
  rescue KeepBar
92
92
  keep = true
93
- rescue
93
+ rescue Exception
94
94
  error = true
95
95
  raise $!
96
96
  ensure
@@ -80,6 +80,9 @@ module Misc
80
80
  def self.digest_file(file)
81
81
  file = file.find if Path === file
82
82
  file = File.expand_path(file)
83
+ if Open.exist? file + '.md5'
84
+ return Open.read(file + '.md5').strip
85
+ end
83
86
  if File.size(file) > 10_000_000
84
87
  fast_file_md5(file)
85
88
  else
@@ -36,7 +36,7 @@ module Misc
36
36
  end
37
37
  end
38
38
 
39
- MAX_TTY_LINE_WIDTH = 160
39
+ MAX_TTY_LINE_WIDTH = 120
40
40
  def self.format_paragraph(text, size = nil, indent = nil, offset = nil)
41
41
  size ||= Log.tty_size || MAX_TTY_LINE_WIDTH
42
42
  size = MAX_TTY_LINE_WIDTH if size > MAX_TTY_LINE_WIDTH
@@ -45,8 +45,8 @@ module Misc
45
45
 
46
46
  i = 0
47
47
  #size = size + offset + indent
48
- re = /((?:\n\s*\n\s*)|(?:\n\s*(?=\*)))/
49
- text.split(re).collect do |paragraph|
48
+ re = /((?:\n\s*\n\s*)|(?:\n\s*(?=\*))|(?:\n```)|(?:\n\s*[-\*])|(?:\n\s\s+))/
49
+ text.split(re).collect do |paragraph|
50
50
  i += 1
51
51
  str = if i % 2 == 1
52
52
  words = paragraph.gsub(/\s+/, "\s").split(" ")
@@ -75,11 +75,12 @@ module Misc
75
75
  def self.format_definition_list_item(dt, dd, indent = nil, size = nil, color: :yellow)
76
76
  if size.nil?
77
77
  base_size = Log.tty_size || MAX_TTY_LINE_WIDTH
78
+ base_size = MAX_TTY_LINE_WIDTH if base_size > MAX_TTY_LINE_WIDTH
78
79
  base_indent = indent || (base_size / 3)
79
80
  size = base_size - base_indent
80
81
  end
81
82
 
82
- indent ||= base_indent || size / 3
83
+ indent ||= base_indent || (size / 3)
83
84
 
84
85
  dd = "" if dd.nil?
85
86
  dt = Log.color color, dt if color
@@ -87,12 +88,12 @@ module Misc
87
88
  len = Log.uncolor(dt).length
88
89
 
89
90
  if indent < 0
90
- text = format_paragraph(dd, size, indent.abs-1, 0)
91
+ text = format_paragraph(dd, base_size, indent.abs-1, 0)
91
92
  text = dt << "\n" << text
92
93
  else
93
94
  offset = len - indent
94
95
  offset = 0 if offset < 0
95
- text = format_paragraph(dd, size, indent.abs+1, offset)
96
+ text = format_paragraph(dd, base_size, indent.abs+1, offset)
96
97
  text[0..len-1] = dt
97
98
  end
98
99
  text
@@ -41,4 +41,10 @@ module Hook
41
41
  end
42
42
  end
43
43
  end
44
+
45
+ def self.hook_method(base_class, source_class, method_name)
46
+ base_class.define_singleton_method(method_name) do |*args, &block|
47
+ source_class.send(method_name, *args, &block)
48
+ end
49
+ end
44
50
  end
@@ -48,17 +48,7 @@ module Misc
48
48
  end
49
49
  end
50
50
 
51
- def self.with_env(var, value, &block)
52
- old_value = ENV[var]
53
- begin
54
- ENV[var] = value
55
- yield
56
- ensure
57
- ENV[var] = old_value
58
- end
59
- end
60
-
61
- def self.with_envs(hash, &block)
51
+ def self.with_env_hash(hash, &block)
62
52
  old_value = {}
63
53
  begin
64
54
  hash.each do |var,value|
@@ -73,6 +63,24 @@ module Misc
73
63
  end
74
64
  end
75
65
 
66
+ def self.with_env(var, value = nil, &block)
67
+ if Hash === value
68
+ with_env_hash(var, &block)
69
+ else
70
+ old_value = ENV[var]
71
+ begin
72
+ ENV[var] = value
73
+ yield
74
+ ensure
75
+ ENV[var] = old_value
76
+ end
77
+ end
78
+ end
79
+
80
+ class << self
81
+ alias with_envs with_env_hash
82
+ end
83
+
76
84
 
77
85
  def self.update_git(gem_name = 'scout-essentials')
78
86
  gem_name = 'scout-essentials' if gem_name.nil?
@@ -144,8 +144,11 @@ module Open
144
144
  file = file.find if Path === file
145
145
  begin
146
146
  if File.symlink?(file) || File.stat(file).nlink > 1
147
- if File.exist?(file + '.info') && defined?(Step)
148
- done = Persist.load(file + '.info', Step::SERIALIZER)[:done]
147
+ info_file = file + '.info'
148
+ if File.exist?(info_file) && defined?(Step)
149
+ done = Persist.memory([info_file, File.mtime(info_file)], persist: true ) do
150
+ Persist.load(info_file, Step::SERIALIZER)[:done]
151
+ end
149
152
  return done if done
150
153
  end
151
154
 
@@ -35,21 +35,25 @@ module Open
35
35
  Bgzf.setup stream
36
36
  end
37
37
 
38
- def self.gunzip(stream)
39
- CMD.cmd('zcat', :in => stream, :pipe => true, :no_fail => true, :no_wait => true)
38
+ def self.gunzip(stream, options = {})
39
+ options = IndiferentHash.add_defaults options, :pipe => true, :no_fail => false, :no_wait => true, in: stream
40
+ CMD.cmd('zcat', options)
40
41
  end
41
42
 
42
- def self.gzip(stream)
43
- CMD.cmd('gzip', :in => stream, :pipe => true, :no_fail => true, :no_wait => true)
43
+ def self.gzip(stream, options = {})
44
+ options = IndiferentHash.add_defaults options, :pipe => true, :no_fail => false, :no_wait => true, in: stream
45
+ CMD.cmd('gzip', options)
44
46
  end
45
47
 
46
- def self.bgzip(stream)
47
- CMD.cmd('bgzip', :in => stream, :pipe => true, :no_fail => true, :no_wait => true)
48
+ def self.bgzip(stream, options = {})
49
+ options = IndiferentHash.add_defaults options, :pipe => true, :no_fail => false, :no_wait => true, in: stream
50
+ CMD.cmd('bgzip', options)
48
51
  end
49
52
 
50
- def self.unzip(stream)
53
+ def self.unzip(stream, options = {})
54
+ options = IndiferentHash.add_defaults options, :pipe => true, :no_fail => false, :no_wait => true, in: stream
51
55
  TmpFile.with_file(stream.read) do |filename|
52
- StringIO.new(CMD.cmd("unzip '{opt}' #{filename}", "-p" => true, :pipe => true).read)
56
+ StringIO.new(CMD.cmd("unzip '{opt}' #{filename}", options))
53
57
  end
54
58
  end
55
59
 
data/lib/scout/open.rb CHANGED
@@ -52,9 +52,9 @@ module Open
52
52
 
53
53
  io = file_open(file, grep, mode, invert_grep, fixed_grep, options)
54
54
 
55
- io = unzip(io) if ((String === file and zip?(file)) and not options[:noz]) or options[:zip]
56
- io = gunzip(io) if ((String === file and gzip?(file)) and not options[:noz]) or options[:gzip]
57
- io = bgunzip(io) if ((String === file and bgzip?(file)) and not options[:noz]) or options[:bgzip]
55
+ io = unzip(io, options) if ((String === file and zip?(file)) and not options[:noz]) or options[:zip]
56
+ io = gunzip(io, options) if ((String === file and gzip?(file)) and not options[:noz]) or options[:gzip]
57
+ io = bgunzip(io, options) if ((String === file and bgzip?(file)) and not options[:noz]) or options[:bgzip]
58
58
 
59
59
  io.extend NamedStream
60
60
  io.filename = file
@@ -203,7 +203,7 @@ module Path
203
203
  def annotate_found_where(found, where)
204
204
  self.annotate(found).tap{|p|
205
205
  p.instance_variable_set("@where", where)
206
- p.instance_variable_set("@original", self.dup)
206
+ p.instance_variable_set("@original", self.annotate(self.dup))
207
207
  }
208
208
  end
209
209
 
@@ -288,15 +288,15 @@ module Path
288
288
 
289
289
  def find_with_extension(extension, *args, produce: true)
290
290
  found = self.find(*args)
291
- return found if found.exists?(produce: produce) && ! found.directory?
291
+ return found if found.exists? && ! found.directory?
292
292
  if Array === extension
293
293
  extension.each do |ext|
294
294
  found_with_extension = self.set_extension(ext).find
295
- return found_with_extension if found_with_extension.exists?(produce: produce)
295
+ return found_with_extension if found_with_extension.exists?
296
296
  end
297
297
  else
298
298
  found_with_extension = self.set_extension(extension).find
299
- return found_with_extension if found_with_extension.exists?(produce: produce)
299
+ return found_with_extension if found_with_extension.exists?
300
300
  end
301
301
  return found
302
302
  end
@@ -23,6 +23,15 @@ module Path
23
23
  return true
24
24
  end
25
25
 
26
+ def self.can_write?(string)
27
+ return false if Open.remote?(string)
28
+ return false unless Path.is_filename?(string)
29
+ string = string.find if Path === string
30
+ return false if File.directory?(string)
31
+ return File.writable?(string) if File.exist?(string)
32
+ return File.writable?(File.dirname(string))
33
+ end
34
+
26
35
  def self.sanitize_filename(filename, length = 254)
27
36
  if filename.length > length
28
37
  if filename =~ /(\..{2,9})$/
data/lib/scout/path.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'path/digest'
5
5
 
6
6
  module Path
7
7
  extend Annotation
8
- annotation :pkgdir, :libdir, :path_maps, :map_order
8
+ annotation :pkgdir, :libdir, :path_maps, :map_order, :where, :original
9
9
 
10
10
  def self.default_pkgdir
11
11
  @@default_pkgdir ||= 'scout'
@@ -129,7 +129,7 @@ module Persist
129
129
  type = MEMORY if type == :memory
130
130
  return unless Hash === type || Open.exist?(file)
131
131
 
132
- Log.debug "Load #{Log.fingerprint type} on #{file}"
132
+ Log.debug "Load #{Log.fingerprint type} from #{file}"
133
133
  if load_drivers[type]
134
134
  return load_drivers[type].call(file)
135
135
  end
data/lib/scout/persist.rb CHANGED
@@ -42,16 +42,18 @@ module Persist
42
42
  update = Open.mtime(update) if Path === update
43
43
 
44
44
  update = true if file.outdated?(check) if Path === file && check
45
- file_mtime = Open.mtime(file)
46
- update = file_mtime >= update ? false : true if file_mtime && Time === update
47
- update = file_mtime >= (Time.now - update) ? false : true if file_mtime && Numeric === update
45
+ if update
46
+ file_mtime = Open.mtime(file)
47
+ update = file_mtime >= update ? false : true if file_mtime && Time === update
48
+ update = file_mtime >= (Time.now - update) ? false : true if file_mtime && Numeric === update
49
+ end
48
50
 
49
51
  if type == :memory
50
52
  repo = options[:memory] || options[:repo] || MEMORY_CACHE
51
53
  if update
52
54
  repo[file] = yield
53
55
  else
54
- repo[file] ||= yield
56
+ repo[file] = yield unless repo.include? file
55
57
  end
56
58
  return repo[file]
57
59
  end
@@ -76,6 +76,26 @@ module Path
76
76
 
77
77
  def exists?(produce: true)
78
78
  return true if Open.exists?(self.find)
79
- self.produce if produce
79
+ if produce
80
+ self.produce
81
+ Open.exists?(self.find)
82
+ else
83
+ false
84
+ end
85
+ end
86
+
87
+ def find_with_extension(extension, *args, produce: true)
88
+ found = self.find(*args)
89
+ return found if found.exists?(produce: produce) && ! found.directory?
90
+ if Array === extension
91
+ extension.each do |ext|
92
+ found_with_extension = self.set_extension(ext).find
93
+ return found_with_extension if found_with_extension.exists?(produce: produce)
94
+ end
95
+ else
96
+ found_with_extension = self.set_extension(extension).find
97
+ return found_with_extension if found_with_extension.exists?(produce: produce)
98
+ end
99
+ return found
80
100
  end
81
101
  end
@@ -56,12 +56,13 @@ module ScoutRake
56
56
  Rake::FileTask.clear_files
57
57
  end
58
58
  rescue Exception
59
+ Log.exception $!
59
60
  Log.error "Error in rake: #{$!.message}"
60
61
  Kernel.exit! -1
61
62
  end
62
63
  Kernel.exit! 0
63
64
  }
64
- Process.waitpid(pid)
65
+ Misc.wait_child(pid)
65
66
  raise "Rake failed" unless $?.success?
66
67
 
67
68
  end
@@ -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.5 ruby lib
5
+ # stub: scout-essentials 1.8.7 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "scout-essentials".freeze
9
- s.version = "1.8.5".freeze
9
+ s.version = "1.8.7".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]
@@ -21,5 +21,15 @@ class TestOpenRemote < Test::Unit::TestCase
21
21
  end
22
22
  end if false
23
23
  end
24
+
25
+ def test_error
26
+ teardown
27
+ sss 0
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)
30
+ stream.read
31
+ end
32
+ end
33
+
24
34
  end
25
35
 
@@ -37,5 +37,35 @@ class TestPathUtil < Test::Unit::TestCase
37
37
  refute Path.newer? dir.f2.find, dir.f1.find
38
38
  end
39
39
  end
40
+
41
+ def test_can_read
42
+ TmpFile.with_path do |dir|
43
+ Open.write dir.f1, 'test1'
44
+ sleep 0.1
45
+ Open.write dir.f2, 'test2'
46
+ Open.write dir.f4, 'test4'
47
+
48
+ CMD.cmd("chmod -w #{dir.f4.find}")
49
+
50
+ assert Path.can_read? dir.f1
51
+ assert Path.can_read? dir.f3
52
+ assert Path.can_write? dir.f3
53
+
54
+ refute Path.can_write? dir.f4
55
+
56
+ CMD.cmd("chmod -w #{dir.find}")
57
+ refute Path.can_write? dir.f3
58
+ CMD.cmd("chmod +w #{dir.find}")
59
+ end
60
+ end
61
+
62
+ def test_find_exists
63
+ TmpFile.with_path do |dir|
64
+ Open.write dir.f1.set_extension("rb"), 'test1'
65
+
66
+ assert dir.f1.find_with_extension("rb").exists?
67
+ refute dir.f1.find_with_extension("rp").exists?
68
+ end
69
+ end
40
70
  end
41
71
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scout-essentials
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.5
4
+ version: 1.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez