Module: Pikuri::Os::Recoll

Defined in:
lib/pikuri/os/recoll.rb

Overview

Backend wrapper over the recoll full-text index, via its headless recollq CLI (Xapian-backed). The DE-independent sibling of LocalSearch: same seam shape (Recoll.search / Recoll.read_text / Recoll.available? / Recoll.check_binaries!), interchangeable behind the fileindex_search / fileindex_read tools, and Extension wires whichever is available? first.

Recall on prose ties localsearch, so recoll is not a better engine — it earns its place where localsearch is structurally blind (source trees, .git checkouts, non-GNOME desktops). Its blind spot is the inverse: recoll indexes exactly the folders the user configured (+topdirs+), source code included — everything outside them is invisible. The measured survey behind the pick-one-per-host order is in pikuri-os/DESIGN.md.

Recoll.search asks recollq -F 'url abstract' for two base64-encoded fields per line (recoll's own recommended machine shape) so paths and snippets with spaces/brackets/newlines survive with no escaping guesswork. Recoll.read_text uses recollq -d, which dumps a document's stored/extracted text (PDFs/office docs included) after a header line.

Defined Under Namespace

Classes: CommandError

Constant Summary collapse

BINARY =

Returns the headless recoll query CLI.

Returns:

  • (String)

    the headless recoll query CLI.

'recollq'
INDEXER =

Returns the recoll indexer/config CLI (used only to locate the config; never invoked to index from here).

Returns:

  • (String)

    the recoll indexer/config CLI (used only to locate the config; never invoked to index from here).

'recollindex'
CONFIG_FILE =

Returns recoll config filename inside the config dir.

Returns:

  • (String)

    recoll config filename inside the config dir.

'recoll.conf'
DEFAULT_TOPDIRS =

recoll's compiled-in default when topdirs is unset: the whole home directory (the sample config ships topdirs = ~ too).

Returns:

  • (String)
'~'
INDEX_DIR =

Returns the Xapian index directory name inside the config dir; its presence is what available? treats as "usable".

Returns:

  • (String)

    the Xapian index directory name inside the config dir; its presence is what available? treats as "usable".

'xapiandb'

Class Method Summary collapse

Class Method Details

.available?Boolean

Non-raising usability probe — for callers that select a backend among several and want the one that can actually serve searches. Stricter than check_binaries!: it requires both the recollq binary and a built index (INDEX_DIR under confdir), because an installed-but-never-indexed recoll errors ("Xapian index open error") on every query — so it must not win backend selection over a working alternative. See check_binaries! for why exit status is ignored for the binary half.

Returns:

  • (Boolean)


210
211
212
213
214
215
216
217
# File 'lib/pikuri/os/recoll.rb', line 210

def self.available?
  return false unless File.directory?(File.join(confdir, INDEX_DIR))

  Pikuri::Subprocess.spawn(BINARY, '-h', chdir: '/').wait
  true
rescue Errno::ENOENT
  false
end

.check_binaries!Object

Verify recollq is reachable; raise loudly otherwise. Presence is judged by whether the binary ran (no Errno::ENOENT), not by exit status: recollq -h exits non-zero by design, so a zero-exit probe would false-negative on a perfectly good install.

Raises:

  • (RuntimeError)

    if the binary is missing



194
195
196
197
198
# File 'lib/pikuri/os/recoll.rb', line 194

def self.check_binaries!
  Pikuri::Subprocess.spawn(BINARY, '-h', chdir: '/').wait
rescue Errno::ENOENT
  raise install_hint
end

.confdirString

The recoll configuration directory recoll would use: $RECOLL_CONFDIR if set and non-empty, else ~/.recoll.

Returns:

  • (String)


101
102
103
104
# File 'lib/pikuri/os/recoll.rb', line 101

def self.confdir
  env = ENV['RECOLL_CONFDIR']
  env && !env.empty? ? File.expand_path(env) : File.expand_path('~/.recoll')
end

.extract_dumped_text(raw, abs) ⇒ String?

Pull the dumped body of the result whose header line matches abs out of recollq -d output. Each result is a tab-delimited header line (+\t[]\t[]\t<n>\tbytes+) followed by its text, up to the next header line.</p> </div> </div> <div class="tags"> <p class="tag_title">Parameters:</p> <ul class="param"> <li> <span class='name'>raw</span> <span class='type'>(<tt>String</tt>)</span> </li> <li> <span class='name'>abs</span> <span class='type'>(<tt>String</tt>)</span> — <div class='inline'><p>absolute path to match against the header URL</p></div> </li> </ul> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>String</tt>, <tt>nil</tt>)</span> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 171</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_extract_dumped_text'>extract_dumped_text</span><span class='lparen'>(</span><span class='id identifier rubyid_raw'>raw</span><span class='comma'>,</span> <span class='id identifier rubyid_abs'>abs</span><span class='rparen'>)</span> <span class='id identifier rubyid_want'>want</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>"</span><span class='tstring_content'>file://</span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_abs'>abs</span><span class='embexpr_end'>}</span><span class='tstring_end'>"</span></span> <span class='id identifier rubyid_body'>body</span> <span class='op'>=</span> <span class='lbracket'>[</span><span class='rbracket'>]</span> <span class='id identifier rubyid_capturing'>capturing</span> <span class='op'>=</span> <span class='kw'>false</span> <span class='id identifier rubyid_raw'>raw</span><span class='period'>.</span><span class='id identifier rubyid_each_line'>each_line</span> <span class='kw'>do</span> <span class='op'>|</span><span class='id identifier rubyid_line'>line</span><span class='op'>|</span> <span class='id identifier rubyid_url'>url</span> <span class='op'>=</span> <span class='id identifier rubyid_line'>line</span><span class='lbracket'>[</span><span class='tstring'><span class='regexp_beg'>%r{</span><span class='tstring_content'>\t\[(file://[^\]]*)\]\t</span><span class='regexp_end'>}</span></span><span class='comma'>,</span> <span class='int'>1</span><span class='rbracket'>]</span> <span class='kw'>if</span> <span class='id identifier rubyid_url'>url</span> <span class='kw'>break</span> <span class='kw'>if</span> <span class='id identifier rubyid_capturing'>capturing</span> <span class='id identifier rubyid_capturing'>capturing</span> <span class='op'>=</span> <span class='lparen'>(</span><span class='id identifier rubyid_url'>url</span> <span class='op'>==</span> <span class='id identifier rubyid_want'>want</span><span class='rparen'>)</span> <span class='kw'>elsif</span> <span class='id identifier rubyid_capturing'>capturing</span> <span class='id identifier rubyid_body'>body</span> <span class='op'><<</span> <span class='id identifier rubyid_line'>line</span> <span class='kw'>end</span> <span class='kw'>end</span> <span class='id identifier rubyid_capturing'>capturing</span> <span class='op'>||</span> <span class='op'>!</span><span class='id identifier rubyid_body'>body</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span> <span class='op'>?</span> <span class='id identifier rubyid_body'>body</span><span class='period'>.</span><span class='id identifier rubyid_join'>join</span><span class='period'>.</span><span class='id identifier rubyid_chomp'>chomp</span> <span class='op'>:</span> <span class='kw'>nil</span> <span class='kw'>end</span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="indexed_dirs-class_method"> .<strong>indexed_dirs</strong> ⇒ <tt>Array<String></tt> </h3><div class="docstring"> <div class="discussion"> <p>Folders recoll is configured to index, read from <code>topdirs</code> in the active config file. Resolution mirrors recoll itself: the config dir is <code>$RECOLL_CONFDIR</code> or <code>~/.recoll</code>; <code>topdirs</code> is a space-separated list (quoted entries may contain spaces, a trailing <code>\</code> continues to the next line) living in the global section, i.e. above the first <code>[section]</code> header. Entries are +~+- and +$VAR+-expanded to absolute paths. Defaults to the home dir (<span class='object_link'><a href="#DEFAULT_TOPDIRS-constant" title="Pikuri::Os::Recoll::DEFAULT_TOPDIRS (constant)">DEFAULT_TOPDIRS</a></span>) when the config is absent or names no <code>topdirs</code>, matching recoll's own default.</p> </div> </div> <div class="tags"> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>Array<String></tt>)</span> — <div class='inline'><p>absolute folder paths, or <code>[]</code> if the config dir exists but the file is unreadable</p></div> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 87 88 89 90 91 92 93 94 95</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 87</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_indexed_dirs'>indexed_dirs</span> <span class='id identifier rubyid_path'>path</span> <span class='op'>=</span> <span class='const'>File</span><span class='period'>.</span><span class='id identifier rubyid_join'>join</span><span class='lparen'>(</span><span class='id identifier rubyid_confdir'>confdir</span><span class='comma'>,</span> <span class='const'><span class='object_link'><a href="#CONFIG_FILE-constant" title="Pikuri::Os::Recoll::CONFIG_FILE (constant)">CONFIG_FILE</a></span></span><span class='rparen'>)</span> <span class='id identifier rubyid_raw'>raw</span> <span class='op'>=</span> <span class='const'>File</span><span class='period'>.</span><span class='id identifier rubyid_exist?'>exist?</span><span class='lparen'>(</span><span class='id identifier rubyid_path'>path</span><span class='rparen'>)</span> <span class='op'>?</span> <span class='const'>File</span><span class='period'>.</span><span class='id identifier rubyid_read'>read</span><span class='lparen'>(</span><span class='id identifier rubyid_path'>path</span><span class='rparen'>)</span> <span class='op'>:</span> <span class='kw'>nil</span> <span class='id identifier rubyid_entries'>entries</span> <span class='op'>=</span> <span class='id identifier rubyid_raw'>raw</span> <span class='op'>?</span> <span class='id identifier rubyid_parse_topdirs'>parse_topdirs</span><span class='lparen'>(</span><span class='id identifier rubyid_raw'>raw</span><span class='rparen'>)</span> <span class='op'>:</span> <span class='lbracket'>[</span><span class='const'><span class='object_link'><a href="#DEFAULT_TOPDIRS-constant" title="Pikuri::Os::Recoll::DEFAULT_TOPDIRS (constant)">DEFAULT_TOPDIRS</a></span></span><span class='rbracket'>]</span> <span class='id identifier rubyid_entries'>entries</span> <span class='op'>=</span> <span class='lbracket'>[</span><span class='const'><span class='object_link'><a href="#DEFAULT_TOPDIRS-constant" title="Pikuri::Os::Recoll::DEFAULT_TOPDIRS (constant)">DEFAULT_TOPDIRS</a></span></span><span class='rbracket'>]</span> <span class='kw'>if</span> <span class='id identifier rubyid_entries'>entries</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span> <span class='id identifier rubyid_entries'>entries</span><span class='period'>.</span><span class='id identifier rubyid_map'>map</span> <span class='lbrace'>{</span> <span class='op'>|</span><span class='id identifier rubyid_entry'>entry</span><span class='op'>|</span> <span class='id identifier rubyid_expand_entry'>expand_entry</span><span class='lparen'>(</span><span class='id identifier rubyid_entry'>entry</span><span class='rparen'>)</span> <span class='rbrace'>}</span> <span class='kw'>rescue</span> <span class='const'>SystemCallError</span> <span class='lbracket'>[</span><span class='rbracket'>]</span> <span class='kw'>end</span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="label-class_method"> .<strong>label</strong> ⇒ <tt>String</tt> </h3><div class="docstring"> <div class="discussion"> <p>Returns short human name for this backend, used by the fileindex tools when prefixing an <code>"Error: ..."</code> observation.</p> </div> </div> <div class="tags"> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>String</tt>)</span> — <div class='inline'><p>short human name for this backend, used by the fileindex tools when prefixing an <code>"Error: ..."</code> observation.</p></div> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 54</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 54</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_label'>label</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>recoll</span><span class='tstring_end'>'</span></span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="limitations-class_method"> .<strong>limitations</strong> ⇒ <tt>String</tt> </h3><div class="docstring"> <div class="discussion"> <p>Plain-English note of what this backend will <em>not</em> surface, appended to the fileindex tools' LLM description so the model knows when to fall back to a filesystem text/regex search. A <em>method</em>, not a constant like <span class='object_link'><a href="LocalSearch.html#LIMITATIONS-constant" title="Pikuri::Os::LocalSearch::LIMITATIONS (constant)">LocalSearch::LIMITATIONS</a></span>, because the blind spot is host-specific: the user's <code>topdirs</code>, only readable at runtime (<span class='object_link'><a href="#indexed_dirs-class_method" title="Pikuri::Os::Recoll.indexed_dirs (method)">indexed_dirs</a></span>). Falls back to a generic caveat when the config can't be read.</p> </div> </div> <div class="tags"> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>String</tt>)</span> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 65 66 67 68 69 70 71 72 73 74</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 65</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_limitations'>limitations</span> <span class='id identifier rubyid_dirs'>dirs</span> <span class='op'>=</span> <span class='id identifier rubyid_indexed_dirs'>indexed_dirs</span> <span class='kw'>return</span> <span class='id identifier rubyid_generic_limitation'>generic_limitation</span> <span class='kw'>if</span> <span class='id identifier rubyid_dirs'>dirs</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span> <span class='id identifier rubyid_listed'>listed</span> <span class='op'>=</span> <span class='id identifier rubyid_dirs'>dirs</span><span class='period'>.</span><span class='id identifier rubyid_map'>map</span> <span class='lbrace'>{</span> <span class='op'>|</span><span class='id identifier rubyid_dir'>dir</span><span class='op'>|</span> <span class='tstring'><span class='tstring_beg'>"</span><span class='tstring_content'>- </span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_dir'>dir</span><span class='embexpr_end'>}</span><span class='tstring_end'>"</span></span> <span class='rbrace'>}</span><span class='period'>.</span><span class='id identifier rubyid_join'>join</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>"</span><span class='tstring_content'>\n</span><span class='tstring_end'>"</span></span><span class='rparen'>)</span> <span class='heredoc_beg'><<~LIMITS</span><span class='period'>.</span><span class='id identifier rubyid_chomp'>chomp</span> <span class='tstring_content'> Only files under these folders are indexed (anything elsewhere on disk is not in this index — search those files' text directly instead): </span><span class='tstring_content'> </span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_listed'>listed</span><span class='embexpr_end'>}</span><span class='tstring_content'> </span><span class='heredoc_end'> LIMITS </span><span class='kw'>end</span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="parse_search_output-class_method"> .<strong>parse_search_output</strong>(raw) ⇒ <tt>Array<Hash></tt> </h3><div class="docstring"> <div class="discussion"> <p>Parse <code>recollq -F 'url abstract'</code> output into hits. Each data line is two base64 tokens (url, abstract); recollq's <code>"Recoll query: …"</code> / <code>"N results"</code> banner lines are skipped because their first token isn't valid base64 for a <code>file://</code> URL.</p> </div> </div> <div class="tags"> <p class="tag_title">Parameters:</p> <ul class="param"> <li> <span class='name'>raw</span> <span class='type'>(<tt>String</tt>)</span> </li> </ul> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>Array<Hash></tt>)</span> — <div class='inline'><p><code>[{ path:, snippet: }, …]</code> de-duped by path</p></div> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 132 133 134 135 136 137 138 139 140 141 142 143 144</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 132</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_parse_search_output'>parse_search_output</span><span class='lparen'>(</span><span class='id identifier rubyid_raw'>raw</span><span class='rparen'>)</span> <span class='id identifier rubyid_hits'>hits</span> <span class='op'>=</span> <span class='lbracket'>[</span><span class='rbracket'>]</span> <span class='id identifier rubyid_raw'>raw</span><span class='period'>.</span><span class='id identifier rubyid_each_line'>each_line</span> <span class='kw'>do</span> <span class='op'>|</span><span class='id identifier rubyid_line'>line</span><span class='op'>|</span> <span class='id identifier rubyid_url_b64'>url_b64</span><span class='comma'>,</span> <span class='id identifier rubyid_abstract_b64'>abstract_b64</span> <span class='op'>=</span> <span class='id identifier rubyid_line'>line</span><span class='period'>.</span><span class='id identifier rubyid_split'>split</span><span class='lparen'>(</span><span class='tstring'><span class='regexp_beg'>/</span><span class='tstring_content'>\s+</span><span class='regexp_end'>/</span></span><span class='comma'>,</span> <span class='int'>2</span><span class='rparen'>)</span> <span class='id identifier rubyid_url'>url</span> <span class='op'>=</span> <span class='id identifier rubyid_decode_base64'>decode_base64</span><span class='lparen'>(</span><span class='id identifier rubyid_url_b64'>url_b64</span><span class='rparen'>)</span> <span class='kw'>next</span> <span class='kw'>unless</span> <span class='id identifier rubyid_url'>url</span><span class='op'>&.</span><span class='id identifier rubyid_start_with?'>start_with?</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>file://</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span> <span class='id identifier rubyid_snippet'>snippet</span> <span class='op'>=</span> <span class='id identifier rubyid_decode_base64'>decode_base64</span><span class='lparen'>(</span><span class='id identifier rubyid_abstract_b64'>abstract_b64</span><span class='period'>.</span><span class='id identifier rubyid_to_s'>to_s</span><span class='period'>.</span><span class='id identifier rubyid_strip'>strip</span><span class='rparen'>)</span> <span class='id identifier rubyid_snippet'>snippet</span> <span class='op'>=</span> <span class='id identifier rubyid_snippet'>snippet</span><span class='op'>&.</span><span class='id identifier rubyid_strip'>strip</span> <span class='id identifier rubyid_hits'>hits</span> <span class='op'><<</span> <span class='lbrace'>{</span> <span class='label'>path:</span> <span class='id identifier rubyid_url'>url</span><span class='period'>.</span><span class='id identifier rubyid_delete_prefix'>delete_prefix</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>file://</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='comma'>,</span> <span class='label'>snippet:</span> <span class='lparen'>(</span><span class='id identifier rubyid_snippet'>snippet</span> <span class='kw'>unless</span> <span class='id identifier rubyid_snippet'>snippet</span><span class='period'>.</span><span class='id identifier rubyid_to_s'>to_s</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span><span class='rparen'>)</span> <span class='rbrace'>}</span> <span class='kw'>end</span> <span class='id identifier rubyid_hits'>hits</span><span class='period'>.</span><span class='id identifier rubyid_uniq'>uniq</span> <span class='lbrace'>{</span> <span class='op'>|</span><span class='id identifier rubyid_hit'>hit</span><span class='op'>|</span> <span class='id identifier rubyid_hit'>hit</span><span class='lbracket'>[</span><span class='symbol'>:path</span><span class='rbracket'>]</span> <span class='rbrace'>}</span> <span class='kw'>end</span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="parse_topdirs-class_method"> .<strong>parse_topdirs</strong>(raw) ⇒ <tt>Array<String></tt> </h3><div class="docstring"> <div class="discussion"> <p>Join backslash-continued lines, then read the <code>topdirs</code> value from the global section (everything before the first <code>[section]</code> header). The last assignment wins (ConfSimple semantics).</p> </div> </div> <div class="tags"> <p class="tag_title">Parameters:</p> <ul class="param"> <li> <span class='name'>raw</span> <span class='type'>(<tt>String</tt>)</span> </li> </ul> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>Array<String></tt>)</span> — <div class='inline'><p>raw (unexpanded) topdirs entries</p></div> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 225 226 227 228 229 230 231 232 233 234 235 236</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 225</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_parse_topdirs'>parse_topdirs</span><span class='lparen'>(</span><span class='id identifier rubyid_raw'>raw</span><span class='rparen'>)</span> <span class='id identifier rubyid_value'>value</span> <span class='op'>=</span> <span class='kw'>nil</span> <span class='id identifier rubyid_joined_lines'>joined_lines</span><span class='lparen'>(</span><span class='id identifier rubyid_raw'>raw</span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_each'>each</span> <span class='kw'>do</span> <span class='op'>|</span><span class='id identifier rubyid_line'>line</span><span class='op'>|</span> <span class='id identifier rubyid_stripped'>stripped</span> <span class='op'>=</span> <span class='id identifier rubyid_line'>line</span><span class='period'>.</span><span class='id identifier rubyid_strip'>strip</span> <span class='kw'>break</span> <span class='kw'>if</span> <span class='id identifier rubyid_stripped'>stripped</span><span class='period'>.</span><span class='id identifier rubyid_start_with?'>start_with?</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>[</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span> <span class='comment'># first [section] ends the global scope </span> <span class='kw'>next</span> <span class='kw'>if</span> <span class='id identifier rubyid_stripped'>stripped</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span> <span class='op'>||</span> <span class='id identifier rubyid_stripped'>stripped</span><span class='period'>.</span><span class='id identifier rubyid_start_with?'>start_with?</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>#</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span> <span class='id identifier rubyid_key'>key</span><span class='comma'>,</span> <span class='id identifier rubyid__'>_</span><span class='comma'>,</span> <span class='id identifier rubyid_rest'>rest</span> <span class='op'>=</span> <span class='id identifier rubyid_stripped'>stripped</span><span class='period'>.</span><span class='id identifier rubyid_partition'>partition</span><span class='lparen'>(</span><span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>=</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span> <span class='id identifier rubyid_value'>value</span> <span class='op'>=</span> <span class='id identifier rubyid_rest'>rest</span> <span class='kw'>if</span> <span class='id identifier rubyid_key'>key</span><span class='period'>.</span><span class='id identifier rubyid_strip'>strip</span> <span class='op'>==</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>topdirs</span><span class='tstring_end'>'</span></span> <span class='kw'>end</span> <span class='id identifier rubyid_value'>value</span> <span class='op'>?</span> <span class='id identifier rubyid_tokenize'>tokenize</span><span class='lparen'>(</span><span class='id identifier rubyid_value'>value</span><span class='rparen'>)</span> <span class='op'>:</span> <span class='lbracket'>[</span><span class='rbracket'>]</span> <span class='kw'>end</span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="read_text-class_method"> .<strong>read_text</strong>(path) ⇒ <tt>String</tt><sup>?</sup> </h3><div class="docstring"> <div class="discussion"> <p>Full stored/extracted text of an indexed file, via <code>recollq -d</code> scoped to the file's own name. Works for PDFs/office docs (extracted at index time). <code>nil</code> when the path isn't in the index or has no stored text — the caller turns that into an LLM-facing message.</p> </div> </div> <div class="tags"> <p class="tag_title">Parameters:</p> <ul class="param"> <li> <span class='name'>path</span> <span class='type'>(<tt>String</tt>)</span> </li> </ul> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>String</tt>, <tt>nil</tt>)</span> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 153 154 155 156 157 158 159 160 161</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 153</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_read_text'>read_text</span><span class='lparen'>(</span><span class='id identifier rubyid_path'>path</span><span class='rparen'>)</span> <span class='id identifier rubyid_abs'>abs</span> <span class='op'>=</span> <span class='const'>File</span><span class='period'>.</span><span class='id identifier rubyid_expand_path'>expand_path</span><span class='lparen'>(</span><span class='id identifier rubyid_path'>path</span><span class='rparen'>)</span> <span class='id identifier rubyid_result'>result</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="../../Pikuri.html" title="Pikuri (module)">Pikuri</a></span></span><span class='op'>::</span><span class='const'>Subprocess</span><span class='period'>.</span><span class='id identifier rubyid_spawn'>spawn</span><span class='lparen'>(</span><span class='const'><span class='object_link'><a href="#BINARY-constant" title="Pikuri::Os::Recoll::BINARY (constant)">BINARY</a></span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>-d</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>-n</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>50</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>"</span><span class='tstring_content'>filename:</span><span class='embexpr_beg'>#{</span><span class='const'>File</span><span class='period'>.</span><span class='id identifier rubyid_basename'>basename</span><span class='lparen'>(</span><span class='id identifier rubyid_abs'>abs</span><span class='rparen'>)</span><span class='embexpr_end'>}</span><span class='tstring_end'>"</span></span><span class='comma'>,</span> <span class='label'>chdir:</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>/</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_wait'>wait</span> <span class='kw'>return</span> <span class='kw'>nil</span> <span class='kw'>unless</span> <span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_status'>status</span><span class='period'>.</span><span class='id identifier rubyid_success?'>success?</span> <span class='id identifier rubyid_text'>text</span> <span class='op'>=</span> <span class='id identifier rubyid_extract_dumped_text'>extract_dumped_text</span><span class='lparen'>(</span><span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_output'>output</span><span class='comma'>,</span> <span class='id identifier rubyid_abs'>abs</span><span class='rparen'>)</span> <span class='id identifier rubyid_text'>text</span> <span class='op'>&&</span> <span class='op'>!</span><span class='id identifier rubyid_text'>text</span><span class='period'>.</span><span class='id identifier rubyid_strip'>strip</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span> <span class='op'>?</span> <span class='id identifier rubyid_text'>text</span> <span class='op'>:</span> <span class='kw'>nil</span> <span class='kw'>end</span></pre> </td> </tr> </table> </div> <div class="method_details "> <h3 class="signature " id="search-class_method"> .<strong>search</strong>(query:, limit:) ⇒ <tt>Array<Hash></tt> </h3><div class="docstring"> <div class="discussion"> <p>Run <code>recollq -F 'url abstract' -n <limit></code> and parse the results.</p> </div> </div> <div class="tags"> <p class="tag_title">Parameters:</p> <ul class="param"> <li> <span class='name'>query</span> <span class='type'>(<tt>String</tt>)</span> — <div class='inline'><p>recoll query-language string</p></div> </li> <li> <span class='name'>limit</span> <span class='type'>(<tt>Integer</tt>)</span> </li> </ul> <p class="tag_title">Returns:</p> <ul class="return"> <li> <span class='type'>(<tt>Array<Hash></tt>)</span> — <div class='inline'><p><code>[{ path: String, snippet: String|nil }, …]</code>, de-duplicated by path</p></div> </li> </ul> <p class="tag_title">Raises:</p> <ul class="raise"> <li> <span class='type'>(<tt><span class='object_link'><a href="Recoll/CommandError.html" title="Pikuri::Os::Recoll::CommandError (class)">CommandError</a></span></tt>)</span> — <div class='inline'><p>if recollq exits non-zero</p></div> </li> </ul> </div><table class="source_code"> <tr> <td> <pre class="lines"> 113 114 115 116 117 118 119 120 121 122 123</pre> </td> <td> <pre class="code"><span class="info file"># File 'lib/pikuri/os/recoll.rb', line 113</span> <span class='kw'>def</span> <span class='kw'>self</span><span class='period'>.</span><span class='id identifier rubyid_search'>search</span><span class='lparen'>(</span><span class='label'>query:</span><span class='comma'>,</span> <span class='label'>limit:</span><span class='rparen'>)</span> <span class='id identifier rubyid_result'>result</span> <span class='op'>=</span> <span class='const'><span class='object_link'><a href="../../Pikuri.html" title="Pikuri (module)">Pikuri</a></span></span><span class='op'>::</span><span class='const'>Subprocess</span><span class='period'>.</span><span class='id identifier rubyid_spawn'>spawn</span><span class='lparen'>(</span><span class='const'><span class='object_link'><a href="#BINARY-constant" title="Pikuri::Os::Recoll::BINARY (constant)">BINARY</a></span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>-F</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>url abstract</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>-n</span><span class='tstring_end'>'</span></span><span class='comma'>,</span> <span class='id identifier rubyid_limit'>limit</span><span class='period'>.</span><span class='id identifier rubyid_to_s'>to_s</span><span class='comma'>,</span> <span class='id identifier rubyid_query'>query</span><span class='comma'>,</span> <span class='label'>chdir:</span> <span class='tstring'><span class='tstring_beg'>'</span><span class='tstring_content'>/</span><span class='tstring_end'>'</span></span><span class='rparen'>)</span><span class='period'>.</span><span class='id identifier rubyid_wait'>wait</span> <span class='kw'>unless</span> <span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_status'>status</span><span class='period'>.</span><span class='id identifier rubyid_success?'>success?</span> <span class='id identifier rubyid_stderr'>stderr</span> <span class='op'>=</span> <span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_output'>output</span><span class='period'>.</span><span class='id identifier rubyid_strip'>strip</span> <span class='id identifier rubyid_stderr'>stderr</span> <span class='op'>=</span> <span class='tstring'><span class='tstring_beg'>"</span><span class='tstring_content'>exited </span><span class='embexpr_beg'>#{</span><span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_status'>status</span><span class='period'>.</span><span class='id identifier rubyid_exitstatus'>exitstatus</span><span class='embexpr_end'>}</span><span class='tstring_end'>"</span></span> <span class='kw'>if</span> <span class='id identifier rubyid_stderr'>stderr</span><span class='period'>.</span><span class='id identifier rubyid_empty?'>empty?</span> <span class='id identifier rubyid_raise'>raise</span> <span class='const'><span class='object_link'><a href="Recoll/CommandError.html" title="Pikuri::Os::Recoll::CommandError (class)">CommandError</a></span></span><span class='comma'>,</span> <span class='id identifier rubyid_stderr'>stderr</span> <span class='kw'>end</span> <span class='id identifier rubyid_parse_search_output'>parse_search_output</span><span class='lparen'>(</span><span class='id identifier rubyid_result'>result</span><span class='period'>.</span><span class='id identifier rubyid_output'>output</span><span class='rparen'>)</span> <span class='kw'>end</span></pre> </td> </tr> </table> </div> </div> </div> <div id="footer"> Generated on Sun Aug 30 17:27:39 2026 by <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a> 0.9.45 (ruby-4.0.2). </div> </div> </body> </html>