pdfgen-juanipis 0.1.3__py3-none-any.whl

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.
Files changed (33) hide show
  1. pdfgen/__init__.py +17 -0
  2. pdfgen/api.py +69 -0
  3. pdfgen/assets/banner-clean.png +0 -0
  4. pdfgen/assets/banner.png +0 -0
  5. pdfgen/assets/fonts/BCDEEE_Calibri_5.ttf +0 -0
  6. pdfgen/assets/fonts/BCDFEE_CenturyGothic-Bold_9.ttf +0 -0
  7. pdfgen/assets/fonts/BCDGEE_CenturyGothic-Bold_14.ttf +0 -0
  8. pdfgen/assets/fonts/BCDHEE_Calibri-Bold_20.ttf +0 -0
  9. pdfgen/assets/fonts/BCDIEE_Calibri-Bold_25.ttf +0 -0
  10. pdfgen/assets/fonts/BCDJEE_Calibri_27.ttf +0 -0
  11. pdfgen/assets/fonts/BCDKEE_Calibri-Italic_33.ttf +0 -0
  12. pdfgen/assets/fonts/BCDLEE_Calibri-Italic_52.ttf +0 -0
  13. pdfgen/assets/fonts/BCDMEE_SegoeUI_54.ttf +0 -0
  14. pdfgen/assets/fonts/BCDNEE_SegoeUI_60.ttf +0 -0
  15. pdfgen/assets/fonts/BCDOEE_Aptos Narrow,Bold_142.ttf +0 -0
  16. pdfgen/assets/fonts/BCDPEE_Aptos Narrow,Bold_144.ttf +0 -0
  17. pdfgen/assets/fonts/BCEAEE_Aptos Narrow_149.ttf +0 -0
  18. pdfgen/assets/fonts/BCEBEE_Aptos Narrow_154.ttf +0 -0
  19. pdfgen/assets/fonts/TimesNewRomanPS-BoldMT_38.ttf +0 -0
  20. pdfgen/assets/logo.png +0 -0
  21. pdfgen/cli.py +106 -0
  22. pdfgen/pagination.py +1045 -0
  23. pdfgen/render.py +348 -0
  24. pdfgen/schema.json +126 -0
  25. pdfgen/templates/boletin.css +389 -0
  26. pdfgen/templates/boletin_template.html.jinja +129 -0
  27. pdfgen/validator.py +247 -0
  28. pdfgen_juanipis-0.1.3.dist-info/METADATA +170 -0
  29. pdfgen_juanipis-0.1.3.dist-info/RECORD +33 -0
  30. pdfgen_juanipis-0.1.3.dist-info/WHEEL +5 -0
  31. pdfgen_juanipis-0.1.3.dist-info/entry_points.txt +2 -0
  32. pdfgen_juanipis-0.1.3.dist-info/licenses/LICENSE +21 -0
  33. pdfgen_juanipis-0.1.3.dist-info/top_level.txt +1 -0
pdfgen/validator.py ADDED
@@ -0,0 +1,247 @@
1
+ import copy
2
+ import json
3
+ import pathlib
4
+ import re
5
+ from typing import Any, Dict, List, Tuple
6
+
7
+
8
+ ALLOWED_BLOCK_TYPES = {"text", "table", "figure", "map_grid", "html"}
9
+ DEFAULT_TABLE_WIDTH = 532.66
10
+ DEFAULT_DEP_WIDTH = 120.0
11
+
12
+
13
+ def validate_and_normalize(data: Dict[str, Any], root_dir: pathlib.Path) -> Tuple[Dict[str, Any], List[str]]:
14
+ warnings: List[str] = []
15
+ normalized = copy.deepcopy(data)
16
+
17
+ schema_warnings = _validate_schema(normalized, root_dir)
18
+ warnings.extend(schema_warnings)
19
+
20
+ theme = normalized.setdefault("theme", {})
21
+ theme.setdefault("footer_site", "")
22
+ theme.setdefault("footer_phone", "")
23
+ theme.setdefault("show_header_titles", False)
24
+
25
+ assets_dir = root_dir / "assets"
26
+
27
+ refs_catalog = normalized.get("refs_catalog", {})
28
+ if refs_catalog and not isinstance(refs_catalog, dict):
29
+ warnings.append("refs_catalog must be a dict; ignoring invalid value")
30
+ refs_catalog = {}
31
+ normalized["refs_catalog"] = refs_catalog
32
+
33
+ if "pages" in normalized:
34
+ _validate_pages(normalized, assets_dir, refs_catalog, warnings)
35
+ elif "sections" in normalized:
36
+ _validate_sections(normalized, assets_dir, refs_catalog, warnings)
37
+ else:
38
+ warnings.append("data must include 'sections' or 'pages'")
39
+
40
+ return normalized, warnings
41
+
42
+
43
+ def _validate_pages(
44
+ data: Dict[str, Any],
45
+ assets_dir: pathlib.Path,
46
+ refs_catalog: Dict[str, str],
47
+ warnings: List[str],
48
+ ) -> None:
49
+ pages = data.get("pages", [])
50
+ if not isinstance(pages, list):
51
+ warnings.append("pages must be a list")
52
+ return
53
+
54
+ for page in pages:
55
+ page.setdefault("refs_catalog", refs_catalog)
56
+ _normalize_theme_paths(page, assets_dir, warnings)
57
+ _validate_blocks(page.get("blocks", []), assets_dir, refs_catalog, warnings)
58
+
59
+
60
+ def _validate_sections(
61
+ data: Dict[str, Any],
62
+ assets_dir: pathlib.Path,
63
+ refs_catalog: Dict[str, str],
64
+ warnings: List[str],
65
+ ) -> None:
66
+ sections = data.get("sections", [])
67
+ if not isinstance(sections, list):
68
+ warnings.append("sections must be a list")
69
+ return
70
+
71
+ _normalize_theme_paths(data.get("theme", {}), assets_dir, warnings)
72
+
73
+ for section in sections:
74
+ _validate_blocks(section.get("content", []), assets_dir, refs_catalog, warnings)
75
+
76
+
77
+ def _validate_blocks(
78
+ blocks: List[Dict[str, Any]],
79
+ assets_dir: pathlib.Path,
80
+ refs_catalog: Dict[str, str],
81
+ warnings: List[str],
82
+ ) -> None:
83
+ if not isinstance(blocks, list):
84
+ warnings.append("blocks/content must be a list")
85
+ return
86
+
87
+ for block in blocks:
88
+ block_type = block.get("type", "text")
89
+ if block_type not in ALLOWED_BLOCK_TYPES:
90
+ warnings.append(f"Unknown block type: {block_type}")
91
+ continue
92
+
93
+ if block_type == "table":
94
+ _validate_table(block.get("table", {}), warnings)
95
+ elif block_type == "figure":
96
+ _normalize_path(block, "path", assets_dir, warnings)
97
+ elif block_type == "map_grid":
98
+ items = block.get("items", [])
99
+ if not isinstance(items, list):
100
+ warnings.append("map_grid.items must be a list")
101
+ else:
102
+ for item in items:
103
+ _normalize_path(item, "path", assets_dir, warnings)
104
+ elif block_type == "text":
105
+ _validate_text_block(block, refs_catalog, warnings)
106
+ elif block_type == "html":
107
+ _warn_missing_refs(block.get("html", ""), refs_catalog, warnings)
108
+
109
+
110
+ def _validate_table(table: Dict[str, Any], warnings: List[str]) -> None:
111
+ groups = table.get("groups", [])
112
+ rows = table.get("rows", [])
113
+
114
+ if not groups:
115
+ warnings.append("table.groups is empty")
116
+ if not rows:
117
+ warnings.append("table.rows is empty")
118
+
119
+ table.setdefault("total_width", DEFAULT_TABLE_WIDTH)
120
+ table.setdefault("dep_width", DEFAULT_DEP_WIDTH)
121
+
122
+ num_cols = 0
123
+ for group in groups:
124
+ months = group.get("months", [])
125
+ if not months:
126
+ warnings.append("table group has empty months")
127
+ num_cols += len(months)
128
+
129
+ if num_cols == 0:
130
+ return
131
+
132
+ for row in rows:
133
+ vals = row.get("vals", [])
134
+ if not isinstance(vals, list):
135
+ warnings.append("table row vals must be a list")
136
+ row["vals"] = []
137
+ vals = row["vals"]
138
+ if len(vals) < num_cols:
139
+ warnings.append(
140
+ f"table row '{row.get('dep', '')}' has {len(vals)} vals; expected {num_cols}. Padding."
141
+ )
142
+ row["vals"] = vals + [""] * (num_cols - len(vals))
143
+ elif len(vals) > num_cols:
144
+ warnings.append(
145
+ f"table row '{row.get('dep', '')}' has {len(vals)} vals; expected {num_cols}. Truncating."
146
+ )
147
+ row["vals"] = vals[:num_cols]
148
+
149
+
150
+ def _validate_text_block(block: Dict[str, Any], refs_catalog: Dict[str, str], warnings: List[str]) -> None:
151
+ text = block.get("text", "")
152
+ if isinstance(text, list):
153
+ for paragraph in text:
154
+ _warn_missing_refs(paragraph, refs_catalog, warnings)
155
+ else:
156
+ _warn_missing_refs(text, refs_catalog, warnings)
157
+
158
+
159
+ def _warn_missing_refs(text: str, refs_catalog: Dict[str, str], warnings: List[str]) -> None:
160
+ if not refs_catalog or not text:
161
+ return
162
+ for ref_id in _extract_ref_ids(str(text)):
163
+ if ref_id not in refs_catalog:
164
+ warnings.append(f"Missing refs_catalog entry for [{ref_id}]")
165
+
166
+
167
+ def _normalize_theme_paths(theme: Dict[str, Any], assets_dir: pathlib.Path, warnings: List[str]) -> None:
168
+ _normalize_path(theme, "header_banner_path", assets_dir, warnings)
169
+ _normalize_path(theme, "header_banner_path_cont", assets_dir, warnings)
170
+ _normalize_path(theme, "header_logo_path", assets_dir, warnings)
171
+
172
+
173
+ def _normalize_path(container: Dict[str, Any], key: str, assets_dir: pathlib.Path, warnings: List[str]) -> None:
174
+ if key not in container:
175
+ return
176
+ value = container.get(key)
177
+ if not value:
178
+ return
179
+ path = pathlib.Path(str(value))
180
+ if path.is_absolute():
181
+ return
182
+ candidate = assets_dir / path
183
+ if candidate.exists():
184
+ container[key] = str(candidate.resolve())
185
+ return
186
+
187
+ package_assets = pathlib.Path(__file__).resolve().parent / "assets"
188
+ candidate = package_assets / path
189
+ if candidate.exists():
190
+ container[key] = str(candidate.resolve())
191
+ else:
192
+ warnings.append(f"Asset not found for {key}: {value}")
193
+
194
+
195
+ def _extract_ref_ids(text: str) -> List[str]:
196
+ ids: List[str] = []
197
+ seen = set()
198
+ for match in re.findall(r"\[(.*?)\]", text):
199
+ for token in re.split(r"[;,]\s*", match.strip()):
200
+ token = token.strip()
201
+ if not token:
202
+ continue
203
+ range_match = re.match(r"^(\d+)\s*[-–]\s*(\d+)$", token)
204
+ if range_match:
205
+ start = int(range_match.group(1))
206
+ end = int(range_match.group(2))
207
+ step = 1 if end >= start else -1
208
+ for val in range(start, end + step, step):
209
+ key = str(val)
210
+ if key not in seen:
211
+ ids.append(key)
212
+ seen.add(key)
213
+ continue
214
+ if re.match(r"^\d+$", token):
215
+ if token not in seen:
216
+ ids.append(token)
217
+ seen.add(token)
218
+ return ids
219
+
220
+
221
+ def _validate_schema(data: Dict[str, Any], root_dir: pathlib.Path) -> List[str]:
222
+ warnings: List[str] = []
223
+ schema_path = root_dir / "src" / "pdfgen" / "schema.json"
224
+ if not schema_path.exists():
225
+ schema_path = pathlib.Path(__file__).resolve().parent / "schema.json"
226
+ if not schema_path.exists():
227
+ warnings.append("schema.json not found; skipping schema validation")
228
+ return warnings
229
+ try:
230
+ from jsonschema import Draft7Validator
231
+ except Exception:
232
+ warnings.append("jsonschema not installed; skipping schema validation")
233
+ return warnings
234
+
235
+ try:
236
+ schema = json.loads(schema_path.read_text(encoding="utf-8"))
237
+ except Exception as exc:
238
+ warnings.append(f"Failed to read schema.json: {exc}")
239
+ return warnings
240
+
241
+ validator = Draft7Validator(schema)
242
+ errors = sorted(validator.iter_errors(data), key=lambda e: e.path)
243
+ for err in errors:
244
+ loc = ".".join(str(p) for p in err.path)
245
+ prefix = f"schema:{loc}" if loc else "schema"
246
+ warnings.append(f"{prefix}: {err.message}")
247
+ return warnings
@@ -0,0 +1,170 @@
1
+ Metadata-Version: 2.4
2
+ Name: pdfgen-juanipis
3
+ Version: 0.1.3
4
+ Summary: HTML-to-PDF generator with pagination and validation
5
+ Author-email: Juan Pablo Díaz Correa <juanipis@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Juanipis/pdfgen-juanipis
8
+ Project-URL: Issues, https://github.com/Juanipis/pdfgen-juanipis/issues
9
+ Project-URL: Source, https://github.com/Juanipis/pdfgen-juanipis
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: Jinja2==3.1.4
16
+ Requires-Dist: WeasyPrint==59.0
17
+ Requires-Dist: pydyf==0.10.0
18
+ Requires-Dist: jsonschema==4.23.0
19
+ Requires-Dist: PyYAML==6.0.2
20
+ Dynamic: license-file
21
+
22
+ # pdfgen
23
+
24
+ Librería para generar PDFs a partir de data estructurada (HTML + WeasyPrint), con paginación, validación y soporte JSON/YAML.
25
+
26
+ ## Instalación
27
+
28
+ Desde el repo:
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Uso básico (Python)
35
+
36
+ ```python
37
+ from pdfgen.api import PDFGenConfig, PDFGen
38
+
39
+ config = PDFGenConfig.from_root("/ruta/a/tu/proyecto")
40
+ pdf = PDFGen(config)
41
+
42
+ pdf.render(data, output_path="salida.pdf", paginate=True, validate=True)
43
+ ```
44
+
45
+ Atajo:
46
+
47
+ ```python
48
+ from pdfgen.api import render_with_defaults
49
+
50
+ render_with_defaults(data, output_path="salida.pdf", root_dir="/ruta/a/tu/proyecto")
51
+ ```
52
+
53
+ ## CLI
54
+
55
+ Render (JSON o YAML):
56
+
57
+ ```bash
58
+ pdfgen render data.json salida.pdf --root /ruta/proyecto
59
+ pdfgen render data.yaml salida.pdf
60
+ ```
61
+
62
+ Validar (sin generar PDF):
63
+
64
+ ```bash
65
+ pdfgen validate data.yaml
66
+ ```
67
+
68
+ Desde stdin (YAML por defecto):
69
+
70
+ ```bash
71
+ cat data.yaml | pdfgen render - salida.pdf
72
+ ```
73
+
74
+ Opciones útiles:
75
+ - `--template-dir` usar tu template
76
+ - `--css` usar tu CSS
77
+ - `--fonts-conf` usar un `fonts.conf` propio
78
+ - `--fonts-dir` usar un directorio con `.ttf`
79
+ - `--css-extra` inyectar CSS adicional
80
+ - `--no-validate` desactivar validación
81
+ - `--no-paginate` desactivar paginación
82
+
83
+ ## Personalización
84
+
85
+ ### Fuentes propias
86
+
87
+ Opción A: `fonts.conf` propio.
88
+
89
+ ```python
90
+ config = PDFGenConfig.from_root("/ruta/a/tu/proyecto")
91
+ config.fonts_conf = "/ruta/a/mi/fonts.conf"
92
+ PDFGen(config).render(data, "salida.pdf")
93
+ ```
94
+
95
+ Opción B: directorio con `.ttf` desde CLI:
96
+
97
+ ```bash
98
+ pdfgen render data.json salida.pdf --fonts-dir /ruta/a/mis/fuentes
99
+ ```
100
+
101
+ ### Colores/estilos rápidos
102
+
103
+ Puedes inyectar CSS extra para cambiar variables o estilos:
104
+
105
+ ```python
106
+ css_extra = ":root { --blue-title: #0b7285; --orange-footer: #f08c00; }"
107
+ render_with_defaults(data, "salida.pdf", root_dir="/ruta", css_extra=css_extra)
108
+ ```
109
+
110
+ ### Template y CSS propios
111
+
112
+ ```bash
113
+ pdfgen render data.json salida.pdf --template-dir /ruta/template --css /ruta/template/boletin.css
114
+ ```
115
+
116
+ ## Estructura mínima del data
117
+
118
+ ```python
119
+ data = {
120
+ "theme": {
121
+ "header_banner_path": "banner.png",
122
+ "header_logo_path": "logo.png",
123
+ "title_line1": "Titulo",
124
+ "title_line2": "Subtitulo",
125
+ "footer_site": "example.org",
126
+ "footer_phone": "Contacto: +1 555 0100"
127
+ },
128
+ "sections": [
129
+ {
130
+ "title": "I. Introduccion",
131
+ "content": [
132
+ {"type": "text", "text": "Texto con <strong>negrita</strong>."}
133
+ ]
134
+ }
135
+ ]
136
+ }
137
+ ```
138
+
139
+ ## Estilos inline
140
+
141
+ Los bloques de texto aceptan HTML:
142
+ - `<strong>` negrita
143
+ - `<em>` itálica
144
+ - `<u>` subrayado
145
+ - `<s>` tachado
146
+
147
+ ## Ejemplos
148
+
149
+ - `examples/minimal.yaml`
150
+
151
+ ## Validación
152
+
153
+ Se valida automáticamente con `schema.json`. Los warnings se imprimen en consola.
154
+
155
+ Si quieres desactivar:
156
+
157
+ ```python
158
+ pdf.render(data, output_path="salida.pdf", validate=False)
159
+ ```
160
+
161
+ ## Licencia
162
+
163
+ MIT
164
+
165
+ ## Releases
166
+
167
+ Al hacer push a `main`, el workflow **Auto Release** crea tag y Release si la versión en `pyproject.toml` es nueva, y publica automáticamente en PyPI.
168
+
169
+
170
+ Hecho por Juanipis
@@ -0,0 +1,33 @@
1
+ pdfgen/__init__.py,sha256=4jtt7D6NgNMRk-UO_UXrtn17gjir-RDulcptgOmp56Y,446
2
+ pdfgen/api.py,sha256=d9cJ-Seu9EwgIqCeityHgchsMUWcaczwd554njSkISM,2051
3
+ pdfgen/cli.py,sha256=QTWF6-xYG7IE61vj39P59rlLu9ValcZtGsxmFd9GO4I,3782
4
+ pdfgen/pagination.py,sha256=8A-mtFM_tMOUH5jZT7fojpItxDGu1vSUIVmcAOT1Ezk,39027
5
+ pdfgen/render.py,sha256=qUqfhUFFAks76rjicgmC3MQqfelde-KF7Ewri4nyfx4,14895
6
+ pdfgen/schema.json,sha256=vzpefhYtHW7cQj9zQaPmgBYSr6JvsYK45XB5PoISVu4,3863
7
+ pdfgen/validator.py,sha256=FmnMZjb-CcZeyTicJlevigk8awXd0tMFkRFa85HSPt8,8510
8
+ pdfgen/assets/banner-clean.png,sha256=mNJeBFUS1EPWYPN74u_1nHLl7moerw-NmkvYuzUuFzo,1023
9
+ pdfgen/assets/banner.png,sha256=0qvxaK2MTvRF7zk_vqzkge_zUPWr9BU8NdFFLdhH5uo,1543
10
+ pdfgen/assets/logo.png,sha256=mdlP4nHd67W5iaaIJetBLNWbbchkcMe7X0FrbiIw34A,1124
11
+ pdfgen/assets/fonts/BCDEEE_Calibri_5.ttf,sha256=p_g3mkSqeuHzYku1RQeJbB0u4ObVLFrFXw9OK8jBKdc,166944
12
+ pdfgen/assets/fonts/BCDFEE_CenturyGothic-Bold_9.ttf,sha256=nnSa8BTxjLyjHKPvwWIcKj0n-1v7GPyx1hFm7wzpIc0,25004
13
+ pdfgen/assets/fonts/BCDGEE_CenturyGothic-Bold_14.ttf,sha256=nnSa8BTxjLyjHKPvwWIcKj0n-1v7GPyx1hFm7wzpIc0,25004
14
+ pdfgen/assets/fonts/BCDHEE_Calibri-Bold_20.ttf,sha256=fucZ-DSOZKk1zeOR1SRpXpXvTiUz43x94ng4jMWbtE4,123256
15
+ pdfgen/assets/fonts/BCDIEE_Calibri-Bold_25.ttf,sha256=fucZ-DSOZKk1zeOR1SRpXpXvTiUz43x94ng4jMWbtE4,123256
16
+ pdfgen/assets/fonts/BCDJEE_Calibri_27.ttf,sha256=p_g3mkSqeuHzYku1RQeJbB0u4ObVLFrFXw9OK8jBKdc,166944
17
+ pdfgen/assets/fonts/BCDKEE_Calibri-Italic_33.ttf,sha256=RLWG91jghcpaYURNRQUlaMaUZdbGKaLa037peB1PbaM,117848
18
+ pdfgen/assets/fonts/BCDLEE_Calibri-Italic_52.ttf,sha256=RLWG91jghcpaYURNRQUlaMaUZdbGKaLa037peB1PbaM,117848
19
+ pdfgen/assets/fonts/BCDMEE_SegoeUI_54.ttf,sha256=w0Ksdnsp8yvwx8LwzybaBX6NuITVJWS8l0XTm9-xQtc,68880
20
+ pdfgen/assets/fonts/BCDNEE_SegoeUI_60.ttf,sha256=w0Ksdnsp8yvwx8LwzybaBX6NuITVJWS8l0XTm9-xQtc,68880
21
+ "pdfgen/assets/fonts/BCDOEE_Aptos Narrow,Bold_142.ttf",sha256=hrK2c59rBQ4lkX9_-nFHbBMfoDikd9M47hRxiMw3at8,23896
22
+ "pdfgen/assets/fonts/BCDPEE_Aptos Narrow,Bold_144.ttf",sha256=hrK2c59rBQ4lkX9_-nFHbBMfoDikd9M47hRxiMw3at8,23896
23
+ pdfgen/assets/fonts/BCEAEE_Aptos Narrow_149.ttf,sha256=mg-JKL3wHgRd_qVRAXXn6IKGnl_PJZnx5j95WAl6VB8,30924
24
+ pdfgen/assets/fonts/BCEBEE_Aptos Narrow_154.ttf,sha256=mg-JKL3wHgRd_qVRAXXn6IKGnl_PJZnx5j95WAl6VB8,30924
25
+ pdfgen/assets/fonts/TimesNewRomanPS-BoldMT_38.ttf,sha256=CAyIzST1vKiE9DhZriELQ_OzNeBrnWFQmAdRu4Qdoz0,90100
26
+ pdfgen/templates/boletin.css,sha256=lCiCNkCcHrUepmm3JnnbQxSf_UZBYCqGBiIYFXlk2t8,6244
27
+ pdfgen/templates/boletin_template.html.jinja,sha256=aWdeFc-1VkynxGSe4WaL3SqkEKLOQz63K28SMmQ_qOI,4550
28
+ pdfgen_juanipis-0.1.3.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
29
+ pdfgen_juanipis-0.1.3.dist-info/METADATA,sha256=jjHPP5RK_WOpVw8HPkuBjOOrEwXZaFT4yIh6cIeUdOg,3718
30
+ pdfgen_juanipis-0.1.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
31
+ pdfgen_juanipis-0.1.3.dist-info/entry_points.txt,sha256=k94J3IsuMlLw09pyLYkVqXq3rhbUusR_oblgNBD8Qfk,43
32
+ pdfgen_juanipis-0.1.3.dist-info/top_level.txt,sha256=j-KjOvwSSJxIWLnr7bjvZj2JiCMp-_DCKA0rGQl89e4,7
33
+ pdfgen_juanipis-0.1.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pdfgen = pdfgen.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ pdfgen