docspan 0.1.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.
- docspan-0.1.0/.github/workflows/ci.yml +34 -0
- docspan-0.1.0/.github/workflows/publish.yml +58 -0
- docspan-0.1.0/.github/workflows/release-please.yml +18 -0
- docspan-0.1.0/.gitignore +67 -0
- docspan-0.1.0/.release-please-manifest.json +3 -0
- docspan-0.1.0/CHANGELOG.md +35 -0
- docspan-0.1.0/CONTRIBUTING.md +148 -0
- docspan-0.1.0/PKG-INFO +273 -0
- docspan-0.1.0/Procfile +1 -0
- docspan-0.1.0/RAILWAY_SETUP.md +67 -0
- docspan-0.1.0/README.md +224 -0
- docspan-0.1.0/docs/backends/confluence.md +67 -0
- docspan-0.1.0/docs/backends/google-docs.md +62 -0
- docspan-0.1.0/docs/commands.md +204 -0
- docspan-0.1.0/docs/configuration.md +89 -0
- docspan-0.1.0/docs/contributing.md +147 -0
- docspan-0.1.0/docs/index.md +63 -0
- docspan-0.1.0/docs/install.md +107 -0
- docspan-0.1.0/docspan.yaml.example +32 -0
- docspan-0.1.0/markgate.yaml.example +25 -0
- docspan-0.1.0/mkdocs.yml +40 -0
- docspan-0.1.0/modules/__init__.py +5 -0
- docspan-0.1.0/modules/auth.py +134 -0
- docspan-0.1.0/modules/conflict_handler.py +163 -0
- docspan-0.1.0/modules/converter.py +470 -0
- docspan-0.1.0/modules/gdrive_client.py +616 -0
- docspan-0.1.0/modules/sync_engine.py +310 -0
- docspan-0.1.0/project_plans/docspan-release/implementation/adversarial-review.md +130 -0
- docspan-0.1.0/project_plans/docspan-release/implementation/plan.md +878 -0
- docspan-0.1.0/project_plans/docspan-release/implementation/release-checklist.md +45 -0
- docspan-0.1.0/project_plans/docspan-release/implementation/validation.md +195 -0
- docspan-0.1.0/project_plans/docspan-release/requirements.md +143 -0
- docspan-0.1.0/project_plans/docspan-release/research/architecture.md +324 -0
- docspan-0.1.0/project_plans/docspan-release/research/features.md +292 -0
- docspan-0.1.0/project_plans/docspan-release/research/google-docs-push.md +181 -0
- docspan-0.1.0/project_plans/docspan-release/research/pitfalls.md +203 -0
- docspan-0.1.0/project_plans/docspan-release/research/stack.md +220 -0
- docspan-0.1.0/project_plans/markgate-sync/decisions/ADR-001-merge3-dependency.md +33 -0
- docspan-0.1.0/project_plans/markgate-sync/decisions/ADR-002-base-content-sidecar-store.md +32 -0
- docspan-0.1.0/project_plans/markgate-sync/implementation/adversarial-review.md +58 -0
- docspan-0.1.0/project_plans/markgate-sync/implementation/plan.md +717 -0
- docspan-0.1.0/project_plans/markgate-sync/implementation/validation.md +253 -0
- docspan-0.1.0/project_plans/markgate-sync/requirements.md +200 -0
- docspan-0.1.0/project_plans/markgate-sync/research/architecture.md +186 -0
- docspan-0.1.0/project_plans/markgate-sync/research/features.md +169 -0
- docspan-0.1.0/project_plans/markgate-sync/research/pitfalls.md +145 -0
- docspan-0.1.0/project_plans/markgate-sync/research/stack.md +86 -0
- docspan-0.1.0/pyproject.toml +96 -0
- docspan-0.1.0/release-please-config.json +9 -0
- docspan-0.1.0/requirements.txt +8 -0
- docspan-0.1.0/runtime.txt +1 -0
- docspan-0.1.0/src/docspan/__init__.py +3 -0
- docspan-0.1.0/src/docspan/__main__.py +0 -0
- docspan-0.1.0/src/docspan/backends/__init__.py +19 -0
- docspan-0.1.0/src/docspan/backends/base.py +85 -0
- docspan-0.1.0/src/docspan/backends/confluence/__init__.py +0 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/__init__.py +14 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/comparator.py +427 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/converter.py +119 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/converters.py +1449 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/interfaces.py +191 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/nodes.py +2085 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/parser.py +400 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/validators.py +161 -0
- docspan-0.1.0/src/docspan/backends/confluence/adf/visitors.py +495 -0
- docspan-0.1.0/src/docspan/backends/confluence/backend.py +227 -0
- docspan-0.1.0/src/docspan/backends/confluence/client.py +44 -0
- docspan-0.1.0/src/docspan/backends/confluence/config/__init__.py +21 -0
- docspan-0.1.0/src/docspan/backends/confluence/config/loader.py +107 -0
- docspan-0.1.0/src/docspan/backends/confluence/config/models.py +167 -0
- docspan-0.1.0/src/docspan/backends/confluence/config/validation.py +297 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/__init__.py +22 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/ast.py +819 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/__init__.py +5 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/frontmatter.py +80 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/mermaid.py +64 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/wikilinks.py +179 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/inline_parser.py +495 -0
- docspan-0.1.0/src/docspan/backends/confluence/markdown/parser.py +1006 -0
- docspan-0.1.0/src/docspan/backends/confluence/models/__init__.py +18 -0
- docspan-0.1.0/src/docspan/backends/confluence/models/markdown_file.py +402 -0
- docspan-0.1.0/src/docspan/backends/confluence/models/page.py +212 -0
- docspan-0.1.0/src/docspan/backends/confluence/models/path_utils.py +34 -0
- docspan-0.1.0/src/docspan/backends/confluence/models/results.py +28 -0
- docspan-0.1.0/src/docspan/backends/confluence/models/sync_status.py +382 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/__init__.py +0 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/__init__.py +40 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/attachment_client.py +147 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/base_client.py +420 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/client.py +376 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/comment_client.py +682 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/crawler.py +587 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/label_client.py +130 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/page_client.py +1288 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/space_client.py +179 -0
- docspan-0.1.0/src/docspan/backends/confluence/services/confluence/url_parser.py +106 -0
- docspan-0.1.0/src/docspan/backends/google_docs/__init__.py +0 -0
- docspan-0.1.0/src/docspan/backends/google_docs/auth.py +143 -0
- docspan-0.1.0/src/docspan/backends/google_docs/backend.py +140 -0
- docspan-0.1.0/src/docspan/backends/google_docs/client.py +665 -0
- docspan-0.1.0/src/docspan/backends/google_docs/converter.py +471 -0
- docspan-0.1.0/src/docspan/backends/google_docs/docs_request_builder.py +232 -0
- docspan-0.1.0/src/docspan/backends/google_docs/docs_structure_parser.py +120 -0
- docspan-0.1.0/src/docspan/backends/google_docs/markdown_to_paragraph_parser.py +145 -0
- docspan-0.1.0/src/docspan/cli/__init__.py +0 -0
- docspan-0.1.0/src/docspan/cli/main.py +408 -0
- docspan-0.1.0/src/docspan/config.py +62 -0
- docspan-0.1.0/src/docspan/core/__init__.py +49 -0
- docspan-0.1.0/src/docspan/core/merge.py +30 -0
- docspan-0.1.0/src/docspan/core/orchestrator.py +332 -0
- docspan-0.1.0/src/docspan/core/paths.py +8 -0
- docspan-0.1.0/src/docspan/core/state.py +53 -0
- docspan-0.1.0/sync.py +368 -0
- docspan-0.1.0/terraform/main.tf +28 -0
- docspan-0.1.0/terraform/variables.tf +17 -0
- docspan-0.1.0/tests/__init__.py +0 -0
- docspan-0.1.0/tests/test_cli.py +415 -0
- docspan-0.1.0/tests/test_config.py +191 -0
- docspan-0.1.0/tests/test_conflict_resolution.py +197 -0
- docspan-0.1.0/tests/test_docs_request_builder.py +130 -0
- docspan-0.1.0/tests/test_docs_structure_parser.py +187 -0
- docspan-0.1.0/tests/test_markdown_to_paragraph_parser.py +113 -0
- docspan-0.1.0/tests/test_merge.py +78 -0
- docspan-0.1.0/tests/test_orchestrator.py +253 -0
- docspan-0.1.0/tests/test_state.py +115 -0
- docspan-0.1.0/uv.lock +2559 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0 # needed for hatch-vcs version detection
|
|
20
|
+
|
|
21
|
+
- name: Install uv
|
|
22
|
+
uses: astral-sh/setup-uv@v4
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
run: uv python install ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --extra dev
|
|
29
|
+
|
|
30
|
+
- name: Lint
|
|
31
|
+
run: uv run ruff check src tests
|
|
32
|
+
|
|
33
|
+
- name: Test
|
|
34
|
+
run: uv run pytest --tb=short -q
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
with:
|
|
13
|
+
fetch-depth: 0 # needed for hatch-vcs version from git tags
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
|
|
18
|
+
- name: Build package
|
|
19
|
+
run: uv build
|
|
20
|
+
|
|
21
|
+
- uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish-testpypi:
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment:
|
|
30
|
+
name: testpypi
|
|
31
|
+
url: https://test.pypi.org/p/docspan
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
41
|
+
with:
|
|
42
|
+
repository-url: https://test.pypi.org/legacy/
|
|
43
|
+
|
|
44
|
+
publish-pypi:
|
|
45
|
+
needs: publish-testpypi
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
environment:
|
|
48
|
+
name: pypi
|
|
49
|
+
url: https://pypi.org/p/docspan
|
|
50
|
+
permissions:
|
|
51
|
+
id-token: write
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/download-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: dist
|
|
56
|
+
path: dist/
|
|
57
|
+
|
|
58
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Release Please
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
pull-requests: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-please:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: googleapis/release-please-action@v4
|
|
16
|
+
with:
|
|
17
|
+
config-file: release-please-config.json
|
|
18
|
+
manifest-file: .release-please-manifest.json
|
docspan-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
env/
|
|
8
|
+
venv/
|
|
9
|
+
ENV/
|
|
10
|
+
build/
|
|
11
|
+
develop-eggs/
|
|
12
|
+
dist/
|
|
13
|
+
downloads/
|
|
14
|
+
eggs/
|
|
15
|
+
.eggs/
|
|
16
|
+
lib/
|
|
17
|
+
lib64/
|
|
18
|
+
parts/
|
|
19
|
+
sdist/
|
|
20
|
+
var/
|
|
21
|
+
wheels/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.installed.cfg
|
|
24
|
+
*.egg
|
|
25
|
+
|
|
26
|
+
# Google API credentials
|
|
27
|
+
credentials.json
|
|
28
|
+
token.json
|
|
29
|
+
*_credentials.json
|
|
30
|
+
account_a_credentials.json
|
|
31
|
+
account_b_credentials.json
|
|
32
|
+
|
|
33
|
+
# Configuration files with sensitive data
|
|
34
|
+
config.yaml
|
|
35
|
+
markgate.yaml
|
|
36
|
+
|
|
37
|
+
# State files
|
|
38
|
+
.sync_state.json
|
|
39
|
+
conflicts.log
|
|
40
|
+
.markgate-state.json
|
|
41
|
+
.markgate-base/
|
|
42
|
+
|
|
43
|
+
# IDE
|
|
44
|
+
.vscode/
|
|
45
|
+
.idea/
|
|
46
|
+
.cursor/
|
|
47
|
+
*.swp
|
|
48
|
+
*.swo
|
|
49
|
+
*~
|
|
50
|
+
|
|
51
|
+
# OS
|
|
52
|
+
.DS_Store
|
|
53
|
+
Thumbs.db
|
|
54
|
+
|
|
55
|
+
# Logs
|
|
56
|
+
*.log
|
|
57
|
+
logs/
|
|
58
|
+
|
|
59
|
+
# MkDocs build output
|
|
60
|
+
site/
|
|
61
|
+
|
|
62
|
+
# Terraform
|
|
63
|
+
.terraform/
|
|
64
|
+
*.tfstate
|
|
65
|
+
*.tfstate.backup
|
|
66
|
+
*.tfvars
|
|
67
|
+
.terraform.lock.hcl
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-06-07
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `docspan push` — push local markdown files to Google Docs or Confluence
|
|
14
|
+
- `docspan pull` — pull remote documents into local markdown files with three-way merge
|
|
15
|
+
- `docspan status` — show current mapping status in a table
|
|
16
|
+
- `docspan auth setup` — interactive authentication setup for `google_docs` and `confluence` backends
|
|
17
|
+
- `docspan conflicts list` — list files with unresolved merge conflicts
|
|
18
|
+
- `docspan conflicts resolve` — resolve merge conflicts with `remote`, `local`, or `merged` strategy
|
|
19
|
+
- Google Docs backend: push and pull via Google Docs API (service account auth)
|
|
20
|
+
- Confluence backend: push and pull via Atlassian REST API (API token auth)
|
|
21
|
+
- Three-way merge for bidirectional sync conflict detection
|
|
22
|
+
- Confluence comment sidecar: pull writes inline and footer comments to `{file}.comments.md`
|
|
23
|
+
- `markgate.yaml` config file format with per-mapping direction control (`push`/`pull`/`both`)
|
|
24
|
+
- Sync state tracking via `.markgate-state.json` and content-addressed base store in `.markgate-base/`
|
|
25
|
+
|
|
26
|
+
### Known Limitations
|
|
27
|
+
- Google Docs: comments on edited paragraphs are destroyed on push (paragraph-level diff; comments on unchanged paragraphs are preserved)
|
|
28
|
+
- Push: no image support — local image files cannot be pushed to Google Docs or Confluence
|
|
29
|
+
- Push: no table support — markdown tables are not rendered in Google Docs
|
|
30
|
+
- Confluence: requires an Atlassian API token; no OAuth flow
|
|
31
|
+
- Confluence: comment sidecar (`{file}.comments.md`) is informational only; comments cannot be pushed back
|
|
32
|
+
- Config file is named `markgate.yaml` (not `docspan.yaml`) and state file is `.markgate-state.json` (not `.docspan-state.json`). These will be renamed in v0.2.0.
|
|
33
|
+
|
|
34
|
+
[Unreleased]: https://github.com/tstapler/docspan/compare/v0.1.0...HEAD
|
|
35
|
+
[0.1.0]: https://github.com/tstapler/docspan/releases/tag/v0.1.0
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Contributing to docspan
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Python 3.9+
|
|
6
|
+
- [uv](https://docs.astral.sh/uv/) — fast Python package manager
|
|
7
|
+
- git
|
|
8
|
+
|
|
9
|
+
## Dev Setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git clone https://github.com/tstapler/docspan
|
|
13
|
+
cd docspan
|
|
14
|
+
uv venv
|
|
15
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
16
|
+
uv pip install -e ".[dev]"
|
|
17
|
+
docspan --help # verify the CLI works
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running Tests
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# All tests
|
|
24
|
+
pytest
|
|
25
|
+
|
|
26
|
+
# With coverage
|
|
27
|
+
pytest --cov=docspan --cov-report=term-missing
|
|
28
|
+
|
|
29
|
+
# Lint
|
|
30
|
+
ruff check src/ tests/
|
|
31
|
+
|
|
32
|
+
# Format
|
|
33
|
+
ruff format src/ tests/
|
|
34
|
+
|
|
35
|
+
# Type check
|
|
36
|
+
mypy src/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Project Structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
src/docspan/
|
|
43
|
+
├── cli/main.py <- Typer commands (push, pull, status, auth, conflicts)
|
|
44
|
+
├── config.py <- MarkgateConfig Pydantic model + YAML loader
|
|
45
|
+
├── backends/
|
|
46
|
+
│ ├── base.py <- Backend ABC (push, pull, auth_setup, validate_config, get_remote_version)
|
|
47
|
+
│ ├── __init__.py <- BACKENDS registry {"google_docs": ..., "confluence": ...}
|
|
48
|
+
│ ├── google_docs/ <- Google Docs backend implementation
|
|
49
|
+
│ └── confluence/ <- Confluence backend implementation
|
|
50
|
+
└── core/
|
|
51
|
+
├── state.py <- SyncState / MappingState (JSON persistence)
|
|
52
|
+
├── orchestrator.py <- Push/pull orchestration (conflict detection, three-way merge)
|
|
53
|
+
├── merge.py <- Three-way text merge
|
|
54
|
+
└── paths.py <- Path constants (.markgate-state.json, etc.)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## How to Add a New Backend
|
|
58
|
+
|
|
59
|
+
### Step 1: Create the backend package
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
src/docspan/backends/myplatform/
|
|
63
|
+
├── __init__.py
|
|
64
|
+
└── backend.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 2: Implement the Backend ABC in `backend.py`
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from docspan.backends.base import Backend, PushResult, PullResult
|
|
71
|
+
from docspan.config import MarkgateConfig
|
|
72
|
+
|
|
73
|
+
class MyPlatformBackend(Backend):
|
|
74
|
+
name = "myplatform" # must match the key used in markgate.yaml
|
|
75
|
+
|
|
76
|
+
def __init__(self, config) -> None:
|
|
77
|
+
self.config = config
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_config(cls, markgate_config: MarkgateConfig) -> "MyPlatformBackend":
|
|
81
|
+
return cls(markgate_config.backends.myplatform)
|
|
82
|
+
|
|
83
|
+
def push(self, local_path: str, doc_id: str, **kwargs) -> PushResult:
|
|
84
|
+
# read local_path, convert to platform format, upload to doc_id
|
|
85
|
+
# return PushResult(status="ok", doc_id=doc_id, url=url)
|
|
86
|
+
# on error: return PushResult(status="error", doc_id=doc_id, message=str(exc))
|
|
87
|
+
...
|
|
88
|
+
|
|
89
|
+
def pull(self, doc_id: str, local_path: str, **kwargs) -> PullResult:
|
|
90
|
+
# fetch from platform, convert to markdown, write to local_path
|
|
91
|
+
# return PullResult(status="ok", doc_id=doc_id, local_path=local_path)
|
|
92
|
+
...
|
|
93
|
+
|
|
94
|
+
def auth_setup(self) -> None:
|
|
95
|
+
# print setup instructions or run interactive prompts
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
def get_remote_version(self, doc_id: str) -> str:
|
|
99
|
+
# return an opaque version token (e.g. revision ID, version number string)
|
|
100
|
+
# used to detect whether the remote has changed since last sync
|
|
101
|
+
...
|
|
102
|
+
|
|
103
|
+
def validate_config(self) -> None:
|
|
104
|
+
# raise ValueError("Missing X. Run: docspan auth setup myplatform") if misconfigured
|
|
105
|
+
...
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Step 3: Add a config model to `config.py`
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
class MyPlatformConfig(BaseModel):
|
|
112
|
+
api_key: Optional[str] = None
|
|
113
|
+
base_url: Optional[str] = None
|
|
114
|
+
|
|
115
|
+
class BackendsConfig(BaseModel):
|
|
116
|
+
google_docs: Optional[GoogleDocsConfig] = None
|
|
117
|
+
confluence: Optional[ConfluenceConfig] = None
|
|
118
|
+
myplatform: Optional[MyPlatformConfig] = None # add this
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Step 4: Register in the `BACKENDS` dict
|
|
122
|
+
|
|
123
|
+
In `src/docspan/backends/__init__.py`:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from docspan.backends.myplatform.backend import MyPlatformBackend
|
|
127
|
+
|
|
128
|
+
BACKENDS: dict[str, type[Backend]] = {
|
|
129
|
+
"google_docs": GoogleDocsBackend,
|
|
130
|
+
"confluence": ConfluenceBackend,
|
|
131
|
+
"myplatform": MyPlatformBackend, # add this
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Then write tests in `tests/backends/test_myplatform.py` and a docs page at `docs/backends/myplatform.md`.
|
|
136
|
+
|
|
137
|
+
## Note about `markgate.yaml`
|
|
138
|
+
|
|
139
|
+
The config file is gitignored by default because it contains API tokens. Add your own `markgate.yaml` locally based on the `markgate.yaml.example` template — it will not be committed.
|
|
140
|
+
|
|
141
|
+
## PR Process
|
|
142
|
+
|
|
143
|
+
1. Fork the repository and create a feature branch from `main`
|
|
144
|
+
2. Ensure all tests pass: `pytest`
|
|
145
|
+
3. Ensure lint passes: `ruff check src/ tests/`
|
|
146
|
+
4. Add tests for any new functionality
|
|
147
|
+
5. Open a PR against `main` — CI runs `pytest`, `ruff`, and `mypy` automatically
|
|
148
|
+
6. At least one review approval is required before merge; keep each PR to one logical change
|
docspan-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: docspan
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Push and pull markdown to Google Docs and Confluence from a single CLI
|
|
5
|
+
Project-URL: Homepage, https://github.com/tstapler/docspan
|
|
6
|
+
Project-URL: Repository, https://github.com/tstapler/docspan
|
|
7
|
+
Project-URL: Issues, https://github.com/tstapler/docspan/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/tstapler/docspan/releases
|
|
9
|
+
Author-email: Tyler Stapler <tystapler@gmail.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: cli,confluence,docs-as-code,google-docs,markdown,sync
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
22
|
+
Classifier: Topic :: Text Processing :: Markup :: Markdown
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: google-api-python-client>=2.108.0
|
|
25
|
+
Requires-Dist: google-auth-httplib2>=0.1.1
|
|
26
|
+
Requires-Dist: google-auth-oauthlib>=1.1.0
|
|
27
|
+
Requires-Dist: google-auth>=2.23.0
|
|
28
|
+
Requires-Dist: httpx>=0.24.0
|
|
29
|
+
Requires-Dist: markdownify>=0.11.6
|
|
30
|
+
Requires-Dist: merge3>=0.0.16
|
|
31
|
+
Requires-Dist: mistune>=3.0
|
|
32
|
+
Requires-Dist: pydantic>=2.0.0
|
|
33
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
34
|
+
Requires-Dist: pyyaml>=6.0
|
|
35
|
+
Requires-Dist: requests>=2.25.0
|
|
36
|
+
Requires-Dist: rich>=13.0.0
|
|
37
|
+
Requires-Dist: typer>=0.9.0
|
|
38
|
+
Provides-Extra: dev
|
|
39
|
+
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
41
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
43
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: types-requests>=2.25.0; extra == 'dev'
|
|
45
|
+
Provides-Extra: docs
|
|
46
|
+
Requires-Dist: mkdocs-material<10.0,>=9.5.0; extra == 'docs'
|
|
47
|
+
Requires-Dist: mkdocs<2.0,>=1.5.0; extra == 'docs'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# docspan
|
|
51
|
+
|
|
52
|
+
[](https://pypi.org/project/docspan/)
|
|
53
|
+
[](https://opensource.org/licenses/MIT)
|
|
54
|
+
|
|
55
|
+
Push and pull markdown to Google Docs and Confluence from a single CLI. docspan provides bidirectional sync with three-way merge conflict detection, structural diff push that preserves comments on unchanged paragraphs, and a simple YAML-based configuration file.
|
|
56
|
+
|
|
57
|
+
The config file is named `markgate.yaml` — this name is preserved for backward compatibility and will be renamed in v0.2.0.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Supported Backends
|
|
62
|
+
|
|
63
|
+
| Backend | Push | Pull |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| Google Docs | yes | yes |
|
|
66
|
+
| Confluence | yes | yes |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install docspan
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
**1. Create `markgate.yaml`:**
|
|
81
|
+
|
|
82
|
+
```yaml
|
|
83
|
+
backends:
|
|
84
|
+
google_docs:
|
|
85
|
+
credentials_path: /path/to/service-account.json
|
|
86
|
+
|
|
87
|
+
mappings:
|
|
88
|
+
- local: docs/design-doc.md
|
|
89
|
+
backend: google_docs
|
|
90
|
+
remote_id: YOUR_GOOGLE_DOC_ID
|
|
91
|
+
direction: both
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**2. Set up authentication:**
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
docspan auth setup google_docs
|
|
98
|
+
# or
|
|
99
|
+
docspan auth setup confluence
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**3. Push and pull:**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
docspan push # push all mappings
|
|
106
|
+
docspan pull # pull all mappings
|
|
107
|
+
docspan status # show mapping table
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**4. Resolve conflicts (if any):**
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
docspan conflicts list
|
|
114
|
+
docspan conflicts resolve docs/design-doc.md --accept remote
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Configuration (`markgate.yaml`)
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
backends:
|
|
123
|
+
google_docs:
|
|
124
|
+
credentials_path: /path/to/service-account.json # or use env ACCOUNT_A_CREDENTIALS_PATH
|
|
125
|
+
confluence:
|
|
126
|
+
base_url: https://yourorg.atlassian.net
|
|
127
|
+
username: you@example.com
|
|
128
|
+
api_token: your-api-token # or env CONFLUENCE_API_TOKEN
|
|
129
|
+
|
|
130
|
+
mappings:
|
|
131
|
+
- local: docs/notes.md
|
|
132
|
+
backend: google_docs
|
|
133
|
+
remote_id: YOUR_GOOGLE_DOC_ID
|
|
134
|
+
direction: both # push | pull | both
|
|
135
|
+
- local: docs/page.md
|
|
136
|
+
backend: confluence
|
|
137
|
+
remote_id: YOUR_CONFLUENCE_PAGE_ID
|
|
138
|
+
direction: both
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Note**: `markgate.yaml` is gitignored by default because it may contain API tokens. Commit a `markgate.yaml.example` template alongside it.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Command Reference
|
|
146
|
+
|
|
147
|
+
### `docspan push`
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
docspan push [FILES]... [--dry-run] [--config PATH]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Push local markdown files to remote docs. Skips mappings with `direction = "pull"`. Accepts an optional list of local file paths to restrict which mappings are pushed.
|
|
154
|
+
|
|
155
|
+
### `docspan pull`
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
docspan pull [FILES]... [--dry-run] [--config PATH]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Pull remote documents into local markdown files with three-way merge. Writes conflict markers to the file if automatic merge fails.
|
|
162
|
+
|
|
163
|
+
### `docspan status`
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
docspan status [--config PATH]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Display all configured mappings in a table showing local file, backend, remote ID, and direction.
|
|
170
|
+
|
|
171
|
+
### `docspan auth setup`
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
docspan auth setup BACKEND [--config PATH]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Interactive authentication setup. `BACKEND` is one of `google_docs` or `confluence`.
|
|
178
|
+
|
|
179
|
+
For Google Docs, prints step-by-step service account setup instructions. For Confluence, prompts for base URL, username, and API token, then prints a YAML snippet to add to `markgate.yaml`.
|
|
180
|
+
|
|
181
|
+
### `docspan conflicts list`
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
docspan conflicts list [--config PATH]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Scan all tracked files for unresolved merge conflict markers (`<<<<<<< `). Prints a table of conflicted files and conflict block counts.
|
|
188
|
+
|
|
189
|
+
### `docspan conflicts resolve`
|
|
190
|
+
|
|
191
|
+
```
|
|
192
|
+
docspan conflicts resolve FILE --accept remote|local|merged [--config PATH]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Resolve a merge conflict in a tracked file.
|
|
196
|
+
|
|
197
|
+
| Strategy | Behavior |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `remote` | Re-fetch the remote version and overwrite the local file |
|
|
200
|
+
| `local` | Restore the pre-merge local content from the `.orig` backup |
|
|
201
|
+
| `merged` | Accept the current file contents as the resolved version (conflict markers must be removed first) |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Configuration Reference
|
|
206
|
+
|
|
207
|
+
### `backends.google_docs`
|
|
208
|
+
|
|
209
|
+
| Field | Type | Default | Description |
|
|
210
|
+
|---|---|---|---|
|
|
211
|
+
| `credentials_path` | string | null | Path to Google service account JSON key |
|
|
212
|
+
| `token_path` | string | `.markgate/google_token.json` | OAuth token storage path |
|
|
213
|
+
|
|
214
|
+
**Environment variable alternatives:**
|
|
215
|
+
- `ACCOUNT_A_CREDENTIALS_PATH` — path to service account JSON
|
|
216
|
+
- `ACCOUNT_A_CREDENTIALS` — inline service account JSON string
|
|
217
|
+
|
|
218
|
+
### `backends.confluence`
|
|
219
|
+
|
|
220
|
+
| Field | Type | Default | Description |
|
|
221
|
+
|---|---|---|---|
|
|
222
|
+
| `base_url` | string | null | Confluence base URL, e.g. `https://yourorg.atlassian.net` |
|
|
223
|
+
| `username` | string | null | Atlassian account email |
|
|
224
|
+
| `api_token` | string | null | API token from id.atlassian.com |
|
|
225
|
+
|
|
226
|
+
**Environment variable alternatives:**
|
|
227
|
+
- `CONFLUENCE_BASE_URL`
|
|
228
|
+
- `ATLASSIAN_USER_NAME`
|
|
229
|
+
- `CONFLUENCE_API_TOKEN`
|
|
230
|
+
|
|
231
|
+
### `mappings[]`
|
|
232
|
+
|
|
233
|
+
| Field | Type | Default | Required | Description |
|
|
234
|
+
|---|---|---|---|---|
|
|
235
|
+
| `local` | string | — | yes | Relative path to local markdown file |
|
|
236
|
+
| `backend` | string | — | yes | `"google_docs"` or `"confluence"` |
|
|
237
|
+
| `remote_id` | string | — | yes | Google Doc ID or Confluence page ID |
|
|
238
|
+
| `direction` | enum | `"both"` | no | `"push"`, `"pull"`, or `"both"` |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## State Files
|
|
243
|
+
|
|
244
|
+
docspan generates these files in your project directory after first sync:
|
|
245
|
+
|
|
246
|
+
| File | Description |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `.markgate-state.json` | Sync state tracking (content hashes, remote versions) |
|
|
249
|
+
| `.markgate-base/` | Content-addressed store of merge bases |
|
|
250
|
+
| `{file}.orig` | Backup of local file before merge; deleted after conflict resolution |
|
|
251
|
+
| `{file}.comments.md` | Confluence comment sidecar; written during pull if comments exist |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Known Limitations
|
|
256
|
+
|
|
257
|
+
> [!NOTE]
|
|
258
|
+
> **Known limitations in v0.1.0**
|
|
259
|
+
>
|
|
260
|
+
> - Google Docs: comments on edited paragraphs are lost on push (paragraph-level structural diff; comments on unchanged paragraphs are preserved)
|
|
261
|
+
> - Push: no image support — local images cannot be pushed to Google Docs or Confluence
|
|
262
|
+
> - Push: no table support — markdown tables are not rendered in Google Docs
|
|
263
|
+
> - Confluence: requires an Atlassian API token; no OAuth flow
|
|
264
|
+
> - Confluence: the comment sidecar (`{file}.comments.md`) is informational only; comments cannot be pushed back
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT. See [LICENSE](LICENSE) for details.
|
|
271
|
+
|
|
272
|
+
For contribution guidelines, see [CONTRIBUTING.md](https://github.com/tstapler/docspan/blob/main/CONTRIBUTING.md).
|
|
273
|
+
For the full change history, see [CHANGELOG.md](https://github.com/tstapler/docspan/blob/main/CHANGELOG.md).
|
docspan-0.1.0/Procfile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
worker: python sync.py
|