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.
- confluence_md_exporter-1.0.0/.env.example +18 -0
- confluence_md_exporter-1.0.0/.gitignore +41 -0
- confluence_md_exporter-1.0.0/.python-version +1 -0
- confluence_md_exporter-1.0.0/CHANGELOG.md +20 -0
- confluence_md_exporter-1.0.0/LICENSE +21 -0
- confluence_md_exporter-1.0.0/PKG-INFO +180 -0
- confluence_md_exporter-1.0.0/README.md +156 -0
- confluence_md_exporter-1.0.0/input/urls.txt.example +3 -0
- confluence_md_exporter-1.0.0/pyproject.toml +63 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/__init__.py +8 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/assets.py +70 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/cli.py +157 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/client.py +344 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/disk_skip.py +103 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/flow.py +521 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/layout.py +178 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/output.py +265 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/settings.py +185 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/transform.py +630 -0
- confluence_md_exporter-1.0.0/src/confluence_md_exporter/url_resolver.py +290 -0
- confluence_md_exporter-1.0.0/tests/fixtures/kitchen_sink.xml +62 -0
- confluence_md_exporter-1.0.0/tests/test_api_adapter.py +169 -0
- confluence_md_exporter-1.0.0/tests/test_attachment_download.py +134 -0
- confluence_md_exporter-1.0.0/tests/test_bronze_and_frontmatter.py +118 -0
- confluence_md_exporter-1.0.0/tests/test_diff_export.py +201 -0
- confluence_md_exporter-1.0.0/tests/test_disk_skip.py +321 -0
- confluence_md_exporter-1.0.0/tests/test_edge_cases.py +104 -0
- confluence_md_exporter-1.0.0/tests/test_exit_codes.py +333 -0
- confluence_md_exporter-1.0.0/tests/test_fixture_pipeline.py +358 -0
- confluence_md_exporter-1.0.0/tests/test_flow_isolation.py +95 -0
- confluence_md_exporter-1.0.0/tests/test_http_retry.py +80 -0
- confluence_md_exporter-1.0.0/tests/test_internal_links.py +81 -0
- confluence_md_exporter-1.0.0/tests/test_layout_paths.py +25 -0
- confluence_md_exporter-1.0.0/tests/test_macro_handlers.py +226 -0
- confluence_md_exporter-1.0.0/tests/test_manifest_and_report.py +125 -0
- confluence_md_exporter-1.0.0/tests/test_media_links.py +126 -0
- confluence_md_exporter-1.0.0/tests/test_prefect_artifacts.py +137 -0
- confluence_md_exporter-1.0.0/tests/test_safe_filename.py +39 -0
- confluence_md_exporter-1.0.0/tests/test_settings.py +185 -0
- confluence_md_exporter-1.0.0/tests/test_skeleton.py +15 -0
- confluence_md_exporter-1.0.0/tests/test_slug.py +44 -0
- confluence_md_exporter-1.0.0/tests/test_unknown_macro_fallback.py +54 -0
- 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,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
|
+
)
|