Metadata-Version: 2.4
Name: docfill
Version: 0.2.5
Summary: Library: fill Excel (.xlsx) templates with Jinja2 and optional row expansion from a context dict
Author: docfill contributors
License-Expression: MIT
Project-URL: Homepage, https://pypi.org/project/docfill/
Keywords: excel,xlsx,openpyxl,jinja2,template,report
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openpyxl>=3.1.0
Requires-Dist: jinja2>=3.1.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Provides-Extra: publish
Requires-Dist: build>=1.0.0; extra == "publish"
Requires-Dist: twine>=5.0.0; extra == "publish"
Dynamic: license-file

# docfill

Заполнение шаблонов Excel (`.xlsx`) через **Jinja2** и опциональное размножение **строки-шаблона** по списку из JSON.

## Установка

```bash
pip install -e .
pip install -e ".[dev]"   # опционально: pytest
```

Тесты без pytest (stdlib):

```bash
set PYTHONPATH=src
python -m unittest discover -s tests -p "test_*.py" -v
```

## Шаблон и контекст

- **Все листы** книги обрабатываются подряд **одним** контекстом; лист без размеченной строки под список и без блоков `{% for %}` получает только глобальный Jinja (если в контексте есть непустой `items`, авторазвёртывание на таком листе **пропускается**, чтобы не падать на пустых листах).
- **Несколько таблиц на листе:** для каждой пары `{% for var in listKey %}` … `{% endfor %}` в контексте должен быть список `listKey` (например `items`, `items2`). Блоки обрабатываются **снизу вверх**, чтобы вставка строк не сдвигала нижележащие шаблоны. Сдвиг формул под верхней таблицей **не затрагивает** строки нижней (до начала её блока `for`).
- Если на листе **нет** тегов `{% for %}`, но в контексте есть непустой список по ключу **`items`**, строка-шаблон ищется автоматически: сначала строка с `{{ item.`, иначе строка с **наибольшим** числом ячеек с `{{` (при равенстве — нижняя). Если максимум **1** ячейка и таких строк **несколько** (например шапка «Customer» / «Date» в разных строках), авторазвёртывание **не делается** — только глобальный Jinja; таблицу пометьте `{% for %}` или `{{ item.... }}` на строке данных.
- Для каждого элемента списка контекст строки = глобальный контекст без этого списка + поля элемента (плоское объединение) + `item` (тот же объект). Работают и `{{ name }}`, и `{{ item.name }}`.
- Поддерживается шаблон с маркерами цикла в Excel: в строке-шаблоне можно использовать `{% for item in items %}...`, а в следующей строке `{% endfor %}`. Эти служебные маркеры удаляются в результате. В ячейках строки данных доступен объект **`loop`** (как в Jinja): `loop.index`, `loop.index0`, `loop.first` / `loop.last`, `loop.length`, `loop.revindex` / `loop.revindex0`, `loop.previtem` / `loop.nextitem`.
- Формулы ниже блока цикла (например строка `Итого`) автоматически сдвигаются по относительным ссылкам на число добавленных строк.
- Значения вида `2`, `10`, `3.5` после Jinja записываются как **числа**, а не текст — иначе `SUM` и другие формулы дают 0.
- После размножения строк выполняется второй проход по листу: подставляются глобальные плейсхолдеры вне строки-шаблона (например `{{ customer_name }}` в шапке).
- Формулы в строке-шаблоне копируются со сдвигом относительных ссылок (`openpyxl` `Translator`), как при протягивании в Excel. **Накопительный итог** в колонке (running total): в шаблоне первая строка данных, например строка 2, задайте `=SUM($E$2:E2)` или локально `=СУММ($E$2:E2)` — начало диапазона с **двумя** долларами у строки (`$E$2`). Вариант `=SUM($E2:E2)` после копирования вниз даст `=SUM($E3:E3)` и посчитает только текущую строку; это не ошибка листа или docfill, а формула шаблона.
- Ячейки-**формулы** Jinja не обрабатывает (только обычный текст в ячейках).

## Библиотека

Установка: `pip install .` из корня репозитория или `pip install -e .` для разработки. Пакет следует [PEP 517](https://peps.python.org/pep-0517/) (`python -m build` при наличии `build`).

```python
from docfill import fill_workbook

ctx = {"title": "Отчёт", "items": [{"name": "A", "qty": 1}]}
fill_workbook("template.xlsx", "out.xlsx", ctx)
```

Контекст всегда передаётся **словарём** (или иным `Mapping`). Прочитать JSON из файла — на стороне вызывающего кода, например `json.loads(Path("context.json").read_text(encoding="utf-8"))`.

## Публикация на PyPI

1. Зарегистрируйте аккаунт на [pypi.org](https://pypi.org). Загрузка **с GitHub Actions** идёт через [trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC): в настройках проекта на PyPI добавьте publisher с вашим `owner/repo`, workflow **`publish.yml`**, окружение **`pypi`**. Отдельный API token для CI не нужен. Для ручной загрузки через `twine` можно создать [API token](https://pypi.org/manage/account/token/).
2. Имя пакета **`docfill`** должно быть свободно на PyPI; при занятости смените `name` в `pyproject.toml`.
3. При желании добавьте в `pyproject.toml` в `[project.urls]` поля **Repository** и **Issues** (рядом с `Homepage`).
4. **Версия** в `pyproject.toml` должна совпадать с релизом: при каждом GitHub Release поднимайте `version` иначе PyPI отклонит повтор той же версии.
5. Сборка и проверка дистрибутивов вручную:

```bash
pip install -e ".[publish]"
python -m build
twine check dist/*
```

6. Загрузка вручную (тестовый индекс [TestPyPI](https://test.pypi.org) или основной PyPI):

```bash
twine upload dist/*
```

Для TestPyPI: `twine upload --repository testpypi dist/*` и установка проверки: `pip install -i https://test.pypi.org/simple/ docfill`.

Лицензия: **MIT** (файл `LICENSE`). Версию перед релизом поднимайте в `pyproject.toml` и синхронно в запасном значении в `docfill/__init__.py` (`PackageNotFoundError`).

## Ограничения

- Нет пересчёта формул при сохранении — результат пересчитает Excel при открытии.
- Объединённые ячейки в строке-шаблона не поддерживаются.
- Rich text в ячейке не разбирается как шаблон.
