scout-essentials 1.8.6 → 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: 860143272938aa7cf5cea99f3c6b58206ed6670cf0c8638adcfe62774fcab925
4
- data.tar.gz: 6c92e398a80f3d39c3cae9db84d947c9386d8a08cb0bfffab2239c0810d1ba6e
3
+ metadata.gz: 3193beb1b7024505d5a238bb6d9caa280ddab034b23431652138697ebc9ac7f0
4
+ data.tar.gz: 29ef13d1a7862228eabc8f153f602ba728eda98cf4907b23770a8fb65cb36b98
5
5
  SHA512:
6
- metadata.gz: aef77cc47e9de21fa0da8ddc9e0246afd91849aad07b3e7d8b33813098cde01a9971c6dcd58d8b07d2b9c372a580ce74aaa484258b5cf914c48e0eae28dc165e
7
- data.tar.gz: 4638f2423e319274ff0ad0b5885b9669b156c519278e775e6ab33733faf0b1c2abeb561f7256c8ebada13c4faaaf2325897a39e0d85b0c49d19413dd0fc6c125
6
+ metadata.gz: c074200ead16664e4ecdcd262315f389500cd59391b533bcdd1f4a84bdf9ab3fcf96152698f643393d198d9cc1d552707144fd4fbc9c962badbb33d57c79d364
7
+ data.tar.gz: 57663384b0258537a626eb867e8dd209c9e84bc477dbc462c6096af539f54b176936b1cd4ace7cd47525b5e791a6b13ac41a000fdf2709a9add3a830eb1a33ab
data/.vimproject CHANGED
@@ -1,146 +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
4
5
  intro
5
6
 
6
7
  review
7
8
  document.rb
8
- docs=docs{
9
- Analyze Annotation tests
10
- Analyze CMD and related tests
11
- Analyze CMD tests
12
- Analyze log tests
13
- Analyze Open tests
14
- Analyze Path tests
15
- Analyze Persist tests
16
- Analyze Resource tests
17
- Analyze selected tests
18
- simple_opt, log, open, persist, cmd, tmpfile
19
-
20
- Analyze SimpleOpt tests
21
- Analyze simple_opt tests
22
- Analyze tests: simple_opt, log, open, persist, cmd, tmpfile
23
- Analyze tmpfile tests
24
- Analyze Utilities tests
25
- Assemble Annotation docs
26
- Assemble CMD docs
27
- Assemble CMD docs
28
- update
29
-
30
- Assemble Open docs
31
- Assemble Open docs
32
- update
33
-
34
- Assemble Path docs
35
- Assemble Persist docs
36
- Assemble Persist docs
37
- update
38
-
39
- Assemble Resource docs
40
- Assemble Resource docs
41
- update
42
-
43
- Assemble Top-level README
44
- Assemble Top-level README
45
- update
46
-
47
- Assemble Top-level README draft
48
- Assemble Utilities docs
49
- Assemble Utilities docs
50
- update
51
-
52
- Confirm·Explore Utilities dirs
53
- Explore Annotation dirs
54
- Explore CMD dirs
55
- Explore Log dir
56
- Explore Log lib dir
57
- Explore Log lib dir
58
- refresh
59
-
60
- Explore Log test dir
61
- Explore Log test dir
62
- refresh
63
-
64
- Explore Open dirs
65
- Explore Path dirs
66
- Explore Persist dirs
67
- Explore Resource dirs
68
- Explore Utilities dirs
69
- Finalize README step
70
- refresh
71
-
72
- Finalize README.md into memory key
73
- .md
74
-
75
- conditional
76
-
77
- NEXT_STEPS
78
- QA Annotation docs
79
- QA CMD docs
80
- QA Integrated README
81
- QA Integrated README
82
- post-update
83
-
84
- QA Integrated README
85
- post-update
86
-
87
- refresh
88
-
89
- QA Open docs
90
- QA Path docs
91
- QA Persist docs
92
- QA Resource docs
93
- QA Utilities docs
94
- Read Annotation source files
95
- Read CMD source files
96
- Read CMD source files
97
- refresh
98
-
99
- Read Log source files
100
- Read Log source files
101
- iterate
102
-
103
- Read Log source files
104
- refresh
105
-
106
- Read Log source files
107
- use explorer output
108
-
109
- Read Open source files
110
- Read Open stream and lockfile
111
- Read Path source files
112
- Read Persist serialize and open
113
- Read Persist source files
114
- Read Resource scout.rb
115
- Read Resource source files
116
- Read SimpleOpt source files
117
- Read TmpFile and IndiferentHash source files
118
- Read Utilities source files
119
- Run QA Integrator on assembled docs
120
- Update CMD docs
121
- assemble
122
-
123
- Update Open docs
124
- assemble
125
-
126
- Update Persist docs
127
- assemble
128
-
129
- Update Resource docs
130
- assemble
131
-
132
- Update Utilities docs
133
- assemble
134
-
135
- final=final{
136
- annotation.md
137
- cmd.md
138
- open.md
139
- path.md
140
- persist.md
141
- resource.md
142
- }
143
- }
144
9
  }
145
10
  doc=doc{
146
11
  Annotation.md
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.8.6
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.
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]}...>"
@@ -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
@@ -45,7 +45,7 @@ module Misc
45
45
 
46
46
  i = 0
47
47
  #size = size + offset + indent
48
- re = /((?:\n\s*\n\s*)|(?:\n\s*(?=\*))|(?:\n\s\s+)|(?:\n\s*[-\*]))/
48
+ re = /((?:\n\s*\n\s*)|(?:\n\s*(?=\*))|(?:\n```)|(?:\n\s*[-\*])|(?:\n\s\s+))/
49
49
  text.split(re).collect do |paragraph|
50
50
  i += 1
51
51
  str = if i % 2 == 1
@@ -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
@@ -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
@@ -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
@@ -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.6 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.6".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]
@@ -159,7 +159,7 @@ Gem::Specification.new do |s|
159
159
  ]
160
160
  s.homepage = "http://github.com/mikisvaz/scout-essentials".freeze
161
161
  s.licenses = ["MIT".freeze]
162
- s.rubygems_version = "3.7.2".freeze
162
+ s.rubygems_version = "3.7.0.dev".freeze
163
163
  s.summary = "Scout essential tools".freeze
164
164
 
165
165
  s.specification_version = 4
@@ -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
 
@@ -58,5 +58,14 @@ class TestPathUtil < Test::Unit::TestCase
58
58
  CMD.cmd("chmod +w #{dir.find}")
59
59
  end
60
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
61
70
  end
62
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.6
4
+ version: 1.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Miguel Vazquez
@@ -298,7 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
298
  - !ruby/object:Gem::Version
299
299
  version: '0'
300
300
  requirements: []
301
- rubygems_version: 3.7.2
301
+ rubygems_version: 3.7.0.dev
302
302
  specification_version: 4
303
303
  summary: Scout essential tools
304
304
  test_files: []