pubanon 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 (48) hide show
  1. pubanon-0.1.0/.gitignore +46 -0
  2. pubanon-0.1.0/LICENSE +21 -0
  3. pubanon-0.1.0/PKG-INFO +152 -0
  4. pubanon-0.1.0/README.md +132 -0
  5. pubanon-0.1.0/pyproject.toml +127 -0
  6. pubanon-0.1.0/src/pubanon/__init__.py +7 -0
  7. pubanon-0.1.0/src/pubanon/__main__.py +5 -0
  8. pubanon-0.1.0/src/pubanon/_cli/__init__.py +167 -0
  9. pubanon-0.1.0/src/pubanon/_cli/_arguments.py +544 -0
  10. pubanon-0.1.0/src/pubanon/_cli/_context.py +109 -0
  11. pubanon-0.1.0/src/pubanon/_cli/_history.py +463 -0
  12. pubanon-0.1.0/src/pubanon/_cli/_init.py +624 -0
  13. pubanon-0.1.0/src/pubanon/_cli/_init_draft.py +338 -0
  14. pubanon-0.1.0/src/pubanon/_cli/_inspect.py +248 -0
  15. pubanon-0.1.0/src/pubanon/_cli/_prepare.py +412 -0
  16. pubanon-0.1.0/src/pubanon/_cli/_remote.py +319 -0
  17. pubanon-0.1.0/src/pubanon/_cli/_selection.py +215 -0
  18. pubanon-0.1.0/src/pubanon/_cli/_setup.py +488 -0
  19. pubanon-0.1.0/src/pubanon/_cli/_status.py +413 -0
  20. pubanon-0.1.0/src/pubanon/_cli/_workflow.py +528 -0
  21. pubanon-0.1.0/src/pubanon/_config.py +1659 -0
  22. pubanon-0.1.0/src/pubanon/_errors.py +97 -0
  23. pubanon-0.1.0/src/pubanon/_filesystem.py +94 -0
  24. pubanon-0.1.0/src/pubanon/_gate.py +188 -0
  25. pubanon-0.1.0/src/pubanon/_locking.py +37 -0
  26. pubanon-0.1.0/src/pubanon/_policy.py +173 -0
  27. pubanon-0.1.0/src/pubanon/_remote/__init__.py +1 -0
  28. pubanon-0.1.0/src/pubanon/_remote/_host.py +1213 -0
  29. pubanon-0.1.0/src/pubanon/_remote/_lifecycle.py +708 -0
  30. pubanon-0.1.0/src/pubanon/_remote/_package.py +277 -0
  31. pubanon-0.1.0/src/pubanon/_remote/_publish.py +1494 -0
  32. pubanon-0.1.0/src/pubanon/_remote/_stage.py +679 -0
  33. pubanon-0.1.0/src/pubanon/_remote/_watch.py +536 -0
  34. pubanon-0.1.0/src/pubanon/_remote/_workflow_checks.py +320 -0
  35. pubanon-0.1.0/src/pubanon/_run.py +220 -0
  36. pubanon-0.1.0/src/pubanon/_snapshots/__init__.py +1 -0
  37. pubanon-0.1.0/src/pubanon/_snapshots/_audit.py +866 -0
  38. pubanon-0.1.0/src/pubanon/_snapshots/_candidate.py +2198 -0
  39. pubanon-0.1.0/src/pubanon/_snapshots/_conflicts.py +636 -0
  40. pubanon-0.1.0/src/pubanon/_snapshots/_history.py +822 -0
  41. pubanon-0.1.0/src/pubanon/_snapshots/_records.py +1358 -0
  42. pubanon-0.1.0/src/pubanon/_snapshots/_rewrite.py +314 -0
  43. pubanon-0.1.0/src/pubanon/_snapshots/_source.py +810 -0
  44. pubanon-0.1.0/src/pubanon/_snapshots/_store.py +47 -0
  45. pubanon-0.1.0/src/pubanon/_terminal.py +464 -0
  46. pubanon-0.1.0/src/pubanon/_toml.py +39 -0
  47. pubanon-0.1.0/src/pubanon/_windows_fs.py +101 -0
  48. pubanon-0.1.0/src/pubanon/py.typed +0 -0
@@ -0,0 +1,46 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ dist/
13
+ .eggs/
14
+ *.egg-info/
15
+ *.egg
16
+
17
+ # Virtual environments
18
+ .venv/
19
+ venv/
20
+ ENV/
21
+
22
+ # PyInstaller
23
+ *.manifest
24
+ *.spec
25
+
26
+ # Unit test / coverage reports
27
+ htmlcov/
28
+ .tox/
29
+ .nox/
30
+ .coverage*
31
+ .pytest_cache/
32
+ .cache/
33
+
34
+ # Type checker / linter caches
35
+ .pyright/
36
+ .mypy_cache/
37
+ .ruff_cache/
38
+ pyrightconfig.json
39
+
40
+ # Editors
41
+ .vscode/
42
+ .idea/
43
+
44
+ # OS
45
+ .DS_Store
46
+ Thumbs.db
pubanon-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the pubanon authors
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.
pubanon-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.5
2
+ Name: pubanon
3
+ Version: 0.1.0
4
+ Summary: Publish selected files to GitHub under a separate identity
5
+ Project-URL: Documentation, https://github.com/dactylo/pubanon/tree/main/docs
6
+ Project-URL: Homepage, https://github.com/dactylo/pubanon
7
+ Project-URL: Issues, https://github.com/dactylo/pubanon/issues
8
+ Project-URL: Repository, https://github.com/dactylo/pubanon
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: anonymization,git,privacy,publishing,security
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.13
18
+ Requires-Dist: ruamel-yaml>=0.19.1
19
+ Description-Content-Type: text/markdown
20
+
21
+ # pubanon
22
+
23
+ Pubanon publishes selected files from a private Git repository to GitHub under a separate name and
24
+ email. Each release creates one publication commit without copying your private commit history.
25
+ Choose the files, review the prepared release, and confirm publication.
26
+
27
+ Pubanon checks the selected content for configured sensitive terms. It does not rewrite secrets out
28
+ of files. You control what you publish; the
29
+ [content matching limits](docs/guide.md#how-a-release-works) explain what the checks can detect.
30
+
31
+ ## Installation
32
+
33
+ You need Python 3.13 or newer, Git 2.41.0 or newer, and the GitHub CLI (`gh`). Pubanon targets
34
+ Linux, macOS, and native Windows. See [platform requirements](docs/platforms.md) for details.
35
+
36
+ From a checkout of this repository, install the command with [uv](https://docs.astral.sh/uv/):
37
+
38
+ ```sh
39
+ uv tool install .
40
+ ```
41
+
42
+ After updating the checkout, update the installed command:
43
+
44
+ ```sh
45
+ uv tool install --reinstall .
46
+ ```
47
+
48
+ uv is needed for these installation commands. If you use make, `make install` and `make reinstall`
49
+ run the same commands.
50
+
51
+ ## Your first release
52
+
53
+ ### Choose your publication identity
54
+
55
+ ```sh
56
+ pubanon setup
57
+ ```
58
+
59
+ Setup asks for the name and email to use in published Git history and the terms you want to keep
60
+ private. It verifies your GitHub CLI account and shows your answers before saving. Choose the name
61
+ and email you want readers to see: existing publication commits keep that identity. Use
62
+ `pubanon setup --edit` to change saved answers or `pubanon doctor` to check readiness.
63
+
64
+ ### Select your project and files
65
+
66
+ Enter a private source repository with no Git remote:
67
+
68
+ ```sh
69
+ cd /path/to/private-project
70
+ pubanon init
71
+ ```
72
+
73
+ Follow the prompts to choose files, a GitHub destination, and repository settings. Choose `private`
74
+ visibility for this walkthrough so you can check the result on GitHub before making it public.
75
+ Review your choices and save. Pubanon stores its configuration outside the source repository.
76
+
77
+ ### Prepare and review
78
+
79
+ Commit the changes you want to publish in your source repository, then run:
80
+
81
+ ```sh
82
+ pubanon repo create
83
+ pubanon prepare
84
+ pubanon inspect
85
+ ```
86
+
87
+ `prepare` saves the selected committed files as a local *snapshot*. Uncommitted changes are left
88
+ out. If the selected `pyproject.toml` declares a package version, Pubanon uses it for the release
89
+ tag; otherwise the snapshot is untagged. See [version tags](docs/guide.md#version-tags) to choose
90
+ an explicit version.
91
+
92
+ `inspect` shows the release details and a directory of read-only files to review. Builds or tests
93
+ that write files need a separate writable copy. Changes in either copy do not alter the snapshot.
94
+ To correct it, edit and commit in the source, then run `pubanon discard` and `pubanon prepare`
95
+ again.
96
+
97
+ ### Publish and make it public
98
+
99
+ When you are satisfied, run:
100
+
101
+ ```sh
102
+ pubanon publish
103
+ pubanon repo reveal
104
+ ```
105
+
106
+ `publish` asks for confirmation before sending the saved snapshot to your private destination.
107
+ `repo reveal` checks the published history, settings, and any configured CI requirements before
108
+ asking to make the repository public. It also provides links to Actions logs and artifacts for you
109
+ to review; Pubanon does not scan their contents.
110
+
111
+ For the next release, commit your changes and repeat `prepare`, `inspect`, and `publish`. For a
112
+ versioned Python package, update and commit its version first. The
113
+ [publication guide](docs/guide.md#quick-path) also explains the `release VERSION` shortcut.
114
+
115
+ Still correcting the initial release privately? Commit your fixes, then use
116
+ `pubanon prepare --replace all`, `pubanon inspect`, and `pubanon publish` to replace the entire
117
+ published history while keeping the same version. To correct only the latest publication and keep
118
+ earlier history, use `pubanon prepare --replace`. See
119
+ [correcting published history](docs/guide.md#correcting-published-history) for the available
120
+ scopes.
121
+
122
+ After replacements, `pubanon clean` previews and removes superseded local snapshots with one
123
+ confirmation. It keeps current published snapshots and prepared work. See
124
+ [cleanup](docs/operations.md#cleanup) for other cleanup tasks.
125
+
126
+ ## Where to go next
127
+
128
+ | If you want to... | Read |
129
+ | --------------------------------------------------------------- | ----------------------------------------------------------------------------- |
130
+ | Publish another release or choose a workflow | [Publication guide](docs/guide.md) |
131
+ | Change the selected files, CI checks, or other project settings | [Project configuration](docs/guide.md#edit-an-existing-project) |
132
+ | Run CI privately before publishing | [Private staging](docs/guide.md#private-first-ci) |
133
+ | Inspect an earlier release or recover its private source | [Published history](docs/operations.md#published-history-and-source-recovery) |
134
+ | Correct a published release | [History replacement](docs/guide.md#correcting-published-history) |
135
+ | Recover from an interruption, clean up, or make backups | [Operations guide](docs/operations.md) |
136
+ | Check platform requirements | [Platforms](docs/platforms.md) |
137
+ | See changes by version | [Changelog](CHANGELOG.md) |
138
+
139
+ Use `pubanon status` to see local state or `pubanon status --remote` to compare it with GitHub. If
140
+ a push outcome is unknown, follow the reported recovery command: `pubanon publish --resolve`.
141
+
142
+ ## Development
143
+
144
+ Provide the tools in `[dependency-groups].dev` through your environment or package manager, then
145
+ run:
146
+
147
+ ```sh
148
+ make check
149
+ ```
150
+
151
+ `make format` applies the repository formatters. Tests run offline with temporary Git repositories
152
+ and a fake GitHub CLI.
@@ -0,0 +1,132 @@
1
+ # pubanon
2
+
3
+ Pubanon publishes selected files from a private Git repository to GitHub under a separate name and
4
+ email. Each release creates one publication commit without copying your private commit history.
5
+ Choose the files, review the prepared release, and confirm publication.
6
+
7
+ Pubanon checks the selected content for configured sensitive terms. It does not rewrite secrets out
8
+ of files. You control what you publish; the
9
+ [content matching limits](docs/guide.md#how-a-release-works) explain what the checks can detect.
10
+
11
+ ## Installation
12
+
13
+ You need Python 3.13 or newer, Git 2.41.0 or newer, and the GitHub CLI (`gh`). Pubanon targets
14
+ Linux, macOS, and native Windows. See [platform requirements](docs/platforms.md) for details.
15
+
16
+ From a checkout of this repository, install the command with [uv](https://docs.astral.sh/uv/):
17
+
18
+ ```sh
19
+ uv tool install .
20
+ ```
21
+
22
+ After updating the checkout, update the installed command:
23
+
24
+ ```sh
25
+ uv tool install --reinstall .
26
+ ```
27
+
28
+ uv is needed for these installation commands. If you use make, `make install` and `make reinstall`
29
+ run the same commands.
30
+
31
+ ## Your first release
32
+
33
+ ### Choose your publication identity
34
+
35
+ ```sh
36
+ pubanon setup
37
+ ```
38
+
39
+ Setup asks for the name and email to use in published Git history and the terms you want to keep
40
+ private. It verifies your GitHub CLI account and shows your answers before saving. Choose the name
41
+ and email you want readers to see: existing publication commits keep that identity. Use
42
+ `pubanon setup --edit` to change saved answers or `pubanon doctor` to check readiness.
43
+
44
+ ### Select your project and files
45
+
46
+ Enter a private source repository with no Git remote:
47
+
48
+ ```sh
49
+ cd /path/to/private-project
50
+ pubanon init
51
+ ```
52
+
53
+ Follow the prompts to choose files, a GitHub destination, and repository settings. Choose `private`
54
+ visibility for this walkthrough so you can check the result on GitHub before making it public.
55
+ Review your choices and save. Pubanon stores its configuration outside the source repository.
56
+
57
+ ### Prepare and review
58
+
59
+ Commit the changes you want to publish in your source repository, then run:
60
+
61
+ ```sh
62
+ pubanon repo create
63
+ pubanon prepare
64
+ pubanon inspect
65
+ ```
66
+
67
+ `prepare` saves the selected committed files as a local *snapshot*. Uncommitted changes are left
68
+ out. If the selected `pyproject.toml` declares a package version, Pubanon uses it for the release
69
+ tag; otherwise the snapshot is untagged. See [version tags](docs/guide.md#version-tags) to choose
70
+ an explicit version.
71
+
72
+ `inspect` shows the release details and a directory of read-only files to review. Builds or tests
73
+ that write files need a separate writable copy. Changes in either copy do not alter the snapshot.
74
+ To correct it, edit and commit in the source, then run `pubanon discard` and `pubanon prepare`
75
+ again.
76
+
77
+ ### Publish and make it public
78
+
79
+ When you are satisfied, run:
80
+
81
+ ```sh
82
+ pubanon publish
83
+ pubanon repo reveal
84
+ ```
85
+
86
+ `publish` asks for confirmation before sending the saved snapshot to your private destination.
87
+ `repo reveal` checks the published history, settings, and any configured CI requirements before
88
+ asking to make the repository public. It also provides links to Actions logs and artifacts for you
89
+ to review; Pubanon does not scan their contents.
90
+
91
+ For the next release, commit your changes and repeat `prepare`, `inspect`, and `publish`. For a
92
+ versioned Python package, update and commit its version first. The
93
+ [publication guide](docs/guide.md#quick-path) also explains the `release VERSION` shortcut.
94
+
95
+ Still correcting the initial release privately? Commit your fixes, then use
96
+ `pubanon prepare --replace all`, `pubanon inspect`, and `pubanon publish` to replace the entire
97
+ published history while keeping the same version. To correct only the latest publication and keep
98
+ earlier history, use `pubanon prepare --replace`. See
99
+ [correcting published history](docs/guide.md#correcting-published-history) for the available
100
+ scopes.
101
+
102
+ After replacements, `pubanon clean` previews and removes superseded local snapshots with one
103
+ confirmation. It keeps current published snapshots and prepared work. See
104
+ [cleanup](docs/operations.md#cleanup) for other cleanup tasks.
105
+
106
+ ## Where to go next
107
+
108
+ | If you want to... | Read |
109
+ | --------------------------------------------------------------- | ----------------------------------------------------------------------------- |
110
+ | Publish another release or choose a workflow | [Publication guide](docs/guide.md) |
111
+ | Change the selected files, CI checks, or other project settings | [Project configuration](docs/guide.md#edit-an-existing-project) |
112
+ | Run CI privately before publishing | [Private staging](docs/guide.md#private-first-ci) |
113
+ | Inspect an earlier release or recover its private source | [Published history](docs/operations.md#published-history-and-source-recovery) |
114
+ | Correct a published release | [History replacement](docs/guide.md#correcting-published-history) |
115
+ | Recover from an interruption, clean up, or make backups | [Operations guide](docs/operations.md) |
116
+ | Check platform requirements | [Platforms](docs/platforms.md) |
117
+ | See changes by version | [Changelog](CHANGELOG.md) |
118
+
119
+ Use `pubanon status` to see local state or `pubanon status --remote` to compare it with GitHub. If
120
+ a push outcome is unknown, follow the reported recovery command: `pubanon publish --resolve`.
121
+
122
+ ## Development
123
+
124
+ Provide the tools in `[dependency-groups].dev` through your environment or package manager, then
125
+ run:
126
+
127
+ ```sh
128
+ make check
129
+ ```
130
+
131
+ `make format` applies the repository formatters. Tests run offline with temporary Git repositories
132
+ and a fake GitHub CLI.
@@ -0,0 +1,127 @@
1
+ # ==========================================================
2
+ # Build System
3
+ # ==========================================================
4
+ [build-system]
5
+ build-backend = "hatchling.build"
6
+ requires = [ "hatchling>=1.27" ]
7
+
8
+ # ==========================================================
9
+ # Project Metadata
10
+ # ==========================================================
11
+ [project]
12
+ name = "pubanon"
13
+ version = "0.1.0"
14
+ description = "Publish selected files to GitHub under a separate identity"
15
+ readme = "README.md"
16
+ keywords = [ "anonymization", "git", "privacy", "publishing", "security" ]
17
+ license = "MIT"
18
+ license-files = [ "LICENSE" ]
19
+ requires-python = ">=3.13"
20
+ classifiers = [
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Programming Language :: Python :: 3.15",
25
+ "Typing :: Typed",
26
+ ]
27
+ dependencies = [ "ruamel-yaml>=0.19.1" ]
28
+ urls.Documentation = "https://github.com/dactylo/pubanon/tree/main/docs"
29
+ urls.Homepage = "https://github.com/dactylo/pubanon"
30
+ urls.Issues = "https://github.com/dactylo/pubanon/issues"
31
+ urls.Repository = "https://github.com/dactylo/pubanon"
32
+ scripts.pubanon = "pubanon._cli:main"
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "mdformat",
37
+ "mdformat-config",
38
+ "mdformat-frontmatter",
39
+ "mdformat-gfm",
40
+ "mdformat-pyproject",
41
+ "mdformat-ruff",
42
+ "mdformat-shfmt",
43
+ "pyproject-fmt",
44
+ "pyright",
45
+ "pytest",
46
+ "ruff",
47
+ ]
48
+
49
+ [tool.hatch]
50
+ build.targets.wheel.packages = [ "src/pubanon" ]
51
+ build.targets.sdist.include = [ "/src" ]
52
+
53
+ # ==========================================================
54
+ # Ruff Configuration
55
+ # ==========================================================
56
+ [tool.ruff]
57
+ target-version = "py313"
58
+ line-length = 99
59
+ format.quote-style = "single"
60
+ format.exclude = [ "*.md" ]
61
+ lint.select = [ "ALL" ]
62
+ lint.ignore = [
63
+ "COM812", # Conflicts with the Ruff formatter
64
+ "CPY001", # Copyright notices are not required
65
+ "D203", # Conflicts with the Google docstring convention
66
+ "D213", # Conflicts with the Google docstring convention
67
+ "EM101", # Exception must not use string literal
68
+ "EM102", # Exception must not use f-string literal
69
+ "FBT001", # Boolean positional arg in function definition
70
+ "FBT002", # Boolean default value in function definition
71
+ "ISC001", # Implicit string concatenation
72
+ "Q000", # Conflicts with the Ruff formatter
73
+ "Q001", # Conflicts with the Ruff formatter
74
+ "Q002", # Conflicts with the Ruff formatter
75
+ "Q003", # Conflicts with the Ruff formatter
76
+ "T201", # print() found
77
+ "TRY003", # Avoid specifying long messages outside exception class
78
+ ]
79
+ lint.per-file-ignores."src/pubanon/_cli/_arguments.py" = [ "SLF001" ]
80
+ lint.per-file-ignores."src/pubanon/_cli/_workflow.py" = [ "SLF001" ]
81
+ lint.per-file-ignores."src/pubanon/_config.py" = [ "SLF001" ]
82
+ lint.per-file-ignores."src/pubanon/_remote/_publish.py" = [ "SLF001" ]
83
+ lint.per-file-ignores."src/pubanon/_remote/_stage.py" = [ "SLF001" ]
84
+ lint.per-file-ignores."src/pubanon/_snapshots/_candidate.py" = [ "SLF001" ]
85
+ lint.per-file-ignores."src/pubanon/_snapshots/_records.py" = [ "SLF001" ]
86
+ lint.per-file-ignores."tests/**/*.py" = [
87
+ "D", # No docstrings required in tests
88
+ "PLC2701", # Tests intentionally import private implementation symbols
89
+ "PLR2004", # Allow magic values in tests
90
+ "S101", # Allow asserts in tests
91
+ "S603", # Allow subprocess calls in tests
92
+ "S607", # Allow partial executable paths in tests
93
+ "SLF001", # Tests intentionally inspect and patch private state
94
+ ]
95
+ lint.pydocstyle.convention = "google"
96
+
97
+ [tool.pyproject-fmt]
98
+ max_supported_python = "3.15"
99
+
100
+ # ==========================================================
101
+ # Pyright Configuration
102
+ # ==========================================================
103
+ [tool.pyright]
104
+ pythonVersion = "3.13" # Keep in sync with tool.ruff.target-version
105
+ typeCheckingMode = "strict"
106
+ extraPaths = [ ".", "src" ]
107
+ executionEnvironments = [
108
+ { root = "tests", reportPrivateUsage = "none" },
109
+ { root = "." },
110
+ ]
111
+
112
+ # ==========================================================
113
+ # Pytest Configuration
114
+ # ==========================================================
115
+ [tool.pytest]
116
+ ini_options.testpaths = [ "tests" ]
117
+ ini_options.pythonpath = [ "src" ]
118
+ ini_options.addopts = [ "-q", "--strict-markers", "--tb=short" ]
119
+ ini_options.markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')" ]
120
+ ini_options.faulthandler_timeout = 300
121
+
122
+ # ==========================================================
123
+ # Mdformat Configuration (Requires mdformat-pyproject)
124
+ # ==========================================================
125
+ [tool.mdformat]
126
+ wrap = 99 # Keep in sync with tool.ruff.line-length
127
+ number = true
@@ -0,0 +1,7 @@
1
+ """Publish private repositories under a separate publication identity.
2
+
3
+ Use the `pubanon` command or `python -m pubanon`. The package root exports no
4
+ public Python API.
5
+ """
6
+
7
+ __all__: list[str] = []
@@ -0,0 +1,5 @@
1
+ """Run the pubanon command-line interface with `python -m pubanon`."""
2
+
3
+ from pubanon._cli import main
4
+
5
+ raise SystemExit(main())
@@ -0,0 +1,167 @@
1
+ """Provide the Pubanon command-line entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+ from contextlib import ExitStack
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING
10
+
11
+ from pubanon import (
12
+ _config,
13
+ _run,
14
+ )
15
+ from pubanon._cli import (
16
+ _arguments,
17
+ _init,
18
+ _inspect,
19
+ _prepare,
20
+ _setup,
21
+ _status,
22
+ _workflow,
23
+ )
24
+ from pubanon._cli import _history as cli_history
25
+ from pubanon._cli import _remote as cli_remote
26
+ from pubanon._errors import OperationCancelled, PubanonError
27
+ from pubanon._terminal import (
28
+ color_context,
29
+ diagnostic,
30
+ guidance,
31
+ guidance_context,
32
+ output_encoding_context,
33
+ )
34
+
35
+ if TYPE_CHECKING:
36
+ import argparse
37
+ from collections.abc import Callable, Iterable, Sequence
38
+
39
+
40
+ def _dispatch(args: argparse.Namespace) -> None:
41
+ """Dispatch one parsed command."""
42
+
43
+ def _dispatch_doctor(_args: argparse.Namespace) -> None:
44
+ _setup.doctor()
45
+
46
+ def _dispatch_setup(args: argparse.Namespace) -> None:
47
+ if args.edit:
48
+ _setup.setup(edit=True)
49
+ else:
50
+ _setup.setup()
51
+
52
+ handlers: dict[str, Callable[[argparse.Namespace], None]] = {
53
+ 'clean': cli_history.command_clean,
54
+ 'delete': cli_history.command_delete,
55
+ 'discard': cli_history.command_discard,
56
+ 'doctor': _dispatch_doctor,
57
+ 'list': cli_history.command_list,
58
+ 'init': _init.command_init,
59
+ 'inspect': _inspect.command_inspect,
60
+ 'prepare': _prepare.command_prepare,
61
+ 'publish': cli_remote.command_publish,
62
+ 'release': _workflow.command_release,
63
+ 'run': _workflow.command_run,
64
+ 'setup': _dispatch_setup,
65
+ 'show': cli_history.command_show,
66
+ 'status': _status.command_status,
67
+ }
68
+ if args.command == 'repo':
69
+ handler = {
70
+ 'create': cli_remote.repo_create,
71
+ 'reveal': cli_remote.repo_reveal,
72
+ 'verify': cli_remote.verify_repo,
73
+ }.get(args.repo_command)
74
+ elif args.command == 'stage':
75
+ handler = {
76
+ 'clean': cli_remote.stage_clean,
77
+ 'push': cli_remote.stage_push,
78
+ 'status': cli_remote.stage_status,
79
+ }.get(args.stage_command)
80
+ elif args.command == 'watch':
81
+ handler = {'ci': cli_remote.watch_ci, 'package': cli_remote.watch_package}.get(
82
+ args.watch_command
83
+ )
84
+ else:
85
+ handler = handlers.get(args.command)
86
+ if handler is None:
87
+ raise PubanonError('unsupported command')
88
+ if args.command != 'setup':
89
+ _run.check_floors()
90
+ handler(args)
91
+
92
+
93
+ def _implicit_guidance_project(args: argparse.Namespace) -> str | None:
94
+ """Omit redundant project advice when registry lookup is available."""
95
+ if args.command in {'setup', 'doctor'}:
96
+ return None
97
+ try:
98
+ paths = _config.derive_paths(os.environ)
99
+ if not paths.registry.exists():
100
+ return None
101
+ return _config.registry_lookup(paths, Path.cwd())
102
+ except PubanonError:
103
+ # Advice must not add a registry dependency to explicit project selection.
104
+ return None
105
+
106
+
107
+ def _report_error(error: PubanonError) -> None:
108
+ """Report a refusal with its diagnostic values."""
109
+ print(diagnostic(error, stream=sys.stderr), file=sys.stderr)
110
+
111
+
112
+ def _main(argv: Sequence[str] | None = None) -> int:
113
+ """Parse and dispatch one command, translating failures to process exit statuses."""
114
+ with ExitStack() as contexts:
115
+ try:
116
+ parser = _arguments.build_parser()
117
+ arguments = list(sys.argv[1:] if argv is None else argv)
118
+ contexts.enter_context(color_context(parser.selected_color_mode(arguments)))
119
+ args = parser.parse_args(arguments)
120
+ _arguments.validate_options(parser, args)
121
+ try:
122
+ selected = getattr(args, 'project', None)
123
+ implicit = _implicit_guidance_project(args)
124
+ with guidance_context(
125
+ project=selected,
126
+ implicit_project=implicit if selected in {None, implicit} else None,
127
+ ):
128
+ try:
129
+ _dispatch(args)
130
+ except PubanonError as error:
131
+ if error.recovery is not None:
132
+ error.recovery = guidance(error.recovery)
133
+ raise
134
+ except OperationCancelled:
135
+ raise
136
+ except PubanonError as error:
137
+ _report_error(error)
138
+ return 1
139
+ except SystemExit as exc:
140
+ return exc.code if isinstance(exc.code, int) else 2
141
+ except OperationCancelled as error:
142
+ print(diagnostic(error, stream=sys.stderr), file=sys.stderr)
143
+ return 130
144
+ except KeyboardInterrupt:
145
+ error = OperationCancelled('operation cancelled')
146
+ print(diagnostic(error, stream=sys.stderr), file=sys.stderr)
147
+ return 130
148
+ except PubanonError as error:
149
+ print(diagnostic(error, stream=sys.stderr), file=sys.stderr)
150
+ return 1
151
+ return 0
152
+
153
+
154
+ def main(argv: Iterable[str] | None = None) -> int:
155
+ """Run one command with consistent color policy, including help and errors.
156
+
157
+ Args:
158
+ argv: Arguments excluding the executable, or None for process
159
+ arguments.
160
+
161
+ Returns:
162
+ Zero for success, one for refusal, two for usage errors, or 130 for
163
+ interruption.
164
+ """
165
+ arguments = list(sys.argv[1:] if argv is None else argv)
166
+ with output_encoding_context():
167
+ return _main(arguments)