pi-filler 0.0.0 → 0.3.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.
package/README.md CHANGED
@@ -1,82 +1,264 @@
1
1
  # pi-filler
2
2
 
3
- <p align="center"><img src="logo.png" alt="pi-filler logo" width="220"></p>
3
+ ![pi-filler logo](logo.png)
4
4
 
5
- Deterministic DOCX and PDF tooling for Pi.
5
+ [![pipeline
6
+ status](https://gitlab.com/Joao-O-Santos/pi-filler/badges/main/pipeline.svg)](https://gitlab.com/Joao-O-Santos/pi-filler/-/commits/main)
7
+ [![npm
8
+ version](https://img.shields.io/npm/v/pi-filler.svg)](https://www.npmjs.com/package/pi-filler)
9
+ [![npm
10
+ downloads](https://img.shields.io/npm/dt/pi-filler.svg)](https://www.npmjs.com/package/pi-filler)
11
+ [![license](https://img.shields.io/npm/l/pi-filler.svg)](https://gitlab.com/Joao-O-Santos/pi-filler/-/blob/main/LICENSE)
6
12
 
7
- `pi-filler` fills document-handling gaps between ordinary Markdown
8
- workflows and final office-format requirements. It delegates common
9
- conversion and PDF extraction to mature command-line tools while keeping
10
- Word-specific mutations narrow and inspectable.
13
+ **Deterministic DOCX, PDF, PPTX, and XLSX tooling for Pi.**
11
14
 
12
- ## Status
15
+ `pi-filler` covers document tasks that do not fit an ordinary Markdown
16
+ workflow. It delegates conversion and PDF extraction to mature
17
+ command-line tools while keeping DOCX, PPTX, and XLSX mutations narrow,
18
+ transactional, and inspectable.
13
19
 
14
- Early experimental release. The tool schema and supported document
15
- operations may change before 1.0.0.
20
+ The project is experimental and pre-1.0. The README documents the
21
+ current main branch; the npm badge shows the currently published
22
+ version, which may not yet contain every feature described here.
16
23
 
17
- ## Tool
24
+ ## Install
25
+
26
+ Install the published package:
27
+
28
+ ``` sh
29
+ pi install npm:pi-filler
30
+ ```
31
+
32
+ Or install the current main branch for development and unreleased
33
+ features:
34
+
35
+ ``` sh
36
+ pi install git:https://gitlab.com/Joao-O-Santos/pi-filler.git
37
+ ```
38
+
39
+ External programs are required only for the operations listed under
40
+ [External tools](#external-tools). XLSX operations run locally through
41
+ Node.js and do not require an office suite.
42
+
43
+ ## Tool and operation matrix
18
44
 
19
45
  The extension exposes one `filler` tool:
20
46
 
21
- ```text
22
- format: docx | pdf
47
+ ``` text
48
+ format: docx | pdf | pptx | xlsx
23
49
  action: read | search | write | patch
24
- view: text | formatting | image
50
+ view: text | formatting | image | structure
25
51
  ```
26
52
 
27
- Unsupported combinations and operation-irrelevant parameters fail
28
- explicitly. DOCX patching also accepts `dry_run: true` to validate and
29
- report the requested result without writing the output file.
53
+ `format` names the document or target format, not every input: DOCX and
54
+ PPTX writes take Markdown at `path`, and XLSX writes take a workbook
55
+ template at `path` plus CSV at `source_csv`. Choose a supported
56
+ combination and supply only its relevant fields:
57
+
58
+ | Format | Action | View | Required additional fields |
59
+ |---------------|---------------|---------------|---------------------------|
60
+ | DOCX | `read` | `text` or `formatting` | none |
61
+ | DOCX | `read` | `image` | `output`; optional `first_page`, `last_page` |
62
+ | DOCX | `search` | `text` or omitted | `query`; optional `ignore_case` |
63
+ | DOCX | `write` | omitted | Markdown `path`, `output`; optional `reference_docx` |
64
+ | DOCX | `patch` | omitted | `output`, `patches`; optional `dry_run` |
65
+ | PPTX | `read` | `text` or `formatting` | none |
66
+ | PPTX | `read` | `image` | `output`; optional `first_page`, `last_page` |
67
+ | PPTX | `search` | `text` or omitted | `query`; optional `ignore_case` |
68
+ | PPTX | `write` | omitted | Markdown `path`, `output`; optional `reference_pptx` |
69
+ | PPTX | `patch` | omitted | `output`, `pptx_patches`; optional `dry_run` |
70
+ | PDF | `read` | `text` | optional `first_page`, `last_page` |
71
+ | PDF | `read` | `image` | `output`; optional `first_page`, `last_page` |
72
+ | PDF | `search` | `text` or omitted | `query`; optional `first_page`, `last_page`, `literal`, `ignore_case` |
73
+ | XLSX | `read` | `structure` | none |
74
+ | XLSX | `read` | `image` | `output`; optional `first_page`, `last_page` select resulting workbook print pages |
75
+ | XLSX | `write` | omitted | template `path`, `source_csv`, `sheet`, A1 `start_cell`, `output`; optional `column_map` |
76
+ | XLSX | `patch` | omitted | `sheet`, A1 `range`, `xlsx_patches`, `output` |
77
+
78
+ PDF write and patch, and XLSX search, are unsupported. Parameters
79
+ irrelevant to the chosen operation fail explicitly instead of being
80
+ ignored.
30
81
 
31
- ## PDF
82
+ Paths resolve relative to Pi's current working directory; absolute paths
83
+ and Pi-style paths prefixed with `@` are also accepted. A write or patch
84
+ output must differ from its input. For image reads, `output` is an
85
+ existing directory or PNG prefix; the operation creates page images
86
+ rather than one final PNG file.
32
87
 
33
- - Read text with layout preservation and optional page ranges.
34
- - Search with literal or regular-expression queries and page numbers.
35
- - Restrict searches to optional page ranges.
36
- - Render selected pages to PNG files without destroying prior output
37
- when rendering fails.
38
- - PDF writing and patching are not supported.
88
+ `first_page` and `last_page` select 1-based inclusive pages. Both must
89
+ be positive, and `first_page` cannot exceed `last_page`. Use
90
+ `start_cell` like `A2` and `range` like `A2:D10`; a range may also be
91
+ one cell.
39
92
 
40
- ## DOCX
93
+ PDF search interprets `query` as a regular expression by default; set
94
+ `literal: true` for literal text. It is case-sensitive unless
95
+ `ignore_case: true` is set. XLSX writes skip the first CSV record by
96
+ default and preserve fields as text by default. `dry_run: true` still
97
+ requires `output`, but it does not create or replace it.
41
98
 
42
- - Read and search text through Pandoc.
43
- - Render selected pages to PNG through LibreOffice and the existing PDF
44
- rendering path.
45
- - Generate DOCX from Markdown with an optional reference document.
46
- - Validate generated DOCX before replacing the requested output.
47
- - Inspect page setup, margins, sections, line numbering, page
99
+ Example calls:
100
+
101
+ ``` ts
102
+ filler({
103
+ format: "pdf",
104
+ action: "search",
105
+ path: "paper.pdf",
106
+ query: "registered report",
107
+ ignore_case: true
108
+ })
109
+
110
+ filler({
111
+ format: "xlsx",
112
+ action: "write",
113
+ path: "template.xlsx",
114
+ source_csv: "results.csv",
115
+ sheet: "Results",
116
+ start_cell: "A2",
117
+ output: "completed.xlsx",
118
+ dry_run: true
119
+ })
120
+ ```
121
+
122
+ Results may be truncated. For a truncated PDF text read, use a page
123
+ range; for PDF, DOCX, or PPTX search, narrow the query. PDF search can
124
+ also use a page range. Dry runs still read and validate all local
125
+ inputs.
126
+
127
+ ## PDF operations
128
+
129
+ - Text reads use layout-preserving extraction and optional page ranges.
130
+ - Search returns page-aware matches and supports 1-based inclusive page
131
+ ranges, case-insensitive matching, and literal rather than
132
+ regular-expression queries.
133
+ - Image reads render selected pages to numbered PNG files.
134
+ - A failed render preserves previous files for the requested output
135
+ prefix.
136
+
137
+ ## DOCX operations
138
+
139
+ - Text reads and search convert DOCX through Pandoc.
140
+ - Image reads convert DOCX through LibreOffice and then render the
141
+ resulting PDF pages.
142
+ - Writes convert a Markdown input into a validated DOCX, optionally
143
+ using a reference DOCX for styles and layout.
144
+ - Formatting reads inspect page setup, margins, sections, line and page
48
145
  numbering, comments, inserted/deleted tracked-change markers, and
49
146
  common core metadata.
50
- - Patch page setup, margins, line numbering, page numbering, and common
51
- core metadata.
52
- - Use `clearCoreMetadata` to clear common `docProps/core.xml` fields.
53
- This does **not** remove comment authors, revision authors, or every
54
- possible identity-bearing property in a DOCX.
55
-
56
- Page setup, margins, line numbering, and page numbering patches affect
57
- the first section only. Core-metadata patches are document-level.
58
- Untouched ZIP package parts remain content-identical. XML parts that are
59
- edited are parsed and serialized again, so lexical formatting, prefix
60
- choices, or element ordering inside those edited parts may change.
147
+ - Patches change first-section page setup, margins, line numbering, page
148
+ numbering, and common core metadata.
149
+
150
+ DOCX page dimensions and margins use twips (twentieths of a point); 1
151
+ inch is 1,440 twips. Page setup, margins, line numbering, and page
152
+ numbering patches apply to the first section only. Core-metadata patches
153
+ apply to the document.
154
+
155
+ `clearCoreMetadata` clears common `docProps/core.xml` fields. It does
156
+ **not** remove comment authors, revision authors, or every possible
157
+ identity-bearing property and must not be described as anonymization.
158
+
159
+ Untouched ZIP package parts remain content-identical. Edited XML parts
160
+ are parsed and serialized again, so their lexical formatting, prefixes,
161
+ or element ordering may change.
162
+
163
+ ## XLSX operations and privacy
164
+
165
+ Spreadsheet cell values, CSV fields, formula contents, and comments are
166
+ processed locally and are not returned to the model. When a
167
+ deterministic XLSX write or patch does not require inspection, do not
168
+ read CSV or cell contents into model context merely to perform it.
169
+ Structural results may contain sheet names, ranges, dimensions, counts,
170
+ merged-range addresses, hidden row and column ranges, changed package
171
+ parts, and write status---not cell or CSV contents. Layout-range arrays
172
+ are capped and report whether they were truncated; this metadata can
173
+ reveal limited layout and occupancy information.
174
+
175
+ - `read` with `view: "structure"` reports workbook and worksheet shape.
176
+ - `read` with `view: "image"` renders workbook print pages through
177
+ LibreOffice to numbered PNG files; it does not select a sheet.
178
+ `first_page` and `last_page` select resulting print pages.
179
+ - `write` fills an existing workbook template from CSV.
180
+ - `patch` changes formatting or layout while preserving cell contents.
181
+
182
+ CSV writes skip the first record by default (`has_header: true`) and
183
+ preserve fields as text by default (`value_mode: "text"`). Automatic
184
+ mode recognizes only empty values, numbers, and booleans; it does not
185
+ infer dates or formulas. `column_map` optionally maps either all 1-based
186
+ CSV ordinals or all exact CSV headers to absolute XLSX column letters,
187
+ such as `{ "1": "A", "2": "C" }`. Header mappings require unique CSV
188
+ headers; destinations must be unique. The entire write fails if its
189
+ mapped destination cells intersect an existing formula or merged range.
190
+
191
+ XLSX patches support bold, italic, font size, six-digit RGB fills,
192
+ horizontal and vertical alignment, wrapping, Excel number-format codes,
193
+ uniform borders, column width, and row height. Output is validated and
194
+ atomically replaced only after successful processing.
195
+
196
+ XLSX support does not provide cell-content reads or search, formula
197
+ calculation or generation, spreadsheet scripting, or manipulation of
198
+ charts, pivots, macros, comments, named ranges, tables, data validation,
199
+ conditional formatting, or external data.
200
+
201
+ ## PPTX operations
202
+
203
+ - Text reads and search convert PPTX through Pandoc to GFM Markdown.
204
+ - Writes convert GFM Markdown into a validated PPTX; an optional
205
+ reference PPTX supplies Pandoc presentation styles.
206
+ - Formatting reads report slide dimensions, slide count, and common core
207
+ metadata.
208
+ - Patches change slide dimensions, common core metadata, and targeted
209
+ text in slide text paragraphs. Text replacements may target one
210
+ 1-based slide or all slides. They can span formatted text runs, with
211
+ replacement text inheriting the first run's formatting.
212
+ - PPTX-to-Markdown extraction is semantic and lossy: it is not a
213
+ reversible representation of positions, animations, themes, or all
214
+ presentation objects.
215
+
216
+ Slide dimensions use EMUs (English Metric Units); 914,400 EMUs equal one
217
+ inch. PPTX image reads convert slides through LibreOffice to PDF and
218
+ render numbered PNG files; `first_page` and `last_page` select slides.
219
+
220
+ ## Output and mutation behavior
221
+
222
+ DOCX, PPTX, and XLSX writes and patches use temporary sibling files,
223
+ validate the result, and rename it into place only after success. Source
224
+ files are not modified. PDF and office image rendering similarly
225
+ protects prior output until the new render succeeds.
226
+
227
+ `dry_run` validates the requested operation and inputs without creating
228
+ or replacing output; its required `output` path is still validated.
61
229
 
62
230
  ## External tools
63
231
 
64
232
  Required for the corresponding operations:
65
233
 
66
- - `pandoc` for DOCX text conversion and generation.
67
- - `libreoffice` for DOCX image rendering.
68
- - `pdftotext` for PDF text extraction.
69
- - `pdftocairo` for PDF and DOCX page rendering.
234
+ - `pandoc` for DOCX/PPTX text conversion, search, and generation;
235
+ - `libreoffice` for DOCX, PPTX, and XLSX image rendering;
236
+ - `pdftotext` for PDF text extraction;
237
+ - `pdftocairo` for PDF, DOCX, PPTX, and XLSX page rendering; and
70
238
  - `pdfgrep` for page-aware PDF search.
71
239
 
72
- ## Development
240
+ A missing executable produces an explicit error naming the unavailable
241
+ program.
73
242
 
74
- ```sh
243
+ ## Development and release
244
+
245
+ ``` sh
75
246
  npm install
76
247
  make verify
77
248
  make site
78
249
  ```
79
250
 
251
+ `make verify` runs typechecking, lint and formatting checks,
252
+ deterministic tests, project-wide line/function coverage checks, and
253
+ `npm pack --dry-run`. `make site` regenerates the tracked static site
254
+ after documentation changes.
255
+
80
256
  Dependencies and CI actions follow current upstream releases rather than
81
- being pinned. GitLab is canonical and the only release authority.
82
- GitHub may mirror the repository, verify it, and publish GitHub Pages.
257
+ being pinned. GitLab is canonical and the only release authority. GitHub
258
+ may mirror the repository, verify it, and publish GitHub Pages. A
259
+ `vX.Y.Z` GitLab tag must match `package.json`; after verification it
260
+ publishes to npm with provenance.
261
+
262
+ ## License
263
+
264
+ [MIT](LICENSE)
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "pi-filler",
3
- "version": "0.0.0",
4
- "description": "Deterministic DOCX and PDF tooling for Pi",
3
+ "version": "0.3.0",
4
+ "description": "Deterministic DOCX, PDF, and XLSX tooling for Pi",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi-extension",
8
8
  "docx",
9
+ "pptx",
9
10
  "pdf",
11
+ "xlsx",
10
12
  "pandoc",
11
13
  "document-processing"
12
14
  ],
@@ -31,6 +33,7 @@
31
33
  ],
32
34
  "scripts": {
33
35
  "test": "node --import tsx --test test/*.test.ts",
36
+ "test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-include=src/**/*.ts --test-coverage-lines=90 --test-coverage-functions=90 test/*.test.ts",
34
37
  "typecheck": "tsc --noEmit",
35
38
  "lint": "biome lint src test scripts",
36
39
  "format:check": "biome ci --linter-enabled=false --assist-enabled=false src test scripts biome.json package.json tsconfig.json",
@@ -38,7 +41,7 @@
38
41
  "markdown:check": "node scripts/format-markdown.mjs",
39
42
  "markdown:fix": "node scripts/format-markdown.mjs --write",
40
43
  "style": "npm run lint && npm run format:check && npm run markdown:check",
41
- "check": "npm run typecheck && npm run style && npm test",
44
+ "check": "npm run typecheck && npm run style && npm test && npm run test:coverage",
42
45
  "site": "make site"
43
46
  },
44
47
  "pi": {
@@ -46,6 +49,11 @@
46
49
  "./src/index.ts"
47
50
  ]
48
51
  },
52
+ "allowScripts": {
53
+ "@google/genai": false,
54
+ "esbuild": true,
55
+ "protobufjs": false
56
+ },
49
57
  "peerDependencies": {
50
58
  "@earendil-works/pi-ai": "*",
51
59
  "@earendil-works/pi-coding-agent": "*",
@@ -63,6 +71,7 @@
63
71
  "typescript": "latest"
64
72
  },
65
73
  "dependencies": {
74
+ "csv-parse": "latest",
66
75
  "fast-xml-parser": "latest",
67
76
  "fflate": "latest"
68
77
  }
package/src/docx.ts CHANGED
@@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, join, posix, resolve } from "node:path";
4
4
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
5
- import { unzipSync, zipSync } from "fflate";
5
+ import { type OfficePackage, packOfficePackage, unpackOfficePackage } from "./ooxml.js";
6
6
 
7
7
  export interface DocxPatchSet {
8
8
  page?: { width?: number; height?: number; orientation?: "portrait" | "landscape" };
@@ -50,7 +50,7 @@ export interface DocxPatchResult {
50
50
  written: boolean;
51
51
  }
52
52
 
53
- type Package = Record<string, Uint8Array>;
53
+ type Package = OfficePackage;
54
54
  type XmlObject = Record<string, unknown>;
55
55
 
56
56
  const WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
@@ -86,11 +86,7 @@ function serializeXml(value: XmlObject): Uint8Array {
86
86
  }
87
87
 
88
88
  function unpack(bytes: Uint8Array): Package {
89
- try {
90
- return unzipSync(bytes);
91
- } catch (error) {
92
- throw new Error(`Invalid DOCX ZIP package: ${error instanceof Error ? error.message : error}`);
93
- }
89
+ return unpackOfficePackage(bytes, "DOCX");
94
90
  }
95
91
 
96
92
  function findAll(value: unknown, suffix: string, found: XmlObject[] = []): XmlObject[] {
@@ -507,7 +503,7 @@ export async function patchDocx(
507
503
  const temporary = join(dirname(output), `.${output.split(/[\\/]/).pop()}.${randomUUID()}.tmp`);
508
504
  await mkdir(dirname(output), { recursive: true });
509
505
  try {
510
- await writeFile(temporary, zipSync(pkg));
506
+ await writeFile(temporary, packOfficePackage(pkg));
511
507
  await inspectDocx(temporary);
512
508
  await rename(temporary, output);
513
509
  result.written = true;