templify-cli 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {templify_cli-0.2.0 → templify_cli-0.3.0}/PKG-INFO +155 -142
- {templify_cli-0.2.0 → templify_cli-0.3.0}/README.md +153 -140
- {templify_cli-0.2.0 → templify_cli-0.3.0}/pyproject.toml +113 -3
- {templify_cli-0.2.0 → templify_cli-0.3.0}/pyproject.toml.orig +44 -4
- templify_cli-0.3.0/src/templify/cli/__main__.py +4 -0
- templify_cli-0.3.0/src/templify/cli/cli.py +172 -0
- templify_cli-0.3.0/src/templify/cli/dependencies/data.py +304 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/cli/dependencies/template.py +87 -96
- templify_cli-0.3.0/src/templify/cli/help.py +32 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/filter_rules.py +1 -1
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/readers.py +47 -23
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/writers.py +314 -312
- templify_cli-0.2.0/src/templify/cli/dependencies/data.py +0 -202
- templify_cli-0.2.0/src/templify/cli/help.py +0 -61
- templify_cli-0.2.0/src/templify/cli/main.py +0 -230
- {templify_cli-0.2.0 → templify_cli-0.3.0}/LICENSE +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/__init__.py +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/cli/__init__.py +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/cli/dependencies/__init__.py +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/cli/dependencies/console.py +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/models.py +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/py.typed +0 -0
- {templify_cli-0.2.0 → templify_cli-0.3.0}/src/templify/transformers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: templify-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Fill Word, text, Markdown, HTML and JSON templates with Excel or JSON data, from the command line.
|
|
5
5
|
Keywords: template,templating,jinja2,docx,word,excel,mail-merge,document-generation,cli
|
|
6
6
|
Author: aaltatan
|
|
@@ -25,7 +25,7 @@ Requires-Dist: jinja2>=3.1
|
|
|
25
25
|
Requires-Dist: openpyxl>=3.1.5
|
|
26
26
|
Requires-Dist: pyspecification>=4.0.0
|
|
27
27
|
Requires-Dist: rich>=13.0
|
|
28
|
-
Requires-Dist: schemify-cli
|
|
28
|
+
Requires-Dist: schemify-cli>=0.1.0
|
|
29
29
|
Requires-Dist: typer>=0.27.2
|
|
30
30
|
Requires-Dist: typer-di>=0.1.5
|
|
31
31
|
Requires-Dist: typer-swissknife>=0.1.0
|
|
@@ -40,10 +40,10 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
A command line tool that fills document templates with data.
|
|
42
42
|
|
|
43
|
-
Point it at a **template** (Word, text, Markdown, HTML or JSON) and a **data
|
|
43
|
+
Point it at a **template** (Word, text, Markdown, HTML or JSON) and a **data source**: an Excel or JSON file, or a JSON Schema that templify asks you to fill. templify renders one file per row, one file with every row in it, or one file from a single object. Before rendering, you can **order**, **filter** and **group** the data.
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
templify
|
|
46
|
+
templify render --each -t offer.docx -d employees.xlsx -o offers -n name
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
```text
|
|
@@ -63,10 +63,9 @@ offers/
|
|
|
63
63
|
- [Quick start](#quick-start)
|
|
64
64
|
- [How it works](#how-it-works)
|
|
65
65
|
- [Commands](#commands)
|
|
66
|
-
- [`
|
|
67
|
-
- [`
|
|
68
|
-
- [
|
|
69
|
-
- [`filters-json-schema` (`fjs`)](#filters-json-schema-fjs)
|
|
66
|
+
- [`render` (`r`)](#render-r)
|
|
67
|
+
- [`filters` (`f`)](#filters-f)
|
|
68
|
+
- [Upgrading from 0.2](#upgrading-from-02)
|
|
70
69
|
- [Data sources](#data-sources)
|
|
71
70
|
- [Templates](#templates)
|
|
72
71
|
- [Supported formats](#supported-formats)
|
|
@@ -91,8 +90,8 @@ offers/
|
|
|
91
90
|
## Features
|
|
92
91
|
|
|
93
92
|
- **Many template formats**: `.docx`, `.txt`, `.md`, `.html`, `.htm` and `.json`. The writer is picked automatically from the template's extension.
|
|
94
|
-
- **Many data sources**: Excel (`.xlsx`)
|
|
95
|
-
- **
|
|
93
|
+
- **Many data sources**: Excel (`.xlsx`), JSON (`.json`), or answers asked from a JSON Schema (`-s`).
|
|
94
|
+
- **One command, three modes**: every row in one file (`render`), one file per row (`render --each`), or one object in one file (`render --object`).
|
|
96
95
|
- **Ordering** by any number of columns, ascending or descending.
|
|
97
96
|
- **Filtering** with a JSON filter language: 50+ rules for strings, numbers, decimals, dates, datetimes and UUIDs. Rules combine with `all` / `any`, nest, and can be inverted.
|
|
98
97
|
- **Grouping** by one or more columns, e.g. one file per department listing its employees.
|
|
@@ -148,7 +147,7 @@ You joined {{ department }} on {{ hired.strftime("%d/%m/%Y") }}.
|
|
|
148
147
|
**3. Run templify:**
|
|
149
148
|
|
|
150
149
|
```bash
|
|
151
|
-
templify
|
|
150
|
+
templify render --each -t welcome.txt -d employees.xlsx -o welcome -n name
|
|
152
151
|
```
|
|
153
152
|
|
|
154
153
|
`welcome/Rama.txt`:
|
|
@@ -162,21 +161,23 @@ You joined IT on 10/02/2024.
|
|
|
162
161
|
|
|
163
162
|
## How it works
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
`render` runs one pipeline:
|
|
166
165
|
|
|
167
166
|
```text
|
|
168
167
|
read data ──► order ──► filter ──► group ──► render template ──► save file(s)
|
|
169
|
-
|
|
168
|
+
(-d / -s) (--order) (--filter) (--group-by) (-t) (-o)
|
|
170
169
|
```
|
|
171
170
|
|
|
172
|
-
1. **Read**: the data
|
|
171
|
+
1. **Read**: the [data source](#data-sources) becomes a list of rows, each row a set of `column: value` pairs.
|
|
173
172
|
2. **Order**: rows are sorted by `--order` columns (optional).
|
|
174
173
|
3. **Filter**: rows not matching `--filter` are dropped (optional).
|
|
175
174
|
4. **Group**: rows are grouped by `--group-by` columns (optional).
|
|
176
175
|
5. **Render**: the writer chosen from the template extension renders the rows.
|
|
177
176
|
6. **Save**:
|
|
178
|
-
-
|
|
179
|
-
-
|
|
177
|
+
- by default, one file containing every row.
|
|
178
|
+
- with `--each`, one file per row, or per group when grouping.
|
|
179
|
+
|
|
180
|
+
With `--object`, the data is a **single object** instead of a list, so steps 2 to 4 don't apply and that object is rendered into one file.
|
|
180
181
|
|
|
181
182
|
## Commands
|
|
182
183
|
|
|
@@ -184,83 +185,62 @@ Every command runs the same pipeline:
|
|
|
184
185
|
templify --help
|
|
185
186
|
```
|
|
186
187
|
|
|
187
|
-
| Command
|
|
188
|
-
|
|
|
189
|
-
| `
|
|
190
|
-
| `
|
|
191
|
-
| `schema-template` | `schema`, `js` | Ask for one object from a JSON Schema, render **one file** |
|
|
192
|
-
| `schema-list-template`| `schema-list`, `jsl` | Ask for a list from a JSON Schema, render **one file** |
|
|
193
|
-
| `filters-json-schema` | `filters-schema`, `fjs` | List the filter rules and their arguments |
|
|
194
|
-
|
|
195
|
-
### `multiple-templates` (`m`)
|
|
188
|
+
| Command | Alias | What it does |
|
|
189
|
+
| --------- | ----- | --------------------------------------------------- |
|
|
190
|
+
| `render` | `r` | Render a template with data, in one of three modes |
|
|
191
|
+
| `filters` | `f` | List the filter rules and their arguments |
|
|
196
192
|
|
|
197
|
-
|
|
193
|
+
### `render` (`r`)
|
|
198
194
|
|
|
199
195
|
```bash
|
|
200
|
-
templify
|
|
196
|
+
templify render -t <template> (-d <data> | -s <schema.json>) [--each | --object] [-o <output-dir>] [OPTIONS]
|
|
201
197
|
```
|
|
202
198
|
|
|
203
|
-
|
|
204
|
-
| ------------------ | ----- | -------------------------------------------------------------------- | ---------------- |
|
|
205
|
-
| `--template` | `-t` | Template file. **Required.** | |
|
|
206
|
-
| `--data` | `-d` | Data file. **Required.** | |
|
|
207
|
-
| `--output` | `-o` | Output directory, created if missing. **Required.** | |
|
|
208
|
-
| `--filename-key` | `-k` | Column whose value names each file | `template-<n>` |
|
|
209
|
-
| `--index` | `-i` | Prefix each filename with its row number: `1 - Rama.docx` | off |
|
|
210
|
-
| `--order` | | Column to order by, `-column` for descending. Repeatable | |
|
|
211
|
-
| `--filter` | | JSON [filter schema](#filter-schema) | |
|
|
212
|
-
| `--group-by` | | Column to [group](#grouping) by. Repeatable | |
|
|
213
|
-
| `--group-items` | | Variable name holding each group's rows | `items` |
|
|
199
|
+
The mode decides what the template sees and how many files are written:
|
|
214
200
|
|
|
215
|
-
|
|
201
|
+
| Mode | Data | Writes | The template sees |
|
|
202
|
+
| ----------- | ------------------- | ------------------------------- | ------------------------------------------------------- |
|
|
203
|
+
| *(default)* | a list of rows | **one file** with all the rows | the rows as a list, `objects`: `{% for e in objects %}` |
|
|
204
|
+
| `--each` | a list of rows | **one file per row** (or group) | each row's columns: `{{ name }}`, `{{ salary }}` |
|
|
205
|
+
| `--object` | a **single object** | **one file** | the object as `object`: `{{ object.name }}` |
|
|
216
206
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
207
|
+
| Option | Short | Modes | Description | Default |
|
|
208
|
+
| ------------- | ----- | ------------------- | --------------------------------------------------------------- | -------------------- |
|
|
209
|
+
| `--template` | `-t` | all | Template file. **Required.** | |
|
|
210
|
+
| `--data` | `-d` | all | Data file. Exactly one of `-d` / `-s` is **required** | |
|
|
211
|
+
| `--schema` | `-s` | all | JSON Schema file, [ask for the data](#data-sources) matching it | |
|
|
212
|
+
| `--output` | `-o` | all | Output directory, created if missing | current directory |
|
|
213
|
+
| `--each` | | | One file per row (or per group) | off |
|
|
214
|
+
| `--object` | | | The data is a single object | off |
|
|
215
|
+
| `--filename` | `-f` | default, `--object` | Output filename **without** extension | the template's name |
|
|
216
|
+
| `--var` | `-v` | default, `--object` | Name of the variable holding the data | `objects` / `object` |
|
|
217
|
+
| `--name-by` | `-n` | `--each` | Column whose value names each file | `template-<n>` |
|
|
218
|
+
| `--numbered` | | `--each` | Prefix each filename with its row number: `1 - Rama.docx` | off |
|
|
219
|
+
| `--order` | | default, `--each` | Column to [order](#ordering) by, `-column` for descending. Repeatable | |
|
|
220
|
+
| `--filter` | | default, `--each` | JSON [filter schema](#filter-schema) | |
|
|
221
|
+
| `--group-by` | | default, `--each` | Column to [group](#grouping) by. Repeatable | |
|
|
222
|
+
| `--items-var` | | default, `--each` | Variable name holding each group's rows | `items` |
|
|
220
223
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
├── 1 - Abdullah.docx
|
|
224
|
-
├── 2 - Rawaa.docx
|
|
225
|
-
├── 3 - Rama.docx
|
|
226
|
-
└── 4 - Omar.docx
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### `single-template` (`s`)
|
|
230
|
-
|
|
231
|
-
Renders the template **once**, with every row in a list variable (`data` by default). The template loops over it.
|
|
224
|
+
An option used in a mode that doesn't take it is an error, never silently ignored:
|
|
225
|
+
`--name-by only works with --each`.
|
|
232
226
|
|
|
233
|
-
|
|
234
|
-
templify s -t <template> -d <data> -o <output-dir> [OPTIONS]
|
|
235
|
-
```
|
|
227
|
+
#### All rows in one file (default)
|
|
236
228
|
|
|
237
|
-
|
|
238
|
-
| ----------------- | ----- | ---------------------------------------------------------- | ---------------------- |
|
|
239
|
-
| `--template` | `-t` | Template file. **Required.** | |
|
|
240
|
-
| `--data` | `-d` | Data file. **Required.** | |
|
|
241
|
-
| `--output` | `-o` | Output directory, created if missing. **Required.** | |
|
|
242
|
-
| `--filename` | `-f` | Output filename **without** extension | the template's name |
|
|
243
|
-
| `--data-variable` | `-v` | Name of the list variable inside the template | `data` |
|
|
244
|
-
| `--order` | | Column to order by, `-column` for descending. Repeatable | |
|
|
245
|
-
| `--filter` | | JSON [filter schema](#filter-schema) | |
|
|
246
|
-
| `--group-by` | | Column to [group](#grouping) by. Repeatable | |
|
|
247
|
-
| `--group-items` | | Variable name holding each group's rows | `items` |
|
|
248
|
-
|
|
249
|
-
Example with `team.md`:
|
|
229
|
+
`team.md`:
|
|
250
230
|
|
|
251
231
|
```jinja
|
|
252
232
|
# Team report
|
|
253
233
|
|
|
254
234
|
| # | Name | Department | Salary |
|
|
255
235
|
|---|------|------------|-------:|
|
|
256
|
-
{% for e in
|
|
236
|
+
{% for e in objects -%}
|
|
257
237
|
| {{ loop.index }} | {{ e.name }} | {{ e.department }} | {{ "{:,}".format(e.salary) }} |
|
|
258
238
|
{% endfor %}
|
|
259
|
-
**Total salaries:** {{
|
|
239
|
+
**Total salaries:** {{ objects | sum(attribute="salary") }}
|
|
260
240
|
```
|
|
261
241
|
|
|
262
242
|
```bash
|
|
263
|
-
templify
|
|
243
|
+
templify render -t team.md -d employees.xlsx -o reports --order -salary
|
|
264
244
|
```
|
|
265
245
|
|
|
266
246
|
`reports/team.md`:
|
|
@@ -281,44 +261,44 @@ templify s -t team.md -d employees.xlsx -o reports --order -salary
|
|
|
281
261
|
Rename the output and the loop variable:
|
|
282
262
|
|
|
283
263
|
```bash
|
|
284
|
-
templify
|
|
264
|
+
templify render -t team.md -d employees.xlsx -o reports -f "2024 team" -v employees
|
|
285
265
|
```
|
|
286
266
|
|
|
287
267
|
The template then loops with `{% for e in employees %}`, and the file is saved as `reports/2024 team.md`.
|
|
288
268
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
No data file: [schemify](https://github.com/aaltatan/schemify-cli) asks for the data matching a
|
|
292
|
-
JSON Schema file, then it is rendered into **one file**. `schema-template` asks for one object,
|
|
293
|
-
`schema-list-template` for a list of them.
|
|
294
|
-
|
|
295
|
-
| Option | Short | Description | Default |
|
|
296
|
-
| ----------------- | ----- | --------------------------------------------------- | ------------------- |
|
|
297
|
-
| `--json-schema` | `--js`| JSON Schema file. **Required.** | |
|
|
298
|
-
| `--template` | `-t` | Template file. **Required.** | |
|
|
299
|
-
| `--output` | `-o` | Output directory, created if missing. **Required.** | |
|
|
300
|
-
| `--filename` | `-f` | Output filename **without** extension | the template's name |
|
|
301
|
-
| `--data-variable` | `-v` | Name of the variable holding the data | `data` |
|
|
269
|
+
#### One file per row (`--each`)
|
|
302
270
|
|
|
303
271
|
```bash
|
|
304
|
-
templify
|
|
305
|
-
templify jsl --js person.schema.json -t team.md -o out # {% for p in data %}...{% endfor %}
|
|
272
|
+
templify render -t offer.docx -d employees.xlsx -o offers --each -n name --numbered
|
|
306
273
|
```
|
|
307
274
|
|
|
308
|
-
|
|
275
|
+
```text
|
|
276
|
+
offers/
|
|
277
|
+
├── 1 - Abdullah.docx
|
|
278
|
+
├── 2 - Rawaa.docx
|
|
279
|
+
├── 3 - Rama.docx
|
|
280
|
+
└── 4 - Omar.docx
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+

|
|
309
284
|
|
|
310
|
-
|
|
285
|
+
#### One object (`--object`)
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
templify render -t letter.docx -d rama.json --object # Hello {{ object.name }}
|
|
289
|
+
templify render -t letter.docx -s person.schema.json --object # asks for the fields first
|
|
290
|
+
```
|
|
311
291
|
|
|
312
|
-
|
|
292
|
+
With `-s`, [schemify](https://github.com/aaltatan/schemify-cli) asks for the fields:
|
|
313
293
|
|
|
314
|
-

|
|
315
295
|
|
|
316
|
-
### `filters
|
|
296
|
+
### `filters` (`f`)
|
|
317
297
|
|
|
318
298
|
Lists every filter rule you can use in `--filter`, with its arguments (in `args` order) and description:
|
|
319
299
|
|
|
320
300
|
```bash
|
|
321
|
-
templify
|
|
301
|
+
templify filters
|
|
322
302
|
```
|
|
323
303
|
|
|
324
304
|
```text
|
|
@@ -336,15 +316,44 @@ templify fjs
|
|
|
336
316
|
|
|
337
317
|
The argument names are also the `kwargs` names: `{"key": "salary", "value": 1400}`.
|
|
338
318
|
|
|
319
|
+
### Upgrading from 0.2
|
|
320
|
+
|
|
321
|
+
| 0.2 | 0.3 |
|
|
322
|
+
| ---------------------------------------- | ----------------------------------------- |
|
|
323
|
+
| `templify single-template` / `s` | `templify render` |
|
|
324
|
+
| `templify multiple-templates` / `m` | `templify render --each` |
|
|
325
|
+
| `templify schema-template` / `js` | `templify render --object -s ...` |
|
|
326
|
+
| `templify schema-list-template` / `jsl` | `templify render -s ...` |
|
|
327
|
+
| `templify filters-json-schema` / `fjs` | `templify filters` |
|
|
328
|
+
| `-k` / `--filename-key` | `-n` / `--name-by` |
|
|
329
|
+
| `-i` / `--index` | `--numbered` |
|
|
330
|
+
| `--group-items` | `--items-var` |
|
|
331
|
+
| `-v` / `--data-variable`, default `data` | `-v` / `--var`, default `objects` |
|
|
332
|
+
| `-o` required | `-o` optional, default current directory |
|
|
333
|
+
| Template errors exit with code 2 | exit with code 1 (usage errors stay 2) |
|
|
334
|
+
|
|
339
335
|
## Data sources
|
|
340
336
|
|
|
341
|
-
|
|
337
|
+
`render` takes **exactly one** data source:
|
|
338
|
+
|
|
339
|
+
- `-d` / `--data`: a data file. The reader is chosen from its extension, ignoring case.
|
|
340
|
+
- `-s` / `--schema`: a JSON Schema file. [schemify](https://github.com/aaltatan/schemify-cli) asks for the data matching it: a list (keep answering until you say **No**), or one object with `--object`.
|
|
341
|
+
|
|
342
|
+

|
|
343
|
+
|
|
344
|
+
Data files for the default mode and `--each` hold a **list of objects**:
|
|
342
345
|
|
|
343
346
|
| Extension | Format |
|
|
344
347
|
| --------- | --------------------------------------------------------------------------- |
|
|
345
348
|
| `.xlsx` | Excel workbook: the **active sheet**, first row = column names |
|
|
346
349
|
| `.json` | A JSON **list of objects**: `[{"name": "Rama", ...}, ...]` |
|
|
347
350
|
|
|
351
|
+
Data files for `--object` hold a **single object**:
|
|
352
|
+
|
|
353
|
+
| Extension | Format |
|
|
354
|
+
| --------- | --------------------------------------------------------------------------- |
|
|
355
|
+
| `.json` | A JSON **object**: `{"name": "Rama", ...}` |
|
|
356
|
+
|
|
348
357
|
**Excel notes**
|
|
349
358
|
|
|
350
359
|
- The first row holds the column names, and each following row is one record.
|
|
@@ -395,7 +404,7 @@ All templates use [Jinja2](https://jinja.palletsprojects.com/en/stable/templates
|
|
|
395
404
|
| ------------------------------------------ | ---------------------------------------- |
|
|
396
405
|
| `{{ name }}` | Print a variable |
|
|
397
406
|
| `{{ e.name }}` or `{{ e["name"] }}` | Print a field of a row |
|
|
398
|
-
| `{% for e in
|
|
407
|
+
| `{% for e in objects %} ... {% endfor %}` | Loop |
|
|
399
408
|
| `{{ loop.index }}` | 1-based loop counter |
|
|
400
409
|
| `{% if salary > 1400 %} ... {% endif %}` | Condition |
|
|
401
410
|
| `{{ name \| upper }}` | Filter: `upper`, `lower`, `title`, `length`, `sum`, `default`, `tojson`, ... |
|
|
@@ -435,24 +444,24 @@ docxtpl adds special tags for Word structure:
|
|
|
435
444
|
|
|
436
445
|
| Tag | Use |
|
|
437
446
|
| ----------------------------------- | ------------------------------------------------------- |
|
|
438
|
-
| `{%p for e in
|
|
439
|
-
| `{%tr for e in
|
|
447
|
+
| `{%p for e in objects %}` ... `{%p endfor %}` | Loop over **paragraphs**: each tag sits on its own paragraph |
|
|
448
|
+
| `{%tr for e in objects %}` ... `{%tr endfor %}` | Loop over **table rows**: one row per record |
|
|
440
449
|
| `{%tc ... %}` | Loop over table cells |
|
|
441
450
|
| `{%r ... %}` | Tag applied to a run |
|
|
442
451
|
|
|
443
452
|
**One Word file, one page per employee** (`all-offers.docx`, three paragraphs):
|
|
444
453
|
|
|
445
454
|
```text
|
|
446
|
-
{%p for e in
|
|
455
|
+
{%p for e in objects %}
|
|
447
456
|
{{ e.name }} - {{ e.department }}{{ page_break }}
|
|
448
457
|
{%p endfor %}
|
|
449
458
|
```
|
|
450
459
|
|
|
451
460
|
```bash
|
|
452
|
-
templify
|
|
461
|
+
templify render -t all-offers.docx -d employees.xlsx -o offers
|
|
453
462
|
```
|
|
454
463
|
|
|
455
|
-
**A Word table with one row per employee**: create a 2-row table. Put `{%tr for e in
|
|
464
|
+
**A Word table with one row per employee**: create a 2-row table. Put `{%tr for e in objects %}` and `{%tr endfor %}` in rows of their own, around a row containing `{{ e.name }}`, `{{ e.salary }}`, ...
|
|
456
465
|
|
|
457
466
|
See the [docxtpl documentation](https://docxtpl.readthedocs.io/) for images, rich text and more.
|
|
458
467
|
|
|
@@ -474,7 +483,7 @@ Always write values through the `tojson` filter. It adds quotes and escapes char
|
|
|
474
483
|
```
|
|
475
484
|
|
|
476
485
|
```bash
|
|
477
|
-
templify
|
|
486
|
+
templify render --each -t profile.json -d employees.json -o profiles -n name
|
|
478
487
|
```
|
|
479
488
|
|
|
480
489
|
`profiles/Omar.json`:
|
|
@@ -491,11 +500,11 @@ templify m -t profile.json -d employees.json -o profiles -k name
|
|
|
491
500
|
Dump the whole (ordered / filtered / grouped) dataset:
|
|
492
501
|
|
|
493
502
|
```jinja
|
|
494
|
-
{{
|
|
503
|
+
{{ objects | tojson(indent=2) }}
|
|
495
504
|
```
|
|
496
505
|
|
|
497
506
|
```bash
|
|
498
|
-
templify
|
|
507
|
+
templify render -t all.json -d employees.xlsx -o export -f employees --order name
|
|
499
508
|
```
|
|
500
509
|
|
|
501
510
|
> `tojson` sorts object keys alphabetically.
|
|
@@ -526,7 +535,7 @@ Invalid value: Template Error: Rendered output is not valid JSON: Expecting ','
|
|
|
526
535
|
```
|
|
527
536
|
|
|
528
537
|
```bash
|
|
529
|
-
templify
|
|
538
|
+
templify render --each -t department.html -d employees.xlsx -o departments --group-by department -n department
|
|
530
539
|
```
|
|
531
540
|
|
|
532
541
|
> Values are **not** HTML-escaped, so `<b>` in your data stays bold. If the data isn't trusted, escape it with `{{ value | e }}`.
|
|
@@ -535,15 +544,15 @@ templify m -t department.html -d employees.xlsx -o departments --group-by depart
|
|
|
535
544
|
|
|
536
545
|
| Situation | Result |
|
|
537
546
|
| ------------------------------------------- | --------------------------------------------------- |
|
|
538
|
-
| No `-
|
|
547
|
+
| No `-n` | `template-1.txt`, `template-2.txt`, ... |
|
|
539
548
|
| `-k name` | `Rama.txt` |
|
|
540
549
|
| `-k name -i` | `3 - Rama.txt` |
|
|
541
|
-
| The `-
|
|
550
|
+
| The `-n` column is empty or missing for a row | That row falls back to `template-<n>.txt` |
|
|
542
551
|
| The value has invalid characters: `a/b:c?` | They become `_`: `a_b_c_.txt` |
|
|
543
552
|
| The value is a number: `-k id` | `42.txt` |
|
|
544
553
|
| The file already exists | A short random suffix is added: `Rama-3f9a1c2e.txt`. **Nothing is ever overwritten** |
|
|
545
554
|
| The output folder doesn't exist | It is created, including parent folders |
|
|
546
|
-
| `
|
|
555
|
+
| `render` without `-f` | Named after the template: `team.md` → `team.md` |
|
|
547
556
|
|
|
548
557
|
Output files are written as **UTF-8**, with the template's line endings kept as they are.
|
|
549
558
|
|
|
@@ -553,10 +562,10 @@ Output files are written as **UTF-8**, with the template's line endings kept as
|
|
|
553
562
|
|
|
554
563
|
```bash
|
|
555
564
|
# highest salary first
|
|
556
|
-
templify
|
|
565
|
+
templify render -t names.txt -d employees.xlsx -o out --order -salary
|
|
557
566
|
|
|
558
567
|
# by department A→Z, then by salary high→low inside each department
|
|
559
|
-
templify
|
|
568
|
+
templify render -t names.txt -d employees.xlsx -o out --order department --order -salary
|
|
560
569
|
```
|
|
561
570
|
|
|
562
571
|
```text
|
|
@@ -596,17 +605,17 @@ Ordering by a column that doesn't exist fails with the list of available columns
|
|
|
596
605
|
|
|
597
606
|
```bash
|
|
598
607
|
# IT only
|
|
599
|
-
templify
|
|
608
|
+
templify render -t names.txt -d employees.xlsx -o out \
|
|
600
609
|
--filter '{"name": "string__eq", "args": ["department", "IT"], "kwargs": {}, "inverse": false}'
|
|
601
610
|
# → Rawaa Rama
|
|
602
611
|
|
|
603
612
|
# everyone except IT
|
|
604
|
-
templify
|
|
613
|
+
templify render -t names.txt -d employees.xlsx -o out \
|
|
605
614
|
--filter '{"name": "string__eq", "args": ["department", "IT"], "kwargs": {}, "inverse": true}'
|
|
606
615
|
# → Abdullah Omar
|
|
607
616
|
|
|
608
617
|
# same rule, arguments by name
|
|
609
|
-
templify
|
|
618
|
+
templify render -t names.txt -d employees.xlsx -o out \
|
|
610
619
|
--filter '{"name": "int__gt", "args": [], "kwargs": {"key": "salary", "value": 1400}, "inverse": false}'
|
|
611
620
|
# → Rawaa Rama
|
|
612
621
|
```
|
|
@@ -620,7 +629,7 @@ templify s -t names.txt -d employees.xlsx -o out \
|
|
|
620
629
|
|
|
621
630
|
```bash
|
|
622
631
|
# in Homs AND earning at least 1300
|
|
623
|
-
templify
|
|
632
|
+
templify render -t names.txt -d employees.xlsx -o out --filter '{
|
|
624
633
|
"operator": "all",
|
|
625
634
|
"expressions": [
|
|
626
635
|
{"name": "string__eq", "args": ["city", "Homs"], "kwargs": {}, "inverse": false},
|
|
@@ -630,7 +639,7 @@ templify s -t names.txt -d employees.xlsx -o out --filter '{
|
|
|
630
639
|
# → Rama
|
|
631
640
|
|
|
632
641
|
# in Aleppo OR earning more than 1700
|
|
633
|
-
templify
|
|
642
|
+
templify render -t names.txt -d employees.xlsx -o out --filter '{
|
|
634
643
|
"operator": "any",
|
|
635
644
|
"expressions": [
|
|
636
645
|
{"name": "string__eq", "args": ["city", "Aleppo"], "kwargs": {}, "inverse": false},
|
|
@@ -644,7 +653,7 @@ Expressions **nest** to any depth:
|
|
|
644
653
|
|
|
645
654
|
```bash
|
|
646
655
|
# IT AND (in Damascus OR hired since 2024)
|
|
647
|
-
templify
|
|
656
|
+
templify render -t names.txt -d employees.xlsx -o out --filter '{
|
|
648
657
|
"operator": "all",
|
|
649
658
|
"expressions": [
|
|
650
659
|
{"name": "string__eq", "args": ["department", "IT"], "kwargs": {}, "inverse": false},
|
|
@@ -724,12 +733,12 @@ They compare the **day only** and accept every form a date comes in:
|
|
|
724
733
|
|
|
725
734
|
```bash
|
|
726
735
|
# hired in 2024 or later (Excel dates)
|
|
727
|
-
templify
|
|
736
|
+
templify render -t names.txt -d employees.xlsx -o out \
|
|
728
737
|
--filter '{"name": "date__ge", "args": ["hired", "2024-01-01"], "kwargs": {}, "inverse": false}'
|
|
729
738
|
# → Rama Omar
|
|
730
739
|
|
|
731
740
|
# hired before 2024 (JSON string dates)
|
|
732
|
-
templify
|
|
741
|
+
templify render -t names.txt -d employees.json -o out \
|
|
733
742
|
--filter '{"name": "date__lt", "args": ["hired", "2024-01-01"], "kwargs": {}, "inverse": false}'
|
|
734
743
|
# → Abdullah Rawaa
|
|
735
744
|
|
|
@@ -753,7 +762,7 @@ templify s -t names.txt -d employees.json -o out \
|
|
|
753
762
|
`--group-by <column>` merges rows that share the same value into **one group**. Each group is a new row holding:
|
|
754
763
|
|
|
755
764
|
- the group-by column(s), with the shared value
|
|
756
|
-
- `items` (rename it with `--
|
|
765
|
+
- `items` (rename it with `--items-var`), the list of the original rows in that group
|
|
757
766
|
|
|
758
767
|
```text
|
|
759
768
|
rows: groups (--group-by department):
|
|
@@ -765,10 +774,10 @@ rows: groups (--group-by department):
|
|
|
765
774
|
|
|
766
775
|
Groups appear in the order their first row appears. Grouping runs **after** ordering and filtering, so `--order` also sorts the rows inside every group, and `--filter` decides which rows get grouped.
|
|
767
776
|
|
|
768
|
-
**One file per group**, with `
|
|
777
|
+
**One file per group**, with `render --each`: `-n` can name files after the group column.
|
|
769
778
|
|
|
770
779
|
```bash
|
|
771
|
-
templify
|
|
780
|
+
templify render --each -t department.html -d employees.xlsx -o departments --group-by department -n department
|
|
772
781
|
```
|
|
773
782
|
|
|
774
783
|
```text
|
|
@@ -777,20 +786,20 @@ departments/
|
|
|
777
786
|
└── IT.html
|
|
778
787
|
```
|
|
779
788
|
|
|
780
|
-
**One report of all groups**, with `
|
|
789
|
+
**One report of all groups**, with `render`: loop over groups, then over each group's rows.
|
|
781
790
|
|
|
782
791
|
`by-city.txt`:
|
|
783
792
|
|
|
784
793
|
```jinja
|
|
785
|
-
{% for group in
|
|
794
|
+
{% for group in objects -%}
|
|
786
795
|
{{ group.city }} / {{ group.department }}:
|
|
787
796
|
{%- for e in group.people %} {{ e.name }}{% endfor %}
|
|
788
797
|
{% endfor %}
|
|
789
798
|
```
|
|
790
799
|
|
|
791
800
|
```bash
|
|
792
|
-
templify
|
|
793
|
-
--order city --group-by city --group-by department --
|
|
801
|
+
templify render -t by-city.txt -d employees.xlsx -o out \
|
|
802
|
+
--order city --group-by city --group-by department --items-var people
|
|
794
803
|
```
|
|
795
804
|
|
|
796
805
|
```text
|
|
@@ -814,7 +823,7 @@ Useful group-level expressions:
|
|
|
814
823
|
| Problem | Message |
|
|
815
824
|
| -------------------------------------------------------- | --------------------------------------------------- |
|
|
816
825
|
| The column doesn't exist | `Key 'country' not found in data, Available keys: ...` |
|
|
817
|
-
| `--
|
|
826
|
+
| `--items-var` equals a group-by column | `Items key 'city' can not be one of the group by keys` |
|
|
818
827
|
| The column holds lists or objects (JSON) | `Can not group by unhashable values ...` |
|
|
819
828
|
|
|
820
829
|
## Cookbook
|
|
@@ -822,20 +831,20 @@ Useful group-level expressions:
|
|
|
822
831
|
**Offer letters in Word, one per employee, numbered:**
|
|
823
832
|
|
|
824
833
|
```bash
|
|
825
|
-
templify
|
|
834
|
+
templify render --each -t offer.docx -d employees.xlsx -o offers -n name --numbered
|
|
826
835
|
```
|
|
827
836
|
|
|
828
837
|
**Only employees hired this year, newest first:**
|
|
829
838
|
|
|
830
839
|
```bash
|
|
831
|
-
templify
|
|
840
|
+
templify render --each -t offer.docx -d employees.xlsx -o new-hires -n name --order -hired \
|
|
832
841
|
--filter '{"name": "date__ge", "args": ["hired", "2024-01-01"], "kwargs": {}, "inverse": false}'
|
|
833
842
|
```
|
|
834
843
|
|
|
835
844
|
**Emails only for people who have an email address:**
|
|
836
845
|
|
|
837
846
|
```bash
|
|
838
|
-
templify
|
|
847
|
+
templify render --each -t email.txt -d employees.xlsx -o emails -n email \
|
|
839
848
|
--filter '{"name": "is_null", "args": ["email"], "kwargs": {}, "inverse": true}'
|
|
840
849
|
```
|
|
841
850
|
|
|
@@ -853,39 +862,42 @@ Total: {{ items | sum(attribute="salary") }}
|
|
|
853
862
|
```
|
|
854
863
|
|
|
855
864
|
```bash
|
|
856
|
-
templify
|
|
865
|
+
templify render --each -t department.md -d employees.xlsx -o reports -n department --group-by department --order -salary
|
|
857
866
|
```
|
|
858
867
|
|
|
859
868
|
**Convert Excel to JSON:**
|
|
860
869
|
|
|
861
870
|
```bash
|
|
862
|
-
templify
|
|
871
|
+
templify render -t all.json -d employees.xlsx -o export -f employees
|
|
863
872
|
```
|
|
864
873
|
|
|
865
|
-
with `all.json` containing `{{
|
|
874
|
+
with `all.json` containing `{{ objects | tojson(indent=2) }}`.
|
|
866
875
|
|
|
867
876
|
**One JSON file per record, for an API import:**
|
|
868
877
|
|
|
869
878
|
```bash
|
|
870
|
-
templify
|
|
879
|
+
templify render --each -t profile.json -d employees.xlsx -o api -n email
|
|
871
880
|
```
|
|
872
881
|
|
|
873
882
|
**A single printable Word file, one page per employee:**
|
|
874
883
|
|
|
875
884
|
```bash
|
|
876
|
-
templify
|
|
885
|
+
templify render -t all-offers.docx -d employees.xlsx -o print --order name
|
|
877
886
|
```
|
|
878
887
|
|
|
879
|
-
**Name files with two columns**: templify names files from one column, so add a combined column to your data (an Excel formula like `=A2&" - "&B2` works once the file is saved in Excel), then use `-
|
|
888
|
+
**Name files with two columns**: templify names files from one column, so add a combined column to your data (an Excel formula like `=A2&" - "&B2` works once the file is saved in Excel), then use `-n full_title`.
|
|
880
889
|
|
|
881
890
|
## Errors
|
|
882
891
|
|
|
883
|
-
templify reports problems as short messages
|
|
892
|
+
templify reports problems as short messages. Bad input (options, data, template files) exits with code `2`; a template that fails while rendering exits with code `1`:
|
|
884
893
|
|
|
885
894
|
| Problem | Example message |
|
|
886
895
|
| ------------------------------------------------ | ---------------------------------------------------------------------- |
|
|
887
896
|
| Unsupported template extension | `Template extension 'pdf' is not supported, supported: docx, htm, ...` |
|
|
888
|
-
| Unsupported data extension | `Extension csv is not supported`
|
|
897
|
+
| Unsupported data extension | `Extension 'csv' is not supported for being a data file` |
|
|
898
|
+
| Both or neither of `-d` / `-s` | `Give exactly one data source: -d / --data or -s / --schema` |
|
|
899
|
+
| Option used in the wrong mode | `--name-by only works with --each` |
|
|
900
|
+
| `--each` with `--object` | `--each and --object can not be used together` |
|
|
889
901
|
| File doesn't exist / is a folder | `File '...' does not exist.` |
|
|
890
902
|
| Jinja syntax error in the template | `Template Error: unexpected '}'` |
|
|
891
903
|
| Corrupted `.docx` template | `Could not load template 't.docx': ...` |
|
|
@@ -903,7 +915,7 @@ Each extension point is a registry filled with a decorator. Add your code, and t
|
|
|
903
915
|
|
|
904
916
|
### A new data source
|
|
905
917
|
|
|
906
|
-
`src/templify/readers.py`: a function that takes a path and returns a list of dicts
|
|
918
|
+
`src/templify/readers.py`: a function that takes a path and returns a list of dicts, registered on `readers`. A reader for `render --object` returns one dict and is registered on `object_readers`.
|
|
907
919
|
|
|
908
920
|
```python
|
|
909
921
|
import csv
|
|
@@ -939,8 +951,9 @@ class PptxWriter:
|
|
|
939
951
|
def __init__(self, template_path: Path) -> None:
|
|
940
952
|
self.template_path = template_path
|
|
941
953
|
|
|
942
|
-
def write(
|
|
943
|
-
|
|
954
|
+
def write(
|
|
955
|
+
self, context: Context, filepath: Path
|
|
956
|
+
) -> None: ... # render context into the template and save to filepath
|
|
944
957
|
```
|
|
945
958
|
|
|
946
959
|
A format that needs validation can extend `TextWriter` and override `render`, the way `JsonWriter` does.
|
|
@@ -985,7 +998,7 @@ The demo GIFs are recorded with [VHS](https://github.com/charmbracelet/vhs) (`sc
|
|
|
985
998
|
or `brew install vhs`) and PowerShell (`pwsh`), from the repository root:
|
|
986
999
|
|
|
987
1000
|
```bash
|
|
988
|
-
vhs docs/demo/schema.tape && vhs docs/demo/schema-list.tape
|
|
1001
|
+
vhs docs/demo/each.tape && vhs docs/demo/schema.tape && vhs docs/demo/schema-list.tape
|
|
989
1002
|
```
|
|
990
1003
|
|
|
991
1004
|
### Project structure
|
|
@@ -993,7 +1006,7 @@ vhs docs/demo/schema.tape && vhs docs/demo/schema-list.tape
|
|
|
993
1006
|
```text
|
|
994
1007
|
src/templify/
|
|
995
1008
|
├── models.py # Data / DataItem types
|
|
996
|
-
├── readers.py # data sources:
|
|
1009
|
+
├── readers.py # data sources: readers / object_readers, read_excel, read_json
|
|
997
1010
|
├── writers.py # Writer protocol, WriterRegistry, Docx/Text/Json writers,
|
|
998
1011
|
│ # write_single / write_multiple, file naming
|
|
999
1012
|
├── transformers.py # order_by_data, filter_data, group_by_data
|