pi-filler 0.0.0 → 0.2.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,235 @@
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, 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 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 | xlsx
23
49
  action: read | search | write | patch
24
- view: text | formatting | image
50
+ view: text | formatting | image | structure
51
+ ```
52
+
53
+ `format` names the document or target format, not every input: DOCX
54
+ writes take Markdown at `path`, and XLSX writes take a workbook template
55
+ at `path` plus CSV at `source_csv`. Choose a supported combination and
56
+ 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
+ | PDF | `read` | `text` | optional `first_page`, `last_page` |
66
+ | PDF | `read` | `image` | `output`; optional `first_page`, `last_page` |
67
+ | PDF | `search` | `text` or omitted | `query`; optional `first_page`, `last_page`, `literal`, `ignore_case` |
68
+ | XLSX | `read` | `structure` | none |
69
+ | XLSX | `read` | `image` | `output`; optional `first_page`, `last_page`; all workbook print pages |
70
+ | XLSX | `write` | omitted | template `path`, `source_csv`, `sheet`, A1 `start_cell`, `output`; optional `column_map` |
71
+ | XLSX | `patch` | omitted | `sheet`, A1 `range`, `xlsx_patches`, `output` |
72
+
73
+ PDF write and patch, and XLSX search, are unsupported. Parameters
74
+ irrelevant to the chosen operation fail explicitly instead of being
75
+ ignored.
76
+
77
+ Paths resolve relative to Pi's current working directory; absolute paths
78
+ and Pi-style paths prefixed with `@` are also accepted. A write or patch
79
+ output must differ from its input. For image reads, `output` is an
80
+ existing directory or PNG prefix; the operation creates page images
81
+ rather than one final PNG file.
82
+
83
+ `first_page` and `last_page` select 1-based inclusive pages. Both must
84
+ be positive, and `first_page` cannot exceed `last_page`. Use
85
+ `start_cell` like `A2` and `range` like `A2:D10`; a range may also be
86
+ one cell.
87
+
88
+ PDF search interprets `query` as a regular expression by default; set
89
+ `literal: true` for literal text. It is case-sensitive unless
90
+ `ignore_case: true` is set. XLSX writes skip the first CSV record by
91
+ default and preserve fields as text by default. `dry_run: true` still
92
+ requires `output`, but it does not create or replace it.
93
+
94
+ Example calls:
95
+
96
+ ``` ts
97
+ filler({
98
+ format: "pdf",
99
+ action: "search",
100
+ path: "paper.pdf",
101
+ query: "registered report",
102
+ ignore_case: true
103
+ })
104
+
105
+ filler({
106
+ format: "xlsx",
107
+ action: "write",
108
+ path: "template.xlsx",
109
+ source_csv: "results.csv",
110
+ sheet: "Results",
111
+ start_cell: "A2",
112
+ output: "completed.xlsx",
113
+ dry_run: true
114
+ })
25
115
  ```
26
116
 
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.
117
+ Results may be truncated. For a truncated PDF text read, use a page
118
+ range; for PDF or DOCX search, narrow the query. PDF search can also use
119
+ a page range. Dry runs still read and validate all local inputs.
30
120
 
31
- ## PDF
121
+ ## PDF operations
32
122
 
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.
123
+ - Text reads use layout-preserving extraction and optional page ranges.
124
+ - Search returns page-aware matches and supports 1-based inclusive page
125
+ ranges, case-insensitive matching, and literal rather than
126
+ regular-expression queries.
127
+ - Image reads render selected pages to numbered PNG files.
128
+ - A failed render preserves previous files for the requested output
129
+ prefix.
39
130
 
40
- ## DOCX
131
+ ## DOCX operations
41
132
 
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
133
+ - Text reads and search convert DOCX through Pandoc.
134
+ - Image reads convert DOCX through LibreOffice and then render the
135
+ resulting PDF pages.
136
+ - Writes convert a Markdown input into a validated DOCX, optionally
137
+ using a reference DOCX for styles and layout.
138
+ - Formatting reads inspect page setup, margins, sections, line and page
48
139
  numbering, comments, inserted/deleted tracked-change markers, and
49
140
  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.
141
+ - Patches change first-section page setup, margins, line numbering, page
142
+ numbering, and common core metadata.
143
+
144
+ DOCX page dimensions and margins use twips (twentieths of a point); 1
145
+ inch is 1,440 twips. Page setup, margins, line numbering, and page
146
+ numbering patches apply to the first section only. Core-metadata patches
147
+ apply to the document.
148
+
149
+ `clearCoreMetadata` clears common `docProps/core.xml` fields. It does
150
+ **not** remove comment authors, revision authors, or every possible
151
+ identity-bearing property and must not be described as anonymization.
152
+
153
+ Untouched ZIP package parts remain content-identical. Edited XML parts
154
+ are parsed and serialized again, so their lexical formatting, prefixes,
155
+ or element ordering may change.
156
+
157
+ ## XLSX operations and privacy
158
+
159
+ Spreadsheet cell values, CSV fields, formulas, comments, and other
160
+ workbook text are processed locally and are not returned to the model.
161
+ Structural results contain sheet names, ranges, dimensions, counts,
162
+ merged-range addresses, hidden row and column ranges, changed package
163
+ parts, and write status---not workbook or CSV contents. Layout-range
164
+ arrays are capped and report whether they were truncated; this metadata
165
+ can reveal limited layout and occupancy information.
166
+
167
+ - `read` with `view: "structure"` reports workbook and worksheet shape.
168
+ - `read` with `view: "image"` renders all workbook print pages through
169
+ LibreOffice to numbered PNG files; it does not select a sheet.
170
+ - `write` fills an existing workbook template from CSV.
171
+ - `patch` changes formatting or layout while preserving cell contents.
172
+
173
+ CSV writes skip the first record by default (`has_header: true`) and
174
+ preserve fields as text by default (`value_mode: "text"`). Automatic
175
+ mode recognizes only empty values, numbers, and booleans; it does not
176
+ infer dates or formulas. `column_map` optionally maps either all 1-based
177
+ CSV ordinals or all exact CSV headers to absolute XLSX column letters,
178
+ such as `{ "1": "A", "2": "C" }`. Header mappings require unique CSV
179
+ headers; destinations must be unique. The entire write fails if its
180
+ mapped destination cells intersect an existing formula or merged range.
181
+
182
+ XLSX patches support bold, italic, font size, six-digit RGB fills,
183
+ horizontal and vertical alignment, wrapping, Excel number-format codes,
184
+ uniform borders, column width, and row height. Output is validated and
185
+ atomically replaced only after successful processing.
186
+
187
+ XLSX support does not provide cell-content reads or search, formula
188
+ calculation or generation, spreadsheet scripting, or manipulation of
189
+ charts, pivots, macros, comments, named ranges, tables, data validation,
190
+ conditional formatting, or external data.
191
+
192
+ ## Output and mutation behavior
193
+
194
+ DOCX and XLSX writes and patches use temporary sibling files, validate
195
+ the result, and rename it into place only after success. Source files
196
+ are not modified. PDF image rendering similarly protects prior output
197
+ until the new render succeeds.
198
+
199
+ `dry_run` validates the requested operation and inputs without creating
200
+ or replacing output; its required `output` path is still validated.
61
201
 
62
202
  ## External tools
63
203
 
64
204
  Required for the corresponding operations:
65
205
 
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.
206
+ - `pandoc` for DOCX text conversion, search, and generation;
207
+ - `libreoffice` for DOCX and XLSX image rendering;
208
+ - `pdftotext` for PDF text extraction;
209
+ - `pdftocairo` for PDF, DOCX, and XLSX page rendering; and
70
210
  - `pdfgrep` for page-aware PDF search.
71
211
 
72
- ## Development
212
+ A missing executable produces an explicit error naming the unavailable
213
+ program.
214
+
215
+ ## Development and release
73
216
 
74
- ```sh
217
+ ``` sh
75
218
  npm install
76
219
  make verify
77
220
  make site
78
221
  ```
79
222
 
223
+ `make verify` runs typechecking, lint and formatting checks,
224
+ deterministic tests, and `npm pack --dry-run`. `make site` regenerates
225
+ the tracked static site after documentation changes.
226
+
80
227
  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.
228
+ being pinned. GitLab is canonical and the only release authority. GitHub
229
+ may mirror the repository, verify it, and publish GitHub Pages. A
230
+ `vX.Y.Z` GitLab tag must match `package.json`; after verification it
231
+ publishes to npm with provenance.
232
+
233
+ ## License
234
+
235
+ [MIT](LICENSE)
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "pi-filler",
3
- "version": "0.0.0",
4
- "description": "Deterministic DOCX and PDF tooling for Pi",
3
+ "version": "0.2.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
9
  "pdf",
10
+ "xlsx",
10
11
  "pandoc",
11
12
  "document-processing"
12
13
  ],
@@ -46,6 +47,11 @@
46
47
  "./src/index.ts"
47
48
  ]
48
49
  },
50
+ "allowScripts": {
51
+ "@google/genai": false,
52
+ "esbuild": true,
53
+ "protobufjs": false
54
+ },
49
55
  "peerDependencies": {
50
56
  "@earendil-works/pi-ai": "*",
51
57
  "@earendil-works/pi-coding-agent": "*",
@@ -63,6 +69,7 @@
63
69
  "typescript": "latest"
64
70
  },
65
71
  "dependencies": {
72
+ "csv-parse": "latest",
66
73
  "fast-xml-parser": "latest",
67
74
  "fflate": "latest"
68
75
  }
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;