confluence-md-exporter 1.0.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.
Files changed (43) hide show
  1. confluence_md_exporter-1.0.0/.env.example +18 -0
  2. confluence_md_exporter-1.0.0/.gitignore +41 -0
  3. confluence_md_exporter-1.0.0/.python-version +1 -0
  4. confluence_md_exporter-1.0.0/CHANGELOG.md +20 -0
  5. confluence_md_exporter-1.0.0/LICENSE +21 -0
  6. confluence_md_exporter-1.0.0/PKG-INFO +180 -0
  7. confluence_md_exporter-1.0.0/README.md +156 -0
  8. confluence_md_exporter-1.0.0/input/urls.txt.example +3 -0
  9. confluence_md_exporter-1.0.0/pyproject.toml +63 -0
  10. confluence_md_exporter-1.0.0/src/confluence_md_exporter/__init__.py +8 -0
  11. confluence_md_exporter-1.0.0/src/confluence_md_exporter/assets.py +70 -0
  12. confluence_md_exporter-1.0.0/src/confluence_md_exporter/cli.py +157 -0
  13. confluence_md_exporter-1.0.0/src/confluence_md_exporter/client.py +344 -0
  14. confluence_md_exporter-1.0.0/src/confluence_md_exporter/disk_skip.py +103 -0
  15. confluence_md_exporter-1.0.0/src/confluence_md_exporter/flow.py +521 -0
  16. confluence_md_exporter-1.0.0/src/confluence_md_exporter/layout.py +178 -0
  17. confluence_md_exporter-1.0.0/src/confluence_md_exporter/output.py +265 -0
  18. confluence_md_exporter-1.0.0/src/confluence_md_exporter/settings.py +185 -0
  19. confluence_md_exporter-1.0.0/src/confluence_md_exporter/transform.py +630 -0
  20. confluence_md_exporter-1.0.0/src/confluence_md_exporter/url_resolver.py +290 -0
  21. confluence_md_exporter-1.0.0/tests/fixtures/kitchen_sink.xml +62 -0
  22. confluence_md_exporter-1.0.0/tests/test_api_adapter.py +169 -0
  23. confluence_md_exporter-1.0.0/tests/test_attachment_download.py +134 -0
  24. confluence_md_exporter-1.0.0/tests/test_bronze_and_frontmatter.py +118 -0
  25. confluence_md_exporter-1.0.0/tests/test_diff_export.py +201 -0
  26. confluence_md_exporter-1.0.0/tests/test_disk_skip.py +321 -0
  27. confluence_md_exporter-1.0.0/tests/test_edge_cases.py +104 -0
  28. confluence_md_exporter-1.0.0/tests/test_exit_codes.py +333 -0
  29. confluence_md_exporter-1.0.0/tests/test_fixture_pipeline.py +358 -0
  30. confluence_md_exporter-1.0.0/tests/test_flow_isolation.py +95 -0
  31. confluence_md_exporter-1.0.0/tests/test_http_retry.py +80 -0
  32. confluence_md_exporter-1.0.0/tests/test_internal_links.py +81 -0
  33. confluence_md_exporter-1.0.0/tests/test_layout_paths.py +25 -0
  34. confluence_md_exporter-1.0.0/tests/test_macro_handlers.py +226 -0
  35. confluence_md_exporter-1.0.0/tests/test_manifest_and_report.py +125 -0
  36. confluence_md_exporter-1.0.0/tests/test_media_links.py +126 -0
  37. confluence_md_exporter-1.0.0/tests/test_prefect_artifacts.py +137 -0
  38. confluence_md_exporter-1.0.0/tests/test_safe_filename.py +39 -0
  39. confluence_md_exporter-1.0.0/tests/test_settings.py +185 -0
  40. confluence_md_exporter-1.0.0/tests/test_skeleton.py +15 -0
  41. confluence_md_exporter-1.0.0/tests/test_slug.py +44 -0
  42. confluence_md_exporter-1.0.0/tests/test_unknown_macro_fallback.py +54 -0
  43. confluence_md_exporter-1.0.0/tests/test_url_resolver.py +241 -0
@@ -0,0 +1,18 @@
1
+ # Optional. Prefer CLI flags: --user, --token, --base-url, --input, --output.
2
+ # Do not commit `.env`.
3
+
4
+ # Application base: origin + optional context path. No /wiki, no trailing slash.
5
+ # If empty, inferred from absolute URLs in the input file.
6
+ CONFLUENCE_BASE_URL=
7
+ CONFLUENCE_EDITION=datacenter
8
+ # anonymous | bearer | basic. If empty, inferred from username/token.
9
+ CONFLUENCE_AUTH_TYPE=
10
+ CONFLUENCE_TOKEN=
11
+ CONFLUENCE_USERNAME=
12
+ CONFLUENCE_VERIFY_SSL=true
13
+ CONFLUENCE_TIMEOUT_SECONDS=30
14
+ CONFLUENCE_MAX_RETRIES=3
15
+ EXPORT_OUTPUT_DIR=output
16
+ EXPORT_INPUT_FILE=input/urls.txt
17
+ EXPORT_FORCE_REFRESH=false
18
+ LOG_LEVEL=INFO
@@ -0,0 +1,41 @@
1
+ # Local export dumps
2
+ data/
3
+ output/
4
+
5
+ # Local URL lists (keep the example committed)
6
+ input/*
7
+ !input/urls.txt.example
8
+
9
+ # Credentials (keep `.env.example`)
10
+ .env
11
+ .env.*
12
+ !.env.example
13
+
14
+ # IDE
15
+ .idea/
16
+ .vscode/
17
+
18
+ # Python
19
+ __pycache__/
20
+ *.pyc
21
+ *.pyo
22
+ *.pyd
23
+ .Python
24
+ env/
25
+ venv/
26
+ .venv/
27
+ pip-log.txt
28
+ pip-delete-this-directory.txt
29
+ .tox
30
+ .coverage
31
+ .coverage.*
32
+ .cache
33
+ nosetests.xml
34
+ coverage.xml
35
+ *.cover
36
+ *.log
37
+ .hypothesis
38
+ .pytest_cache/
39
+ *.egg-info/
40
+ dist/
41
+ build/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,20 @@
1
+ # Изменения в сервисе
2
+
3
+ ## Unreleased
4
+
5
+ ## 1.0.0 — 2026-09-15
6
+
7
+ Первый публичный релиз на PyPI.
8
+
9
+ - запуск без `.env`: аноним по умолчанию, `-u`/`-t` для доступа, база из списка URL
10
+ - выгрузка всегда однопоточная, без Prefect; прогресс каждой страницы в консоли; снимок Prefect — тег `history/prefect-orchestration`
11
+ - default `EXPORT_OUTPUT_DIR` — `output/` (`01-configuration.md`, `03-output.md#layout`)
12
+ - 17 именованные HTML-сущности в Storage Format не валят страницу (`05-transform.md#edge-cases`)
13
+ - тест-кейсы живут в `tests/`, не отдельным каталогом в `docs/` (`07-testing.md#test-cases`)
14
+ - процесс: `/init-requirements` при непустом `docs/init/` спрашивает архив, сброс или стоп (`01-init-requirements.md#existing-init`)
15
+ - процесс: тесты слайса сначала красные на текущем коде, затем фиксация; красное не коммитить
16
+ - процесс: в конце реализации явно `spec unchanged`, если `docs/spec/**` не менялся
17
+ - 15 проба идентичности fail-closed: любой не-успех (в т.ч. 404, HTML) — выход `2`
18
+ - 14 явные причины отклонения URL (`invalid_urls` как `{url, reason}`)
19
+ - процесс: фреймворк DeltaFuse (`delta-fuse`), гайд `docs/process/using.md`
20
+ - процесс: сквозной NN inbox, auto-commit шага, таблица Closed в docs/todo/README.md
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 gste
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,180 @@
1
+ Metadata-Version: 2.5
2
+ Name: confluence-md-exporter
3
+ Version: 1.0.0
4
+ Summary: Local read-only export of Confluence Server/Data Center pages to Markdown
5
+ Project-URL: Homepage, https://github.com/gste/confluence-md-exporter
6
+ Project-URL: Repository, https://github.com/gste/confluence-md-exporter
7
+ Project-URL: Issues, https://github.com/gste/confluence-md-exporter/issues
8
+ Project-URL: Changelog, https://github.com/gste/confluence-md-exporter/blob/master/CHANGELOG.md
9
+ Author-email: gste <ghostegor@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: confluence,datacenter,export,markdown,wiki
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Documentation
21
+ Classifier: Topic :: Text Processing :: Markup
22
+ Requires-Python: <3.14,>=3.11
23
+ Description-Content-Type: text/markdown
24
+
25
+ # confluence-md-exporter
26
+
27
+ Локальный read-only конвейер: выгрузка страниц Confluence Server/Data Center → Markdown, медиа-ассеты, diff-сравнение версий и каталог батча. Закон реализации — [спецификация](https://github.com/gste/confluence-md-exporter/blob/master/docs/spec/README.md).
28
+
29
+ ## Установка
30
+
31
+ Требуется Python `>=3.11,<3.14`.
32
+
33
+ ```bash
34
+ pip install confluence-md-exporter
35
+ ```
36
+
37
+ Или через `uv`:
38
+
39
+ ```bash
40
+ uv tool install confluence-md-exporter
41
+ ```
42
+
43
+ Из исходников:
44
+
45
+ ```bash
46
+ uv sync
47
+ ```
48
+
49
+ ## Запуск
50
+
51
+ По умолчанию: вход `input/urls.txt`, выход `output/`, доступ анонимный. База Confluence берётся из абсолютных URL в списке — отдельный `--base-url` не нужен, если в файле есть полные ссылки.
52
+
53
+ Стартовый список ссылок:
54
+
55
+ ```bash
56
+ cp input/urls.txt.example input/urls.txt
57
+ ```
58
+
59
+ Закрытый инстанс (нужны учётные данные):
60
+
61
+ ```bash
62
+ uv run confluence-md-exporter -u USER -t TOKEN
63
+ ```
64
+
65
+ Только PAT (Bearer):
66
+
67
+ ```bash
68
+ uv run confluence-md-exporter -t TOKEN
69
+ ```
70
+
71
+ `.env` не обязателен. Если удобнее держать креды в файле — [`.env.example`](https://github.com/gste/confluence-md-exporter/blob/master/.env.example) и `uv run --env-file .env confluence-md-exporter`. CLI-флаги перекрывают окружение.
72
+
73
+ ---
74
+
75
+ ## Форматы входных ссылок (input/urls.txt)
76
+
77
+ Файл `input/urls.txt` (UTF-8, одна ссылка на строку; строки с `#` и пустые строки игнорируются).
78
+
79
+ Поддерживаются:
80
+ 1. **Прямые страницы по ID**:
81
+ - `https://confluence.example.com/pages/viewpage.action?pageId=123456`
82
+ - `https://confluence.example.com/wiki/spaces/SPACE/pages/123456/Some+Page+Title`
83
+ - `123456` (чистый идентификатор)
84
+ 2. **Страницы по Space и Title**:
85
+ - `https://confluence.example.com/display/SPACE/Page+Title`
86
+ 3. **Diff-страницы (сравнение версий)**:
87
+ - `https://confluence.example.com/pages/diffpagesbyversion.action?pageId=607636678&selectedPageVersions=41&selectedPageVersions=42`
88
+ - `https://confluence.example.com/pages/diffpagesbyversion.action?pageId=607636678&originalVersion=41&revisedVersion=42`
89
+
90
+ *Примечание: неподдерживаемые форматы (tiny-link `/x/...` и т.д.) помечаются в `run_report.json` как `invalid_urls` и не валят батч.*
91
+
92
+ ---
93
+
94
+ ## CLI команды и флаги
95
+
96
+ ```text
97
+ usage: confluence-md-exporter [-h] [--version] [-i INPUT] [-o OUTPUT]
98
+ [-u USER] [-t TOKEN] [--base-url URL] [-r] [-c] [-s]
99
+
100
+ options:
101
+ --version Версия пакета
102
+ -i, --input INPUT Список URL (default: input/urls.txt)
103
+ -o, --output OUTPUT Каталог выгрузки (default: output)
104
+ -u, --user USER Имя пользователя (вместе с -t — HTTP Basic)
105
+ -t, --token TOKEN PAT или пароль
106
+ --base-url URL База инстанса, если её нельзя вывести из списка URL
107
+ -r, --refresh Игнорировать disk-skip
108
+ -c, --clean Очистить каталог выгрузки перед работой
109
+ ```
110
+
111
+ Выгрузка всегда однопоточная: один процесс, страницы строго последовательно, без Prefect и без лишних серверов. В консоли на уровне INFO виден прогресс каждой страницы и итоговая сводка.
112
+
113
+ ---
114
+
115
+ ## Примеры использования
116
+
117
+ ### 1. Обычный запуск
118
+ Выгрузка страниц по списку из `input/urls.txt` (анонимно, база из URL):
119
+ ```bash
120
+ uv run confluence-md-exporter
121
+ ```
122
+
123
+ ### 2. Чистая выгрузка с очисткой папки
124
+ Полная очистка папки выгрузки и выгрузка заново:
125
+ ```bash
126
+ uv run confluence-md-exporter -c
127
+ ```
128
+
129
+ ### 3. Принудительное обновление кэша (Refresh)
130
+ Перескачивание контента даже при совпадении версий на диске:
131
+ ```bash
132
+ uv run confluence-md-exporter -r
133
+ ```
134
+
135
+ ### 4. Указание произвольных входных и выходных путей
136
+ ```bash
137
+ uv run confluence-md-exporter -i ./my_pages.txt -o ./exports/march_release
138
+ ```
139
+
140
+ ### 5. Выгрузка разницы версий (Diff)
141
+ Добавь в файл ссылок URL вида:
142
+ ```text
143
+ https://confluence.example.com/pages/diffpagesbyversion.action?pageId=607636678&selectedPageVersions=41&selectedPageVersions=42
144
+ ```
145
+ И запусти:
146
+ ```bash
147
+ uv run confluence-md-exporter
148
+ ```
149
+ В результате в папке `05_diffs/` будет создан файл `607636678_title_v41_to_v42.md` с YAML-метаданными (авторы версий, даты, статистика `+X/-Y`) и блоком ` ```diff `.
150
+
151
+ ---
152
+
153
+ ## Структура выгрузки (Output)
154
+
155
+ ```text
156
+ output/
157
+ ├── 01_raw/ # Сырой Confluence JSON ответа REST API
158
+ ├── 02_interim/ # Storage XML страницы
159
+ ├── 03_assets/ # Скачанные вложения и картинки (по page_id)
160
+ ├── 04_markdown/ # Итоговый Markdown (*.md) и manifest.json
161
+ ├── 05_diffs/ # Unified diff между версиями страниц (*_v41_to_v42.md)
162
+ └── run_report.json # Сводный отчет о результатах выгрузки батча
163
+ ```
164
+
165
+ ---
166
+
167
+ ## Коды возврата (Exit Codes)
168
+
169
+ - `0` — успех (все страницы обработаны со статусом `ok` или `skipped`);
170
+ - `1` — частичная ошибка (хотя бы одна страница завершилась со статусом `failed`);
171
+ - `2` — критическая ошибка конфигурации / непройденная авторизация / отсутствует входной файл.
172
+
173
+ ---
174
+
175
+ ## Разработка и тестирование
176
+
177
+ Запуск полного набора тестов:
178
+ ```bash
179
+ uv run python -m pytest
180
+ ```
@@ -0,0 +1,156 @@
1
+ # confluence-md-exporter
2
+
3
+ Локальный read-only конвейер: выгрузка страниц Confluence Server/Data Center → Markdown, медиа-ассеты, diff-сравнение версий и каталог батча. Закон реализации — [спецификация](https://github.com/gste/confluence-md-exporter/blob/master/docs/spec/README.md).
4
+
5
+ ## Установка
6
+
7
+ Требуется Python `>=3.11,<3.14`.
8
+
9
+ ```bash
10
+ pip install confluence-md-exporter
11
+ ```
12
+
13
+ Или через `uv`:
14
+
15
+ ```bash
16
+ uv tool install confluence-md-exporter
17
+ ```
18
+
19
+ Из исходников:
20
+
21
+ ```bash
22
+ uv sync
23
+ ```
24
+
25
+ ## Запуск
26
+
27
+ По умолчанию: вход `input/urls.txt`, выход `output/`, доступ анонимный. База Confluence берётся из абсолютных URL в списке — отдельный `--base-url` не нужен, если в файле есть полные ссылки.
28
+
29
+ Стартовый список ссылок:
30
+
31
+ ```bash
32
+ cp input/urls.txt.example input/urls.txt
33
+ ```
34
+
35
+ Закрытый инстанс (нужны учётные данные):
36
+
37
+ ```bash
38
+ uv run confluence-md-exporter -u USER -t TOKEN
39
+ ```
40
+
41
+ Только PAT (Bearer):
42
+
43
+ ```bash
44
+ uv run confluence-md-exporter -t TOKEN
45
+ ```
46
+
47
+ `.env` не обязателен. Если удобнее держать креды в файле — [`.env.example`](https://github.com/gste/confluence-md-exporter/blob/master/.env.example) и `uv run --env-file .env confluence-md-exporter`. CLI-флаги перекрывают окружение.
48
+
49
+ ---
50
+
51
+ ## Форматы входных ссылок (input/urls.txt)
52
+
53
+ Файл `input/urls.txt` (UTF-8, одна ссылка на строку; строки с `#` и пустые строки игнорируются).
54
+
55
+ Поддерживаются:
56
+ 1. **Прямые страницы по ID**:
57
+ - `https://confluence.example.com/pages/viewpage.action?pageId=123456`
58
+ - `https://confluence.example.com/wiki/spaces/SPACE/pages/123456/Some+Page+Title`
59
+ - `123456` (чистый идентификатор)
60
+ 2. **Страницы по Space и Title**:
61
+ - `https://confluence.example.com/display/SPACE/Page+Title`
62
+ 3. **Diff-страницы (сравнение версий)**:
63
+ - `https://confluence.example.com/pages/diffpagesbyversion.action?pageId=607636678&selectedPageVersions=41&selectedPageVersions=42`
64
+ - `https://confluence.example.com/pages/diffpagesbyversion.action?pageId=607636678&originalVersion=41&revisedVersion=42`
65
+
66
+ *Примечание: неподдерживаемые форматы (tiny-link `/x/...` и т.д.) помечаются в `run_report.json` как `invalid_urls` и не валят батч.*
67
+
68
+ ---
69
+
70
+ ## CLI команды и флаги
71
+
72
+ ```text
73
+ usage: confluence-md-exporter [-h] [--version] [-i INPUT] [-o OUTPUT]
74
+ [-u USER] [-t TOKEN] [--base-url URL] [-r] [-c] [-s]
75
+
76
+ options:
77
+ --version Версия пакета
78
+ -i, --input INPUT Список URL (default: input/urls.txt)
79
+ -o, --output OUTPUT Каталог выгрузки (default: output)
80
+ -u, --user USER Имя пользователя (вместе с -t — HTTP Basic)
81
+ -t, --token TOKEN PAT или пароль
82
+ --base-url URL База инстанса, если её нельзя вывести из списка URL
83
+ -r, --refresh Игнорировать disk-skip
84
+ -c, --clean Очистить каталог выгрузки перед работой
85
+ ```
86
+
87
+ Выгрузка всегда однопоточная: один процесс, страницы строго последовательно, без Prefect и без лишних серверов. В консоли на уровне INFO виден прогресс каждой страницы и итоговая сводка.
88
+
89
+ ---
90
+
91
+ ## Примеры использования
92
+
93
+ ### 1. Обычный запуск
94
+ Выгрузка страниц по списку из `input/urls.txt` (анонимно, база из URL):
95
+ ```bash
96
+ uv run confluence-md-exporter
97
+ ```
98
+
99
+ ### 2. Чистая выгрузка с очисткой папки
100
+ Полная очистка папки выгрузки и выгрузка заново:
101
+ ```bash
102
+ uv run confluence-md-exporter -c
103
+ ```
104
+
105
+ ### 3. Принудительное обновление кэша (Refresh)
106
+ Перескачивание контента даже при совпадении версий на диске:
107
+ ```bash
108
+ uv run confluence-md-exporter -r
109
+ ```
110
+
111
+ ### 4. Указание произвольных входных и выходных путей
112
+ ```bash
113
+ uv run confluence-md-exporter -i ./my_pages.txt -o ./exports/march_release
114
+ ```
115
+
116
+ ### 5. Выгрузка разницы версий (Diff)
117
+ Добавь в файл ссылок URL вида:
118
+ ```text
119
+ https://confluence.example.com/pages/diffpagesbyversion.action?pageId=607636678&selectedPageVersions=41&selectedPageVersions=42
120
+ ```
121
+ И запусти:
122
+ ```bash
123
+ uv run confluence-md-exporter
124
+ ```
125
+ В результате в папке `05_diffs/` будет создан файл `607636678_title_v41_to_v42.md` с YAML-метаданными (авторы версий, даты, статистика `+X/-Y`) и блоком ` ```diff `.
126
+
127
+ ---
128
+
129
+ ## Структура выгрузки (Output)
130
+
131
+ ```text
132
+ output/
133
+ ├── 01_raw/ # Сырой Confluence JSON ответа REST API
134
+ ├── 02_interim/ # Storage XML страницы
135
+ ├── 03_assets/ # Скачанные вложения и картинки (по page_id)
136
+ ├── 04_markdown/ # Итоговый Markdown (*.md) и manifest.json
137
+ ├── 05_diffs/ # Unified diff между версиями страниц (*_v41_to_v42.md)
138
+ └── run_report.json # Сводный отчет о результатах выгрузки батча
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Коды возврата (Exit Codes)
144
+
145
+ - `0` — успех (все страницы обработаны со статусом `ok` или `skipped`);
146
+ - `1` — частичная ошибка (хотя бы одна страница завершилась со статусом `failed`);
147
+ - `2` — критическая ошибка конфигурации / непройденная авторизация / отсутствует входной файл.
148
+
149
+ ---
150
+
151
+ ## Разработка и тестирование
152
+
153
+ Запуск полного набора тестов:
154
+ ```bash
155
+ uv run python -m pytest
156
+ ```
@@ -0,0 +1,3 @@
1
+ # Copy to input/urls.txt. One URL per line. Lines starting with # are ignored.
2
+ https://confluence.example.com/pages/viewpage.action?pageId=123456
3
+ https://confluence.example.com/display/SPACE/Page+Title
@@ -0,0 +1,63 @@
1
+ [project]
2
+ name = "confluence-md-exporter"
3
+ version = "1.0.0"
4
+ description = "Local read-only export of Confluence Server/Data Center pages to Markdown"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.11,<3.14"
8
+ authors = [
9
+ { name = "gste", email = "ghostegor@gmail.com" },
10
+ ]
11
+ keywords = [
12
+ "confluence",
13
+ "markdown",
14
+ "export",
15
+ "datacenter",
16
+ "wiki",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Environment :: Console",
21
+ "Intended Audience :: Developers",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Topic :: Documentation",
27
+ "Topic :: Text Processing :: Markup",
28
+ ]
29
+ dependencies = []
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/gste/confluence-md-exporter"
33
+ Repository = "https://github.com/gste/confluence-md-exporter"
34
+ Issues = "https://github.com/gste/confluence-md-exporter/issues"
35
+ Changelog = "https://github.com/gste/confluence-md-exporter/blob/master/CHANGELOG.md"
36
+
37
+ [project.scripts]
38
+ confluence-md-exporter = "confluence_md_exporter.cli:entry"
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.wheel]
45
+ packages = ["src/confluence_md_exporter"]
46
+
47
+ [tool.hatch.build.targets.sdist]
48
+ exclude = [
49
+ "/.cursor",
50
+ "/.github",
51
+ "/docs",
52
+ "/AGENTS.md",
53
+ "/TODO.md",
54
+ "/uv.lock",
55
+ ]
56
+
57
+ [dependency-groups]
58
+ dev = [
59
+ "pytest>=8",
60
+ ]
61
+
62
+ [tool.pytest.ini_options]
63
+ testpaths = ["tests"]
@@ -0,0 +1,8 @@
1
+ """Local read-only Confluence Server/Data Center to Markdown exporter."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("confluence-md-exporter")
7
+ except PackageNotFoundError: # pragma: no cover - source tree without install
8
+ __version__ = "1.0.0"
@@ -0,0 +1,70 @@
1
+ """Download page attachments over REST and write them under 03_assets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from dataclasses import dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any, Mapping, Sequence
9
+
10
+ from confluence_md_exporter.client import ConfluenceClient
11
+ from confluence_md_exporter.layout import asset_file_path, unique_safe_filenames
12
+ from confluence_md_exporter.output import write_asset_sidecar
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def missing_attachment_placeholder(original: str) -> str:
18
+ return f"[missing-attachment: {original}]"
19
+
20
+
21
+ @dataclass
22
+ class AssetSyncResult:
23
+ original_to_safe: dict[str, str]
24
+ missing_placeholders: list[str] = field(default_factory=list)
25
+ warnings: list[str] = field(default_factory=list)
26
+ sidecar_path: Path | None = None
27
+
28
+ @property
29
+ def page_failed(self) -> bool:
30
+ return False
31
+
32
+
33
+ def sync_page_assets(
34
+ client: ConfluenceClient,
35
+ output_dir: Path,
36
+ page_id: str,
37
+ attachments: Sequence[Mapping[str, Any]],
38
+ ) -> AssetSyncResult:
39
+ originals = [str(item.get("title") or "") for item in attachments]
40
+ names = unique_safe_filenames(originals)
41
+ downloaded: dict[str, str] = {}
42
+ missing: list[str] = []
43
+ warnings: list[str] = []
44
+
45
+ for item in attachments:
46
+ original = str(item.get("title") or "")
47
+ safe = names[original]
48
+ url = client.attachment_download_url(item)
49
+ if "/download/attachments/" in url:
50
+ raise RuntimeError("UI attachment path must not be used as the primary download")
51
+ response = client.download(url)
52
+ if response.status >= 400:
53
+ placeholder = missing_attachment_placeholder(original)
54
+ warning = f"attachment not downloaded: {original}"
55
+ missing.append(placeholder)
56
+ warnings.append(warning)
57
+ logger.warning(warning)
58
+ continue
59
+ path = output_dir / asset_file_path(page_id, safe)
60
+ path.parent.mkdir(parents=True, exist_ok=True)
61
+ path.write_bytes(response.body)
62
+ downloaded[original] = safe
63
+
64
+ sidecar = write_asset_sidecar(output_dir, page_id, downloaded)
65
+ return AssetSyncResult(
66
+ original_to_safe=downloaded,
67
+ missing_placeholders=missing,
68
+ warnings=warnings,
69
+ sidecar_path=sidecar,
70
+ )