pikuri-extractors 0.0.7 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +3 -2
- data/lib/pikuri/extractors/documents.rb +39 -130
- data/lib/pikuri-extractors.rb +4 -13
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 987bc9fcc9828a020ffaabd52551f22c40d5862a807cb8a6b528dd807182ab7b
|
|
4
|
+
data.tar.gz: c2e1ac45003e57acf35efde390d200b12b447ce0249c43a8748eddf431bda4e3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 45e4ea2c9d7127f2d45621dab27e03d54d742aeaca310424c6cc21463993c08129dbc716dd9738d70daf09c6c0f55564762441472eeb9a8c1b30c95fcd4a9558
|
|
7
|
+
data.tar.gz: c64393a2f30e85803262021cc7756d38b3ffb773589d7a1f278eefe7454e77e8bf7a73f282e920e8f1e96b61144852fb8d244a7364007e64f5dcdbeda4e651dc
|
data/README.md
CHANGED
|
@@ -50,8 +50,9 @@ wiring.** This gem parses PDFs inside the sandbox (poppler is native
|
|
|
50
50
|
code chewing attacker-controlled bytes — exactly what the container
|
|
51
51
|
is for) but re-converts the whole document on every paged read;
|
|
52
52
|
pikuri-pdf is in-process pure Ruby with lazy page-windowed reads and
|
|
53
|
-
no infrastructure to set up. The guide wires pikuri-pdf in
|
|
54
|
-
and supersedes it with this gem in
|
|
53
|
+
no infrastructure to set up. The guide wires pikuri-pdf in the
|
|
54
|
+
vector-DB chapter and supersedes it with this gem in the
|
|
55
|
+
universal-assistant chapter.
|
|
55
56
|
|
|
56
57
|
## Install
|
|
57
58
|
|
|
@@ -6,120 +6,35 @@ module Pikuri
|
|
|
6
6
|
module Extractors
|
|
7
7
|
# Document extractor for the {Pikuri::Extractor} registry:
|
|
8
8
|
# DOCX / ODT / XLSX / legacy XLS / PPTX / EPUB / RTF / PDF →
|
|
9
|
-
# Markdown, by piping the document bytes through pandoc (ODF,
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# Markdown, by piping the document bytes through pandoc (ODF, RTF,
|
|
10
|
+
# EPUB, DOCX), markitdown (OOXML spreadsheet / presentation), or
|
|
11
|
+
# pdftotext (PDF), selected per format.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# Prefers a one-shot, networkless docker container ({IMAGE}) — bytes
|
|
14
|
+
# in via stdin, Markdown out via stdout, no volume mounts — and falls
|
|
15
|
+
# back to host +pandoc+ / +markitdown+ / +pdftotext+ CLIs on the same
|
|
16
|
+
# stdin→stdout contract when docker is absent. Requiring the gem
|
|
17
|
+
# defines this class + the shared {DOCUMENTS} instance but registers
|
|
18
|
+
# nothing; a host opts in with +DOCUMENTS.register+.
|
|
14
19
|
#
|
|
15
|
-
# The
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# converter directly:
|
|
21
|
-
#
|
|
22
|
-
# * *Security.* These documents typically arrive via +fetch+ /
|
|
23
|
-
# +web_scrape+ — untrusted bytes — and complex format parsers
|
|
24
|
-
# are a classic exploitation surface. In the container the
|
|
25
|
-
# parser sees no network and no host filesystem; the worst a
|
|
26
|
-
# malicious document can do is produce garbage Markdown.
|
|
27
|
-
# * *Reproducibility.* The Dockerfile pins pandoc (via the base
|
|
28
|
-
# image's apt) and markitdown (exact pip version); a host
|
|
29
|
-
# install is whatever version the machine happens to have.
|
|
30
|
-
#
|
|
31
|
-
# When docker is unavailable (binary absent or daemon down), the
|
|
32
|
-
# extractor falls back to host-installed +pandoc+ / +markitdown+
|
|
33
|
-
# / +pdftotext+ CLIs — same stdin→stdout contract, one code path
|
|
34
|
-
# with two argv builders. Which arm was picked is logged once via
|
|
35
|
-
# +Pikuri.logger_for('Extractors')+.
|
|
36
|
-
#
|
|
37
|
-
# == Registration is explicit
|
|
38
|
-
#
|
|
39
|
-
# Requiring pikuri-extractors defines this class and the shared
|
|
40
|
-
# {DOCUMENTS} instance but registers nothing. A host script opts
|
|
41
|
-
# in with +Pikuri::Extractors::DOCUMENTS.register+, which inserts
|
|
42
|
-
# the instance before the registry's terminal +Passthrough+ entry
|
|
43
|
-
# (and after core's +HTML+ — and pikuri-pdf's front-inserted
|
|
44
|
-
# +PDF+, when the host registers that too — which keep winning
|
|
45
|
-
# their formats). Same opt-in philosophy as +c.add_extension+ —
|
|
46
|
-
# no behavior changes by require alone.
|
|
20
|
+
# The cross-cutting *why* — the container's security + reproducibility
|
|
21
|
+
# rationale, the PDF-arm trade-off against pikuri-pdf, the
|
|
22
|
+
# deliberately-unsupported formats (ODS/ODP, OCR/audio), and why
|
|
23
|
+
# paging re-converts each time — lives in
|
|
24
|
+
# +pikuri-extractors/DESIGN.md+.
|
|
47
25
|
#
|
|
48
26
|
# == Format detection
|
|
49
27
|
#
|
|
50
|
-
# {#matches?} claims content by
|
|
51
|
-
# ({
|
|
52
|
-
# +
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# OLE2 container whose discriminating directory sits at the end of
|
|
61
|
-
# the file, past the sample), the bytes go to markitdown with no
|
|
62
|
-
# format hint and its own magic-byte detection takes over. The
|
|
63
|
-
# consequence: a *local* +.xls+ (no transport content-type, sniff
|
|
64
|
-
# blind) is not claimed at all and keeps today's binary refusal.
|
|
65
|
-
# One ordering edge vs pikuri-pdf: this instance sits *after*
|
|
66
|
-
# core's +HTML+ in the registry, so a PDF served under a lying
|
|
67
|
-
# +text/html+ header goes to the HTML extractor (pikuri-pdf
|
|
68
|
-
# front-inserts and wins that case). Accepted — lying-header PDFs
|
|
69
|
-
# under specifically +text/html+ are rare.
|
|
70
|
-
#
|
|
71
|
-
# == PDF: this gem or pikuri-pdf — pick one per wiring
|
|
72
|
-
#
|
|
73
|
-
# The PDF arm (pdftotext, with {#pdf_page_lines} restoring the
|
|
74
|
-
# +"--- Page N ---"+ markers from pdftotext's +\f+ separators)
|
|
75
|
-
# makes this extractor a complete superset of pikuri-pdf's
|
|
76
|
-
# formats, so a host that registers {DOCUMENTS} does NOT also
|
|
77
|
-
# register +Extractors::PDF+ — one extractor per format keeps the
|
|
78
|
-
# registry's first-match-wins semantics legible. The trade per
|
|
79
|
-
# wiring:
|
|
80
|
-
#
|
|
81
|
-
# * *This gem* — PDF parsing happens inside the sandbox (poppler
|
|
82
|
-
# is native code parsing attacker-controlled bytes; the
|
|
83
|
-
# container is exactly the right place for it), one gem covers
|
|
84
|
-
# every document format. Costs: docker (or host CLIs), no lazy
|
|
85
|
-
# paging (each paged read re-converts the whole PDF), and the
|
|
86
|
-
# generic +:document+ kind (the Read tools say "End of file",
|
|
87
|
-
# not "End of PDF", and a scanned PDF reads as "(Empty file)"
|
|
88
|
-
# rather than the scanned-image hint).
|
|
89
|
-
# * *pikuri-pdf* — in-process pure Ruby (no infrastructure), lazy
|
|
90
|
-
# +extract_lines+ paging (a windowed read of a 500-page PDF
|
|
91
|
-
# parses only its window), PDF-specific Read-tool wording.
|
|
92
|
-
# Costs: pdf-reader's dependency subtree, parsing untrusted
|
|
93
|
-
# bytes in-process (pure Ruby, so DoS at worst).
|
|
94
|
-
#
|
|
95
|
-
# The guide walks this as a progression: chapter 3 wires
|
|
96
|
-
# pikuri-pdf (no docker yet), chapter 7's assistant supersedes it
|
|
97
|
-
# with this extractor.
|
|
98
|
-
#
|
|
99
|
-
# == Deliberately out of scope
|
|
100
|
-
#
|
|
101
|
-
# * *ODS / ODP* — neither pandoc nor markitdown reads them; the
|
|
102
|
-
# only converter that does (LibreOffice headless) costs a 2 GB+
|
|
103
|
-
# image. Excluded rather than half-supported.
|
|
104
|
-
# * *Image OCR / audio transcription* — markitdown's optional
|
|
105
|
-
# arms need model downloads; the converter image stays
|
|
106
|
-
# networkless and small. A multi-modal main LLM is the pikuri
|
|
107
|
-
# answer to images.
|
|
108
|
-
#
|
|
109
|
-
# == Paging economics
|
|
110
|
-
#
|
|
111
|
-
# A subprocess converter needs the whole document before it can
|
|
112
|
-
# emit anything, so there is no lazy parse: every
|
|
113
|
-
# +Extractor.extract_paged+ call (each +Read+ page of a long DOCX)
|
|
114
|
-
# re-runs the full conversion. Accepted — no result cache in v1.
|
|
115
|
-
# Both legs of one conversion still stream, though: the source +io+
|
|
116
|
-
# is handed to {Pikuri::Subprocess.run} and copied straight into
|
|
117
|
-
# the converter's stdin (+IO.copy_stream+ — a big local file never
|
|
118
|
-
# loads into the Ruby heap), and the converter's stdout lands in a
|
|
119
|
-
# Tempfile (also what makes the stdin/stdout pumping deadlock-free
|
|
120
|
-
# — see {Pikuri::Subprocess.run}) whose lines {#extract_lines}
|
|
121
|
-
# yields from disk — so neither the document nor the full Markdown
|
|
122
|
-
# String is ever resident during paging.
|
|
28
|
+
# {#matches?} claims content by content-type ({CONTENT_TYPES}) or byte
|
|
29
|
+
# sniff (see {#sniff}). {#extract} re-sniffs (the registry duck type
|
|
30
|
+
# doesn't pass +content_type+ to +extract+); when the sniff is blind
|
|
31
|
+
# — legacy XLS, an OLE2 container whose discriminating directory sits
|
|
32
|
+
# past the sample — bytes go to markitdown with no hint and its own
|
|
33
|
+
# detection takes over. Consequence: a *local* +.xls+ (no transport
|
|
34
|
+
# content-type, sniff blind) isn't claimed at all and keeps today's
|
|
35
|
+
# binary refusal. Ordering edge: this instance sits *after* core's
|
|
36
|
+
# +HTML+, so a PDF served under a lying +text/html+ header goes to
|
|
37
|
+
# HTML (pikuri-pdf front-inserts and wins that) — accepted, rare.
|
|
123
38
|
class Documents
|
|
124
39
|
# @return [Logger] gem-wide diagnostics logger.
|
|
125
40
|
LOGGER = Pikuri.logger_for('Extractors')
|
|
@@ -145,14 +60,10 @@ module Pikuri
|
|
|
145
60
|
# claimed by content-type but the byte sniff is blind.
|
|
146
61
|
AUTO = 'auto'
|
|
147
62
|
|
|
148
|
-
# @return [String] the PDF format tag.
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# +"--- Page N ---"+ marker lines pikuri-pdf's extractor emits,
|
|
153
|
-
# so page provenance (vectordb chunk citations, the Read tools'
|
|
154
|
-
# page references) survives whichever PDF extractor a host
|
|
155
|
-
# wires.
|
|
63
|
+
# @return [String] the PDF format tag. A constant because PDF is the
|
|
64
|
+
# one format whose output gets a post-processing pass ({#pdf_page_lines}
|
|
65
|
+
# restoring +"--- Page N ---"+ markers), so page provenance survives
|
|
66
|
+
# whichever PDF extractor a host wires.
|
|
156
67
|
PDF = 'pdf'
|
|
157
68
|
|
|
158
69
|
# @return [Hash{String => String}] normalized content-type →
|
|
@@ -231,17 +142,14 @@ module Pikuri
|
|
|
231
142
|
end
|
|
232
143
|
end
|
|
233
144
|
|
|
234
|
-
# Same content as {#extract}, as a stream of +chomp+ed lines
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
# consumption, streaming +io+ into the converter. The enumerator
|
|
240
|
-
# owns the Tempfile and deletes it when iteration ends.
|
|
145
|
+
# Same content as {#extract}, as a stream of +chomp+ed lines off the
|
|
146
|
+
# converter's stdout Tempfile. The full conversion still runs up
|
|
147
|
+
# front (fired on first consumption), but neither the document nor
|
|
148
|
+
# the Markdown ever materialises as one String. The enumerator owns
|
|
149
|
+
# the Tempfile and deletes it when iteration ends.
|
|
241
150
|
#
|
|
242
|
-
# @param io [IO, StringIO] seekable IO positioned at the start;
|
|
243
|
-
#
|
|
244
|
-
# contract as pikuri-pdf's lazy +extract_lines+).
|
|
151
|
+
# @param io [IO, StringIO] seekable IO positioned at the start; must
|
|
152
|
+
# remain open until the enumerator is consumed.
|
|
245
153
|
# @return [Enumerator<String>]
|
|
246
154
|
# @raise [Pikuri::Extractor::Error] as for {#extract}, raised on
|
|
247
155
|
# first consumption.
|
|
@@ -257,9 +165,10 @@ module Pikuri
|
|
|
257
165
|
end
|
|
258
166
|
end
|
|
259
167
|
|
|
260
|
-
# Plug this extractor into {Pikuri::Extractor.registry}, before
|
|
261
|
-
#
|
|
262
|
-
#
|
|
168
|
+
# Plug this extractor into {Pikuri::Extractor.registry}, before the
|
|
169
|
+
# terminal +Passthrough+ entry but after core's +HTML+ and
|
|
170
|
+
# pikuri-pdf's front-inserted +PDF+ (both keep winning their
|
|
171
|
+
# formats). Idempotent — a second call is a no-op.
|
|
263
172
|
#
|
|
264
173
|
# @return [Documents] self, for chaining.
|
|
265
174
|
def register
|
data/lib/pikuri-extractors.rb
CHANGED
|
@@ -2,19 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
require 'pikuri-core'
|
|
4
4
|
|
|
5
|
-
# Entry file for the pikuri-extractors gem
|
|
6
|
-
#
|
|
7
|
-
# shared
|
|
8
|
-
#
|
|
9
|
-
# +Pikuri::Extractors::DOCUMENTS+ instance are defined — but *nothing
|
|
10
|
-
# is registered*: extractors plug into +Pikuri::Extractor.registry+
|
|
11
|
-
# only when the host script calls their +#register+ explicitly, so a
|
|
12
|
-
# +bin/pikuri-*+ picks which extractors it wires in (same opt-in
|
|
13
|
-
# philosophy as +c.add_extension+).
|
|
14
|
-
#
|
|
15
|
-
# The loader is per-gem (not shared with pikuri-core's loader) so each
|
|
16
|
-
# gem owns its own +lib/+ tree and the cooperation between gems is via
|
|
17
|
-
# the Pikuri namespace alone.
|
|
5
|
+
# Entry file for the pikuri-extractors gem: sets up the per-gem Zeitwerk
|
|
6
|
+
# loader. Requiring it defines {Pikuri::Extractors::Documents} + the
|
|
7
|
+
# shared {Pikuri::Extractors::DOCUMENTS} but registers nothing — a host
|
|
8
|
+
# opts in with +DOCUMENTS.register+ (see there).
|
|
18
9
|
module Pikuri
|
|
19
10
|
module Extractors
|
|
20
11
|
LOADER = Zeitwerk::Loader.new
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pikuri-extractors
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Martin Vysny
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.0
|
|
18
|
+
version: 0.1.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.0
|
|
25
|
+
version: 0.1.0
|
|
26
26
|
description: |
|
|
27
27
|
pikuri-extractors plugs additional document formats into
|
|
28
28
|
pikuri-core's +Pikuri::Extractor+ registry. The bundled
|