relator 0.1.0__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.
- relator-0.1.0.dist-info/METADATA +608 -0
- relator-0.1.0.dist-info/RECORD +15 -0
- relator-0.1.0.dist-info/WHEEL +5 -0
- relator-0.1.0.dist-info/entry_points.txt +2 -0
- relator-0.1.0.dist-info/licenses/LICENSE +21 -0
- relator-0.1.0.dist-info/top_level.txt +1 -0
- reporting/__init__.py +7 -0
- reporting/builtins.py +258 -0
- reporting/cli.py +53 -0
- reporting/engine.py +169 -0
- reporting/errors.py +13 -0
- reporting/media.py +124 -0
- reporting/parser.py +83 -0
- reporting/resolver.py +73 -0
- reporting/template.py +128 -0
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: relator
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Simple markdown/html report templating with loops, tables, lists and media.
|
|
5
|
+
Author: reporting maintainers
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://example.com/reporting
|
|
8
|
+
Project-URL: Repository, https://example.com/reporting
|
|
9
|
+
Project-URL: Documentation, https://example.com/reporting/docs
|
|
10
|
+
Keywords: report,template,markdown,html
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: rich
|
|
25
|
+
Requires-Dist: pillow
|
|
26
|
+
Requires-Dist: matplotlib
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# relator
|
|
32
|
+
|
|
33
|
+
`relator` — библиотека шаблонов для автогенерации отчётов в Markdown и HTML.
|
|
34
|
+
|
|
35
|
+
Главная идея:
|
|
36
|
+
|
|
37
|
+
1. Вы задаёте шаблон.
|
|
38
|
+
2. По шагам добавляете данные через `template.data([name, value])`.
|
|
39
|
+
3. Смотрите предпросмотр `template.print()`.
|
|
40
|
+
4. Компилируете отчёт в файл `template.compile(...)`.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Содержание
|
|
45
|
+
|
|
46
|
+
1. [Установка](#установка)
|
|
47
|
+
2. [Быстрый старт](#быстрый-старт)
|
|
48
|
+
3. [Концепция шаблона](#концепция-шаблона)
|
|
49
|
+
4. [Служебные плейсхолдеры](#служебные-плейсхолдеры)
|
|
50
|
+
5. [Пошаговый API](#пошаговый-api)
|
|
51
|
+
6. [Подробные примеры](#подробные-примеры)
|
|
52
|
+
7. [Тяжёлые сценарии](#тяжёлые-сценарии)
|
|
53
|
+
8. [Media: PIL и matplotlib](#media-pil-и-matplotlib)
|
|
54
|
+
9. [CLI](#cli)
|
|
55
|
+
10. [Ошибки и отладка](#ошибки-и-отладка)
|
|
56
|
+
11. [FAQ](#faq)
|
|
57
|
+
12. [Рекомендации по архитектуре](#рекомендации-по-архитектуре)
|
|
58
|
+
13. [Кроссплатформенность](#кроссплатформенность)
|
|
59
|
+
14. [Публикация и релиз](#публикация-и-релиз)
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Установка
|
|
64
|
+
|
|
65
|
+
### Через pip из локального проекта
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd reporting
|
|
69
|
+
python -m pip install -e .
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Через uv из локального проекта
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cd reporting
|
|
76
|
+
uv pip install -e .
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Через pip из PyPI
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
python -m pip install relator
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Через uv из PyPI
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv pip install relator
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Через pip из GitHub
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m pip install "git+https://github.com/threenebula23/Reporting.git"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Через uv из GitHub
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv pip install "git+https://github.com/threenebula23/Reporting.git"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Быстрый старт
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from reporting import Template
|
|
109
|
+
|
|
110
|
+
template = Template("template.md")
|
|
111
|
+
template.data(["name", [{"USER": "Ann", "SCORE": 10}, {"USER": "Bob", "SCORE": 20}]])
|
|
112
|
+
template.data(["names", ["Ann", "Bob", "Chris"]])
|
|
113
|
+
template.print()
|
|
114
|
+
template.compile("report.md")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Концепция шаблона
|
|
120
|
+
|
|
121
|
+
Шаблон — это обычный `.md` или `.html` файл.
|
|
122
|
+
|
|
123
|
+
Внутри него используются:
|
|
124
|
+
|
|
125
|
+
- блоки повторения `%%len=VAR ... %%`
|
|
126
|
+
- плейсхолдеры `[[...]]`
|
|
127
|
+
|
|
128
|
+
Пример:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
%%len=name
|
|
132
|
+
Строка [[CELL.ONE]] из [[CELL.COUNT]]
|
|
133
|
+
|[[name.KEYS]]|
|
|
134
|
+
|[[name.DIVIDER]]|
|
|
135
|
+
|[[name.VALUES]]|
|
|
136
|
+
%%
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Служебные плейсхолдеры
|
|
142
|
+
|
|
143
|
+
Важно: **служебные названия всегда в верхнем регистре**.
|
|
144
|
+
|
|
145
|
+
### CELL
|
|
146
|
+
|
|
147
|
+
- `[[CELL.INDEX]]` — индекс 0..N-1
|
|
148
|
+
- `[[CELL.ONE]]` — индекс 1..N
|
|
149
|
+
- `[[CELL.COUNT]]` — длина текущего цикла
|
|
150
|
+
|
|
151
|
+
### ITEM
|
|
152
|
+
|
|
153
|
+
- `[[ITEM]]` — текущий элемент в теле `%%len=VAR%%`
|
|
154
|
+
|
|
155
|
+
### TABLE
|
|
156
|
+
|
|
157
|
+
- `[[TABLE.VAR]]` — таблица
|
|
158
|
+
- `[[TABLE.VAR.NUMBERED]]` — таблица с колонкой `#`
|
|
159
|
+
- `[[TABLE.VAR.INDEX0]]` — таблица с колонкой index0
|
|
160
|
+
|
|
161
|
+
### LIST / ENUM
|
|
162
|
+
|
|
163
|
+
- `[[LIST.VAR]]` — маркированный список
|
|
164
|
+
- `[[ENUM.VAR]]` — нумерованный список
|
|
165
|
+
|
|
166
|
+
### MEDIA
|
|
167
|
+
|
|
168
|
+
- `[[MEDIA.VAR]]` — универсальная вставка по типу шаблона
|
|
169
|
+
- `[[MEDIA.VAR.PATH]]` — путь до файла
|
|
170
|
+
- `[[MEDIA.VAR.MD]]` — принудительный Markdown
|
|
171
|
+
- `[[MEDIA.VAR.HTML]]` — принудительный HTML
|
|
172
|
+
|
|
173
|
+
### Совместимость
|
|
174
|
+
|
|
175
|
+
- `[[VAR.TABLE]]`
|
|
176
|
+
- `[[VAR.TABLE.NUMBERED]]`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Пошаговый API
|
|
181
|
+
|
|
182
|
+
### 1) Создать объект
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from reporting import Template
|
|
186
|
+
template = Template("template.md")
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 2) Добавить данные по одной переменной
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
template.data(["name", [{"USER": "Ann", "SCORE": 10}]])
|
|
193
|
+
template.data(["names", ["Ann", "Bob"]])
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 3) Посмотреть результат в консоли
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
template.print(width=100)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 4) Скомпилировать в файл
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
template.compile("report.md")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Функциональный шорткат
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from reporting import compile_template
|
|
212
|
+
|
|
213
|
+
compile_template(
|
|
214
|
+
template_path="template.md",
|
|
215
|
+
context={"name": [{"A": 1}], "names": ["x", "y"]},
|
|
216
|
+
output_path="report.md",
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Подробные примеры
|
|
223
|
+
|
|
224
|
+
### Пример A: таблица внутри цикла
|
|
225
|
+
|
|
226
|
+
Шаблон:
|
|
227
|
+
|
|
228
|
+
```text
|
|
229
|
+
%%len=rows
|
|
230
|
+
|[[rows.KEYS]]|
|
|
231
|
+
|[[rows.DIVIDER]]|
|
|
232
|
+
|[[rows.VALUES]]|
|
|
233
|
+
%%
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Данные:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
rows = [
|
|
240
|
+
{"CITY": "Paris", "POP": 2148},
|
|
241
|
+
{"CITY": "Berlin", "POP": 3769},
|
|
242
|
+
]
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Пример B: таблица целиком
|
|
246
|
+
|
|
247
|
+
Шаблон:
|
|
248
|
+
|
|
249
|
+
```text
|
|
250
|
+
[[TABLE.rows]]
|
|
251
|
+
[[TABLE.rows.NUMBERED]]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Пример C: списки
|
|
255
|
+
|
|
256
|
+
Шаблон:
|
|
257
|
+
|
|
258
|
+
```text
|
|
259
|
+
[[LIST.names]]
|
|
260
|
+
[[ENUM.names]]
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Данные:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
names = ["Ann", "Bob", "Chris", "Dina"]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Пример D: вложенный список
|
|
270
|
+
|
|
271
|
+
Шаблон:
|
|
272
|
+
|
|
273
|
+
```text
|
|
274
|
+
%%len=names
|
|
275
|
+
1. [[ITEM]]
|
|
276
|
+
- index: [[CELL.INDEX]]
|
|
277
|
+
- one: [[CELL.ONE]]
|
|
278
|
+
%%
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Тяжёлые сценарии
|
|
284
|
+
|
|
285
|
+
Ниже сценарии ближе к реальному продакшену.
|
|
286
|
+
|
|
287
|
+
### Сценарий 1: еженедельный отчёт команды
|
|
288
|
+
|
|
289
|
+
Цель:
|
|
290
|
+
|
|
291
|
+
- по командам показать KPI
|
|
292
|
+
- дать агрегированную таблицу
|
|
293
|
+
- вставить график
|
|
294
|
+
|
|
295
|
+
Шаблон `weekly.md`:
|
|
296
|
+
|
|
297
|
+
```text
|
|
298
|
+
# Еженедельный отчёт
|
|
299
|
+
|
|
300
|
+
## Команды
|
|
301
|
+
|
|
302
|
+
%%len=teams
|
|
303
|
+
### Команда [[ITEM]]
|
|
304
|
+
Индекс: [[CELL.ONE]] / [[CELL.COUNT]]
|
|
305
|
+
%%
|
|
306
|
+
|
|
307
|
+
## KPI
|
|
308
|
+
[[TABLE.kpi.NUMBERED]]
|
|
309
|
+
|
|
310
|
+
## График
|
|
311
|
+
[[MEDIA.kpi_chart]]
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Код:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
from reporting import Template
|
|
318
|
+
import matplotlib.pyplot as plt
|
|
319
|
+
|
|
320
|
+
fig = plt.figure(figsize=(6, 3))
|
|
321
|
+
ax = fig.add_subplot(111)
|
|
322
|
+
ax.plot([1, 2, 3, 4], [95, 97, 96, 99], marker="o")
|
|
323
|
+
ax.set_title("KPI динамика")
|
|
324
|
+
|
|
325
|
+
template = Template("weekly.md", assets_dir="weekly_assets")
|
|
326
|
+
template.data(["teams", ["Backend", "Frontend", "QA"]])
|
|
327
|
+
template.data(["kpi", [
|
|
328
|
+
{"METRIC": "Coverage", "VALUE": "82%"},
|
|
329
|
+
{"METRIC": "Incidents", "VALUE": "2"},
|
|
330
|
+
{"METRIC": "Deploys", "VALUE": "14"},
|
|
331
|
+
]])
|
|
332
|
+
template.data(["kpi_chart", fig])
|
|
333
|
+
template.print()
|
|
334
|
+
template.compile("weekly_report.md")
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Сценарий 2: HTML-отчёт для менеджмента
|
|
338
|
+
|
|
339
|
+
Шаблон `management.html`:
|
|
340
|
+
|
|
341
|
+
```html
|
|
342
|
+
<h1>Отчёт менеджмента</h1>
|
|
343
|
+
<h2>Сводная таблица</h2>
|
|
344
|
+
[[TABLE.metrics.HTML]]
|
|
345
|
+
<h2>Визуализация</h2>
|
|
346
|
+
[[MEDIA.chart]]
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Код:
|
|
350
|
+
|
|
351
|
+
```python
|
|
352
|
+
from reporting import Template
|
|
353
|
+
from PIL import Image
|
|
354
|
+
|
|
355
|
+
logo = Image.new("RGB", (160, 60), "navy")
|
|
356
|
+
|
|
357
|
+
template = Template("management.html", assets_dir="html_assets")
|
|
358
|
+
template.data(["metrics", [
|
|
359
|
+
{"NAME": "Revenue", "VALUE": "1.2M"},
|
|
360
|
+
{"NAME": "Cost", "VALUE": "0.7M"},
|
|
361
|
+
]])
|
|
362
|
+
template.data(["chart", logo])
|
|
363
|
+
template.compile("management_report.html")
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Сценарий 3: много отчётов в цикле по проектам
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
from pathlib import Path
|
|
370
|
+
from reporting import Template
|
|
371
|
+
|
|
372
|
+
projects = {
|
|
373
|
+
"alpha": {"issues": 14, "coverage": "79%"},
|
|
374
|
+
"beta": {"issues": 4, "coverage": "88%"},
|
|
375
|
+
"gamma": {"issues": 0, "coverage": "93%"},
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for project_name, data in projects.items():
|
|
379
|
+
t = Template("project_template.md", assets_dir=Path("assets") / project_name)
|
|
380
|
+
t.data(["project_name", project_name])
|
|
381
|
+
t.data(["metrics", [{"KEY": "issues", "VALUE": data["issues"]}, {"KEY": "coverage", "VALUE": data["coverage"]}]])
|
|
382
|
+
t.compile(Path("reports") / f"{project_name}.md")
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Сценарий 4: пакетная генерация + предпросмотр только при ошибках
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
from reporting import Template, TemplateError
|
|
389
|
+
|
|
390
|
+
def render_safe(template_path: str, out_path: str, context: dict):
|
|
391
|
+
t = Template(template_path)
|
|
392
|
+
for k, v in context.items():
|
|
393
|
+
t.data([k, v])
|
|
394
|
+
try:
|
|
395
|
+
t.compile(out_path)
|
|
396
|
+
except TemplateError:
|
|
397
|
+
t.print()
|
|
398
|
+
raise
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Media: PIL и matplotlib
|
|
404
|
+
|
|
405
|
+
### PIL.Image
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
from PIL import Image
|
|
409
|
+
from reporting import Template
|
|
410
|
+
|
|
411
|
+
img = Image.new("RGB", (120, 40), "steelblue")
|
|
412
|
+
t = Template("template.md", assets_dir="assets")
|
|
413
|
+
t.data(["logo", img])
|
|
414
|
+
t.compile("report.md")
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### matplotlib.figure.Figure
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
import matplotlib.pyplot as plt
|
|
421
|
+
from reporting import Template
|
|
422
|
+
|
|
423
|
+
fig = plt.figure()
|
|
424
|
+
ax = fig.add_subplot(111)
|
|
425
|
+
ax.bar(["A", "B", "C"], [3, 7, 5])
|
|
426
|
+
|
|
427
|
+
t = Template("template.md")
|
|
428
|
+
t.data(["chart", fig])
|
|
429
|
+
t.compile("report.md")
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Управление вставкой
|
|
433
|
+
|
|
434
|
+
```text
|
|
435
|
+
[[MEDIA.chart]] # авто (md/html)
|
|
436
|
+
[[MEDIA.chart.PATH]] # только путь
|
|
437
|
+
[[MEDIA.chart.MD]] # строго markdown
|
|
438
|
+
[[MEDIA.chart.HTML]] # строго html
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## CLI
|
|
444
|
+
|
|
445
|
+
### Компиляция в файл
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
relator --template template.md --context context.json --output report.md
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Только печать
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
relator --template template.md --context context.json --print
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Пример context.json
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{
|
|
461
|
+
"name": [
|
|
462
|
+
{"USER": "Ann", "SCORE": 10},
|
|
463
|
+
{"USER": "Bob", "SCORE": 20}
|
|
464
|
+
],
|
|
465
|
+
"names": ["Ann", "Bob", "Chris"]
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Ошибки и отладка
|
|
472
|
+
|
|
473
|
+
### `TemplateError: Unknown placeholder root ...`
|
|
474
|
+
|
|
475
|
+
Причина:
|
|
476
|
+
|
|
477
|
+
- в шаблоне используется переменная, которую не добавили через `data`.
|
|
478
|
+
|
|
479
|
+
Решение:
|
|
480
|
+
|
|
481
|
+
- проверить имя переменной и регистр.
|
|
482
|
+
|
|
483
|
+
### `CELL.* is available only inside %%len=VAR%%`
|
|
484
|
+
|
|
485
|
+
Причина:
|
|
486
|
+
|
|
487
|
+
- `CELL` использован вне цикла.
|
|
488
|
+
|
|
489
|
+
Решение:
|
|
490
|
+
|
|
491
|
+
- перенести плейсхолдер в тело `%%len=...%%`.
|
|
492
|
+
|
|
493
|
+
### `VAR.VALUES requires %%len=VAR%% context`
|
|
494
|
+
|
|
495
|
+
Причина:
|
|
496
|
+
|
|
497
|
+
- `[[VAR.VALUES]]` использован вне своего цикла.
|
|
498
|
+
|
|
499
|
+
Решение:
|
|
500
|
+
|
|
501
|
+
- использовать `[[TABLE.VAR]]` или обернуть в `%%len=VAR%%`.
|
|
502
|
+
|
|
503
|
+
---
|
|
504
|
+
|
|
505
|
+
## FAQ
|
|
506
|
+
|
|
507
|
+
### Можно ли использовать lowercase имена переменных?
|
|
508
|
+
|
|
509
|
+
Да. Например `name`, `rows`, `items`.
|
|
510
|
+
|
|
511
|
+
### Почему служебные плейсхолдеры uppercase?
|
|
512
|
+
|
|
513
|
+
Чтобы отделять пользовательские данные от системных токенов.
|
|
514
|
+
|
|
515
|
+
### Можно ли использовать библиотеку без Rich?
|
|
516
|
+
|
|
517
|
+
Да, но метод `print()` требует `rich`.
|
|
518
|
+
|
|
519
|
+
### Можно ли рендерить только в строку без файла?
|
|
520
|
+
|
|
521
|
+
Да, через `template.render()`.
|
|
522
|
+
|
|
523
|
+
### Поддерживается ли Windows?
|
|
524
|
+
|
|
525
|
+
Да, библиотека использует `pathlib` и UTF-8.
|
|
526
|
+
|
|
527
|
+
---
|
|
528
|
+
|
|
529
|
+
## Рекомендации по архитектуре
|
|
530
|
+
|
|
531
|
+
1. Храните шаблоны в `templates/`.
|
|
532
|
+
2. Храните generated отчёты в `reports/`.
|
|
533
|
+
3. Для медиа используйте отдельную папку `assets/`.
|
|
534
|
+
4. Не смешивайте бизнес-логику и шаблонную логику.
|
|
535
|
+
5. Добавьте golden-тесты для ключевых шаблонов.
|
|
536
|
+
|
|
537
|
+
Пример структуры:
|
|
538
|
+
|
|
539
|
+
```text
|
|
540
|
+
project/
|
|
541
|
+
templates/
|
|
542
|
+
weekly.md
|
|
543
|
+
management.html
|
|
544
|
+
reports/
|
|
545
|
+
assets/
|
|
546
|
+
scripts/
|
|
547
|
+
build_reports.py
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Кроссплатформенность
|
|
553
|
+
|
|
554
|
+
- операции с путями через `pathlib.Path`
|
|
555
|
+
- чтение/запись через UTF-8
|
|
556
|
+
- CI-матрица: Linux, macOS, Windows
|
|
557
|
+
- тестовая матрица Python: 3.9–3.13
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## Публикация и релиз
|
|
562
|
+
|
|
563
|
+
### Локальная сборка
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
cd reporting
|
|
567
|
+
python -m pip install --upgrade build
|
|
568
|
+
python -m build
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Публикация в TestPyPI
|
|
572
|
+
|
|
573
|
+
```bash
|
|
574
|
+
python -m pip install --upgrade twine
|
|
575
|
+
python -m twine upload --repository testpypi dist/*
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Публикация в PyPI
|
|
579
|
+
|
|
580
|
+
```bash
|
|
581
|
+
python -m twine upload dist/*
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Проверка установки (smoke test)
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
python -m pip install "git+https://github.com/threenebula23/Reporting.git"
|
|
588
|
+
python -c "from reporting import Template; print(Template)"
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## Минимальный чеклист перед релизом
|
|
594
|
+
|
|
595
|
+
- [ ] Все тесты зелёные
|
|
596
|
+
- [ ] README актуален
|
|
597
|
+
- [ ] Версия обновлена
|
|
598
|
+
- [ ] CHANGELOG заполнен
|
|
599
|
+
- [ ] Пакет собирается (`sdist` + `wheel`)
|
|
600
|
+
- [ ] Проверена установка через `pip`
|
|
601
|
+
- [ ] Проверена установка через `uv`
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## Лицензия
|
|
606
|
+
|
|
607
|
+
MIT.
|
|
608
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
relator-0.1.0.dist-info/licenses/LICENSE,sha256=b9T0kEVk60_9TWL3dlEZfRoRKfGQX_VSIZz3Wuw8S3k,1078
|
|
2
|
+
reporting/__init__.py,sha256=pbXkLtap_5V-oI2_-HOzMYqSFg2HKW51B1FaZbYtmt4,190
|
|
3
|
+
reporting/builtins.py,sha256=eyUu7D5JHvIf4918TNbwt5l7MHFxUzODHTlbOquxGUU,7832
|
|
4
|
+
reporting/cli.py,sha256=lbXraIAYiPZJVFymN76w9r28lwqVnMDPr83RN_Qjhxs,1532
|
|
5
|
+
reporting/engine.py,sha256=UrHPEey97MbS3pQ4WvH1PExGmnRI-S-rLKdqZTF7B_s,5242
|
|
6
|
+
reporting/errors.py,sha256=dW4QcYHqblpD9Wdgx89RH1_kTHcq5hsovUtQrn_xvcw,298
|
|
7
|
+
reporting/media.py,sha256=6A_US84mCcYiovU-PEGL7nyoX6-TLCetWQ5FHlq9sBw,3155
|
|
8
|
+
reporting/parser.py,sha256=3S6POZs__-QvhXF9dk6KXjfpnMmmJ9413o-3aJ3aQQg,2057
|
|
9
|
+
reporting/resolver.py,sha256=JZxczHi2Tpg0HdmuU1DviP_1PEB0X9zsr3RviVwV0N4,1796
|
|
10
|
+
reporting/template.py,sha256=XMheByNSGKCSJQAgknlPdpXAq553a-mW5u6zOeIvB7k,3750
|
|
11
|
+
relator-0.1.0.dist-info/METADATA,sha256=lzEP4t_Yu2OhSNf_3KF2t3ccVXjy6-93PJKaatj9xgQ,13844
|
|
12
|
+
relator-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
13
|
+
relator-0.1.0.dist-info/entry_points.txt,sha256=j7vxAyLr2h-8GtDunCChkcxewhNw4tAqfnfpS687EcM,47
|
|
14
|
+
relator-0.1.0.dist-info/top_level.txt,sha256=4KHGYNPUQIGgNtRdbExkO0ueGRwX45dWfTannK_mf1c,10
|
|
15
|
+
relator-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 reporting maintainers
|
|
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
|
+
reporting
|