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.
Files changed (126) hide show
  1. docspan-0.1.0/.github/workflows/ci.yml +34 -0
  2. docspan-0.1.0/.github/workflows/publish.yml +58 -0
  3. docspan-0.1.0/.github/workflows/release-please.yml +18 -0
  4. docspan-0.1.0/.gitignore +67 -0
  5. docspan-0.1.0/.release-please-manifest.json +3 -0
  6. docspan-0.1.0/CHANGELOG.md +35 -0
  7. docspan-0.1.0/CONTRIBUTING.md +148 -0
  8. docspan-0.1.0/PKG-INFO +273 -0
  9. docspan-0.1.0/Procfile +1 -0
  10. docspan-0.1.0/RAILWAY_SETUP.md +67 -0
  11. docspan-0.1.0/README.md +224 -0
  12. docspan-0.1.0/docs/backends/confluence.md +67 -0
  13. docspan-0.1.0/docs/backends/google-docs.md +62 -0
  14. docspan-0.1.0/docs/commands.md +204 -0
  15. docspan-0.1.0/docs/configuration.md +89 -0
  16. docspan-0.1.0/docs/contributing.md +147 -0
  17. docspan-0.1.0/docs/index.md +63 -0
  18. docspan-0.1.0/docs/install.md +107 -0
  19. docspan-0.1.0/docspan.yaml.example +32 -0
  20. docspan-0.1.0/markgate.yaml.example +25 -0
  21. docspan-0.1.0/mkdocs.yml +40 -0
  22. docspan-0.1.0/modules/__init__.py +5 -0
  23. docspan-0.1.0/modules/auth.py +134 -0
  24. docspan-0.1.0/modules/conflict_handler.py +163 -0
  25. docspan-0.1.0/modules/converter.py +470 -0
  26. docspan-0.1.0/modules/gdrive_client.py +616 -0
  27. docspan-0.1.0/modules/sync_engine.py +310 -0
  28. docspan-0.1.0/project_plans/docspan-release/implementation/adversarial-review.md +130 -0
  29. docspan-0.1.0/project_plans/docspan-release/implementation/plan.md +878 -0
  30. docspan-0.1.0/project_plans/docspan-release/implementation/release-checklist.md +45 -0
  31. docspan-0.1.0/project_plans/docspan-release/implementation/validation.md +195 -0
  32. docspan-0.1.0/project_plans/docspan-release/requirements.md +143 -0
  33. docspan-0.1.0/project_plans/docspan-release/research/architecture.md +324 -0
  34. docspan-0.1.0/project_plans/docspan-release/research/features.md +292 -0
  35. docspan-0.1.0/project_plans/docspan-release/research/google-docs-push.md +181 -0
  36. docspan-0.1.0/project_plans/docspan-release/research/pitfalls.md +203 -0
  37. docspan-0.1.0/project_plans/docspan-release/research/stack.md +220 -0
  38. docspan-0.1.0/project_plans/markgate-sync/decisions/ADR-001-merge3-dependency.md +33 -0
  39. docspan-0.1.0/project_plans/markgate-sync/decisions/ADR-002-base-content-sidecar-store.md +32 -0
  40. docspan-0.1.0/project_plans/markgate-sync/implementation/adversarial-review.md +58 -0
  41. docspan-0.1.0/project_plans/markgate-sync/implementation/plan.md +717 -0
  42. docspan-0.1.0/project_plans/markgate-sync/implementation/validation.md +253 -0
  43. docspan-0.1.0/project_plans/markgate-sync/requirements.md +200 -0
  44. docspan-0.1.0/project_plans/markgate-sync/research/architecture.md +186 -0
  45. docspan-0.1.0/project_plans/markgate-sync/research/features.md +169 -0
  46. docspan-0.1.0/project_plans/markgate-sync/research/pitfalls.md +145 -0
  47. docspan-0.1.0/project_plans/markgate-sync/research/stack.md +86 -0
  48. docspan-0.1.0/pyproject.toml +96 -0
  49. docspan-0.1.0/release-please-config.json +9 -0
  50. docspan-0.1.0/requirements.txt +8 -0
  51. docspan-0.1.0/runtime.txt +1 -0
  52. docspan-0.1.0/src/docspan/__init__.py +3 -0
  53. docspan-0.1.0/src/docspan/__main__.py +0 -0
  54. docspan-0.1.0/src/docspan/backends/__init__.py +19 -0
  55. docspan-0.1.0/src/docspan/backends/base.py +85 -0
  56. docspan-0.1.0/src/docspan/backends/confluence/__init__.py +0 -0
  57. docspan-0.1.0/src/docspan/backends/confluence/adf/__init__.py +14 -0
  58. docspan-0.1.0/src/docspan/backends/confluence/adf/comparator.py +427 -0
  59. docspan-0.1.0/src/docspan/backends/confluence/adf/converter.py +119 -0
  60. docspan-0.1.0/src/docspan/backends/confluence/adf/converters.py +1449 -0
  61. docspan-0.1.0/src/docspan/backends/confluence/adf/interfaces.py +191 -0
  62. docspan-0.1.0/src/docspan/backends/confluence/adf/nodes.py +2085 -0
  63. docspan-0.1.0/src/docspan/backends/confluence/adf/parser.py +400 -0
  64. docspan-0.1.0/src/docspan/backends/confluence/adf/validators.py +161 -0
  65. docspan-0.1.0/src/docspan/backends/confluence/adf/visitors.py +495 -0
  66. docspan-0.1.0/src/docspan/backends/confluence/backend.py +227 -0
  67. docspan-0.1.0/src/docspan/backends/confluence/client.py +44 -0
  68. docspan-0.1.0/src/docspan/backends/confluence/config/__init__.py +21 -0
  69. docspan-0.1.0/src/docspan/backends/confluence/config/loader.py +107 -0
  70. docspan-0.1.0/src/docspan/backends/confluence/config/models.py +167 -0
  71. docspan-0.1.0/src/docspan/backends/confluence/config/validation.py +297 -0
  72. docspan-0.1.0/src/docspan/backends/confluence/markdown/__init__.py +22 -0
  73. docspan-0.1.0/src/docspan/backends/confluence/markdown/ast.py +819 -0
  74. docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/__init__.py +5 -0
  75. docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/frontmatter.py +80 -0
  76. docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/mermaid.py +64 -0
  77. docspan-0.1.0/src/docspan/backends/confluence/markdown/extensions/wikilinks.py +179 -0
  78. docspan-0.1.0/src/docspan/backends/confluence/markdown/inline_parser.py +495 -0
  79. docspan-0.1.0/src/docspan/backends/confluence/markdown/parser.py +1006 -0
  80. docspan-0.1.0/src/docspan/backends/confluence/models/__init__.py +18 -0
  81. docspan-0.1.0/src/docspan/backends/confluence/models/markdown_file.py +402 -0
  82. docspan-0.1.0/src/docspan/backends/confluence/models/page.py +212 -0
  83. docspan-0.1.0/src/docspan/backends/confluence/models/path_utils.py +34 -0
  84. docspan-0.1.0/src/docspan/backends/confluence/models/results.py +28 -0
  85. docspan-0.1.0/src/docspan/backends/confluence/models/sync_status.py +382 -0
  86. docspan-0.1.0/src/docspan/backends/confluence/services/__init__.py +0 -0
  87. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/__init__.py +40 -0
  88. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/attachment_client.py +147 -0
  89. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/base_client.py +420 -0
  90. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/client.py +376 -0
  91. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/comment_client.py +682 -0
  92. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/crawler.py +587 -0
  93. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/label_client.py +130 -0
  94. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/page_client.py +1288 -0
  95. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/space_client.py +179 -0
  96. docspan-0.1.0/src/docspan/backends/confluence/services/confluence/url_parser.py +106 -0
  97. docspan-0.1.0/src/docspan/backends/google_docs/__init__.py +0 -0
  98. docspan-0.1.0/src/docspan/backends/google_docs/auth.py +143 -0
  99. docspan-0.1.0/src/docspan/backends/google_docs/backend.py +140 -0
  100. docspan-0.1.0/src/docspan/backends/google_docs/client.py +665 -0
  101. docspan-0.1.0/src/docspan/backends/google_docs/converter.py +471 -0
  102. docspan-0.1.0/src/docspan/backends/google_docs/docs_request_builder.py +232 -0
  103. docspan-0.1.0/src/docspan/backends/google_docs/docs_structure_parser.py +120 -0
  104. docspan-0.1.0/src/docspan/backends/google_docs/markdown_to_paragraph_parser.py +145 -0
  105. docspan-0.1.0/src/docspan/cli/__init__.py +0 -0
  106. docspan-0.1.0/src/docspan/cli/main.py +408 -0
  107. docspan-0.1.0/src/docspan/config.py +62 -0
  108. docspan-0.1.0/src/docspan/core/__init__.py +49 -0
  109. docspan-0.1.0/src/docspan/core/merge.py +30 -0
  110. docspan-0.1.0/src/docspan/core/orchestrator.py +332 -0
  111. docspan-0.1.0/src/docspan/core/paths.py +8 -0
  112. docspan-0.1.0/src/docspan/core/state.py +53 -0
  113. docspan-0.1.0/sync.py +368 -0
  114. docspan-0.1.0/terraform/main.tf +28 -0
  115. docspan-0.1.0/terraform/variables.tf +17 -0
  116. docspan-0.1.0/tests/__init__.py +0 -0
  117. docspan-0.1.0/tests/test_cli.py +415 -0
  118. docspan-0.1.0/tests/test_config.py +191 -0
  119. docspan-0.1.0/tests/test_conflict_resolution.py +197 -0
  120. docspan-0.1.0/tests/test_docs_request_builder.py +130 -0
  121. docspan-0.1.0/tests/test_docs_structure_parser.py +187 -0
  122. docspan-0.1.0/tests/test_markdown_to_paragraph_parser.py +113 -0
  123. docspan-0.1.0/tests/test_merge.py +78 -0
  124. docspan-0.1.0/tests/test_orchestrator.py +253 -0
  125. docspan-0.1.0/tests/test_state.py +115 -0
  126. 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
@@ -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,3 @@
1
+ {
2
+ ".": "0.1.0"
3
+ }
@@ -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
+ [![PyPI](https://img.shields.io/pypi/v/docspan)](https://pypi.org/project/docspan/)
53
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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