dotman-git 1.1.0__tar.gz → 1.2.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.
- {dotman_git-1.1.0 → dotman_git-1.2.0}/CHANGELOG.md +60 -4
- {dotman_git-1.1.0 → dotman_git-1.2.0}/CONTRIBUTING.md +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/LICENSE +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/PKG-INFO +35 -6
- {dotman_git-1.1.0 → dotman_git-1.2.0}/README.md +31 -5
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/__init__.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/branch_ops.py +19 -4
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/__init__.py +4 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/add_cmd.py +9 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/audit_cmd.py +4 -4
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/backup_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/branch_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/clean_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/common.py +98 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/completions_cmd.py +2 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/config_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/deploy_cmd.py +2 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/discover_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/doctor_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/edit_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/encrypt_cmd.py +28 -8
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/export_cmd.py +9 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/import_cmd.py +14 -8
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/init_cmd.py +6 -4
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/interface.py +10 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/log_cmd.py +14 -3
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/navigate_cmd.py +36 -39
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/profile_cmd.py +10 -4
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/remote_cmd.py +3 -3
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/restore_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/revert_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/rollback_cmd.py +16 -5
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/status_cmd.py +4 -4
- dotman_git-1.2.0/dot_man/cli/switch_cmd.py +68 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/tag_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/template_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/verify_cmd.py +2 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/watch_cmd.py +6 -2
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/constants.py +37 -9
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/core.py +5 -3
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/dotman_config.py +49 -76
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/files.py +133 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/global_config.py +59 -21
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/merge.py +4 -30
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/save_deploy_ops.py +105 -48
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/secrets.py +98 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/section.py +46 -20
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/status_ops.py +3 -3
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/PKG-INFO +35 -6
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/SOURCES.txt +10 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/requires.txt +3 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/pyproject.toml +4 -1
- dotman_git-1.2.0/tests/test_branch_ops.py +577 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_commands.py +1 -1
- dotman_git-1.2.0/tests/test_completions_cmd.py +197 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_comprehensive.py +2 -2
- dotman_git-1.2.0/tests/test_config_preservation.py +134 -0
- dotman_git-1.2.0/tests/test_core_extended.py +485 -0
- dotman_git-1.2.0/tests/test_custom_secrets.py +166 -0
- dotman_git-1.2.0/tests/test_encryption.py +338 -0
- dotman_git-1.2.0/tests/test_files_comprehensive.py +682 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_hooks.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_init_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_integration.py +4 -4
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_navigate_cmd.py +1 -1
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_profile_cmd_v1.py +82 -0
- dotman_git-1.2.0/tests/test_save_deploy_ops.py +562 -0
- dotman_git-1.2.0/tests/test_section.py +504 -0
- dotman_git-1.2.0/tests/test_shell_completions.py +136 -0
- dotman_git-1.2.0/tests/test_status_ops.py +356 -0
- dotman_git-1.2.0/tests/test_symlink_deploy.py +173 -0
- dotman_git-1.1.0/dot_man/cli/switch_cmd.py +0 -387
- dotman_git-1.1.0/tests/test_encryption.py +0 -96
- dotman_git-1.1.0/tests/test_section.py +0 -66
- {dotman_git-1.1.0 → dotman_git-1.2.0}/MANIFEST.in +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/backups.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/main.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/onboarding.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/show_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/tui_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/_dot-man.zsh +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/dot-man.bash +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/dot-man.fish +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/install.sh +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/config.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/config_detector.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/encryption.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/exceptions.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/hooks.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/interactive.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/lock.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/operations.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/py.typed +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/tui_log.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/ui.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/utils.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/vault.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/dependency_links.txt +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/entry_points.txt +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/top_level.txt +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/setup.cfg +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_add_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_audit_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_backup_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_backups.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_branch_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_clean.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_extra.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_help.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_revert.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_common_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_completion.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_cmd_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_ops_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_section.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_core.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_core_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_deploy_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_discover_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_doctor_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_dotman_config.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_edit_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_encrypt_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_exceptions.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_export_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_files_atomic.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_files_extended.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_final_push.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_fixtures.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_git_manager.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_git_manager_extended.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_global_config.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_hooks_new.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_import_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_interactive.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_lock.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_log_checkout_tag.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_log_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_log_cmd_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_merge.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_more_integration.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_navigate_switch_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_onboarding.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_operations.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_ops_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_performance_logic.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_profile_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_profile_extra.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_remote_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_restore_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_secrets.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_show_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_switch_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_switch_enhancements.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_tag_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_template_cmd.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_template_cmd_v1.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_template_extra.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_ui.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_utils.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_vault.py +0 -0
- {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_verify_cmd.py +0 -0
|
@@ -110,11 +110,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
110
110
|
- `checkout` → Use `navigate` instead
|
|
111
111
|
- `tag switch` → Use `navigate` instead
|
|
112
112
|
|
|
113
|
-
## [
|
|
113
|
+
## [1.2.0] - 2026-06-09
|
|
114
114
|
|
|
115
115
|
### Added
|
|
116
|
-
-
|
|
117
|
-
-
|
|
116
|
+
- **Generalized placeholder system in hooks** — `{qs_config}` replaced with `{config_name}`, `{config_root}`, `{section_name}`, `{paths}`, `{branch}`
|
|
117
|
+
- **Shell completions** — `encrypt <section>`, `export --branch`, `rollback <target>`
|
|
118
|
+
- **Custom Secret Patterns** — Support defining custom regex patterns for secret detection in config
|
|
119
|
+
- **Secret Scanner Customization** — `use_default_patterns = false` to disable built-in patterns
|
|
120
|
+
- **Symlink Deploy Mode** — per-section `deploy_method = "symlink"` for edit-in-place workflows
|
|
121
|
+
- **Quickshell Hook Aliases** — `quickshell_reload`, `quickshell_restart`, `quickshell_validate` with `{qs_config}` → `{config_name}` placeholder
|
|
122
|
+
- **`complete_sections()`** — shell completion callback for section names
|
|
118
123
|
|
|
119
124
|
### Changed
|
|
120
|
-
-
|
|
125
|
+
- **`switch_cmd` consolidated → thin wrapper** — `switch` is now a 17-line wrapper calling `_navigate_impl()` (~80% code reduction)
|
|
126
|
+
- **`BranchParamType` deduplicated** — moved from `switch_cmd.py`/`navigate_cmd.py` to `common.py`
|
|
127
|
+
- **Schema keys deduplicated** — `VALID_SECTION_KEYS` defined once in `dotman_config.py`
|
|
128
|
+
- **`HOOK_ALIASES` unified** — `constants.py` is canonical; `merge.py` imports from it
|
|
129
|
+
- **Silent `except: pass` cleanup** — `logging.debug()`/`logging.warning()` added to 11+ bare exceptions across `common.py`, `navigate_cmd.py`, `rollback_cmd.py`, `core.py`, `files.py`, `init_cmd.py`
|
|
130
|
+
- **Production `assert` statements removed** — all replaced with `if/error()` pattern in `navigate_cmd.py`, `encrypt_cmd.py`, `import_cmd.py`, `rollback_cmd.py`
|
|
131
|
+
- **Coverage improved from 60% → 66%** — 1146 tests (up from 1021), 1 skipped
|
|
132
|
+
|
|
133
|
+
### Fixed
|
|
134
|
+
- **Weak `>=` assertions** in `test_section.py`, `test_save_deploy_ops.py`, `test_status_ops.py`, `test_core_extended.py`, and 4 new test files
|
|
135
|
+
- **Missing f-string prefix** in `dotman_config.py:96`
|
|
136
|
+
- **Stale `dot-man.ini` reference** in `status_ops.py:158`
|
|
137
|
+
|
|
138
|
+
### New Tests
|
|
139
|
+
| File | Tests | Coverage Impact |
|
|
140
|
+
|------|-------|-----------------|
|
|
141
|
+
| `test_files_comprehensive.py` | 55 | `files.py` 73% → 94% |
|
|
142
|
+
| `test_branch_ops.py` | 39 | `branch_ops.py` 40% → 97% |
|
|
143
|
+
| `test_encryption.py` | 32 | `encryption.py` 33% → 100% |
|
|
144
|
+
| `test_completions_cmd.py` | 10 | `completions_cmd.py` 12% → 94% |
|
|
145
|
+
| `test_shell_completions.py` | 7 | Shell completions verified |
|
|
146
|
+
| `test_section.py` | 39 (+36) | `section.py` 70% → 95% |
|
|
147
|
+
| `test_save_deploy_ops.py` | 26 | `save_deploy_ops.py` 52% → 75% |
|
|
148
|
+
| `test_status_ops.py` | 17 | `status_ops.py` 59% → 89% |
|
|
149
|
+
| `test_core_extended.py` | 46 | `core.py` 58% → 66% |
|
|
150
|
+
|
|
151
|
+
## [1.1.1] - 2026-05-20
|
|
152
|
+
|
|
153
|
+
### Added
|
|
154
|
+
- **PyPI Publication** - Official release published to PyPI as `dotman-git`.
|
|
155
|
+
- **Command Aliases** - Short 3-letter command aliases added for all major operations:
|
|
156
|
+
- `nav` (navigate), `doc` (doctor), `dep` (deploy), `enc` (encrypt)
|
|
157
|
+
- `exp` (export), `imp` (import), `dis` (discover), `aud` (audit)
|
|
158
|
+
- `cln` (clean), `ver` (verify), `rev` (revert), `rol` (rollback)
|
|
159
|
+
- `wat` (watch), `cpl` (completions), `rst` (restore), `edt` (edit)
|
|
160
|
+
- `sta` (status), `ini` (init), `syn` (sync), `log` (log), `dif` (diff)
|
|
161
|
+
- `hks` (hooks)
|
|
162
|
+
- **`dot-man watch`** — Auto-save tracked dotfiles on change (supporting both watchdog and polling backends, debounced commits, and `--no-commit`/`--dry-run` flags).
|
|
163
|
+
- **`dot-man rollback`** — Transaction-style rollback to any previous commit, tag, or `HEAD~N`, with colored diffs and automatic pre-rollback backups.
|
|
164
|
+
- **YAML Comment Preservation** - Added round-trip YAML configuration comment and layout preservation using `ruamel.yaml`.
|
|
165
|
+
|
|
166
|
+
### Changed
|
|
167
|
+
- **README Overhaul** - Redesigned README with project badges, architecture diagrams, config examples (TOML/YAML), and command reference tables.
|
|
168
|
+
- **License & Copyright** - Updated license with additional copyright holder (ZVAXEROWS) and copyright years to 2025, 2026.
|
|
169
|
+
|
|
170
|
+
### Fixed
|
|
171
|
+
- **Profile Switch & Serialization** - Resolved a traceback in `profile switch` by properly parsing branch parameters for `ctx.invoke`.
|
|
172
|
+
- **Global Config Save** - Defaulted `GlobalConfig.save()` to write to disk (`force=True`) to prevent silent save failures across CLI commands.
|
|
173
|
+
- **TOML Serialization of None** - Prevented `tomlkit` ConvertError by omitting the `inherits` key if its value is `None` during profile creation.
|
|
174
|
+
- **YAML Configuration Overwrites** - Resolved a bug where editing configuration fields would silently convert YAML files back to TOML on save.
|
|
175
|
+
- **TOML/YAML Comment Preservation & Key Deletions** - Fixed a bug in document updates where deleted configuration keys were not removed from the saved file, and added value equality checks to prevent losing comments on sequence fields.
|
|
176
|
+
- **Integration Tests** - Added robust test cases covering the complete `profile switch` flow, validating branch switching, warnings, inheritance, and error handling.
|
|
@@ -27,7 +27,7 @@ pytest tests/ -v
|
|
|
27
27
|
dot-man/
|
|
28
28
|
├── dot_man/ # Main package (20 modules)
|
|
29
29
|
│ ├── __init__.py # Package version
|
|
30
|
-
│ ├── cli/ # CLI commands (Click-based,
|
|
30
|
+
│ ├── cli/ # CLI commands (Click-based, 28 files)
|
|
31
31
|
│ │ ├── main.py # Entry point: calls cli()
|
|
32
32
|
│ │ ├── interface.py # Click group definition (DotManGroup)
|
|
33
33
|
│ │ ├── common.py # Shared utilities: require_init, completions,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2025 BeshoyEhab
|
|
3
|
+
Copyright (c) 2025, 2026 BeshoyEhab, ZVAXEROWS
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dotman-git
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Dotfile manager with git-powered branching
|
|
5
5
|
Author-email: Bishoy Ehab <beshoyehabhhmm@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -38,6 +38,7 @@ Requires-Dist: tomlkit>=0.12.0
|
|
|
38
38
|
Requires-Dist: questionary>=2.0.0
|
|
39
39
|
Provides-Extra: yaml
|
|
40
40
|
Requires-Dist: pyyaml>=6.0; extra == "yaml"
|
|
41
|
+
Requires-Dist: ruamel.yaml>=0.17.0; extra == "yaml"
|
|
41
42
|
Provides-Extra: tui
|
|
42
43
|
Requires-Dist: textual>=0.73.0; extra == "tui"
|
|
43
44
|
Provides-Extra: watch
|
|
@@ -45,11 +46,13 @@ Requires-Dist: watchdog>=4.0; extra == "watch"
|
|
|
45
46
|
Provides-Extra: dev
|
|
46
47
|
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
47
48
|
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
|
|
48
50
|
Requires-Dist: black>=24.8.0; extra == "dev"
|
|
49
51
|
Requires-Dist: mypy>=1.14.1; extra == "dev"
|
|
50
52
|
Requires-Dist: textual>=0.73.0; extra == "dev"
|
|
51
53
|
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
52
54
|
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
55
|
+
Requires-Dist: ruamel.yaml>=0.17.0; extra == "dev"
|
|
53
56
|
Dynamic: license-file
|
|
54
57
|
|
|
55
58
|
<div align="center">
|
|
@@ -66,7 +69,7 @@ Dynamic: license-file
|
|
|
66
69
|
[](https://github.com/psf/black)
|
|
67
70
|
[](https://github.com/astral-sh/ruff)
|
|
68
71
|
[](https://mypy-lang.org/)
|
|
69
|
-
[](https://github.com/BeshoyEhab/dot-man)
|
|
70
73
|
[](https://github.com/BeshoyEhab/dot-man/pulls)
|
|
71
74
|
|
|
72
75
|
---
|
|
@@ -88,7 +91,7 @@ Your Machine dot-man Repo Any Machine
|
|
|
88
91
|
────────────── ──────────── ───────────
|
|
89
92
|
~/.bashrc ──── save ──► branch: main ── deploy ──► ~/.bashrc
|
|
90
93
|
~/.config/nvim ──── save ──► branch: work ── deploy ──► ~/.config/nvim
|
|
91
|
-
~/.gitconfig ──── save ──► branch: server
|
|
94
|
+
~/.gitconfig ──── save ──► branch: server ~/.gitconfig
|
|
92
95
|
```
|
|
93
96
|
|
|
94
97
|
**Secrets are never committed.** API keys, tokens and passwords are automatically detected, encrypted locally in a vault, and replaced with hashes in the repository before any commit.
|
|
@@ -178,6 +181,7 @@ dot-man add ~/.config/nvim
|
|
|
178
181
|
|
|
179
182
|
# 3. Create a "work" profile (branch)
|
|
180
183
|
dot-man navigate work
|
|
184
|
+
# btw it was "switch" instead of "navigate" untill Bishoy decided to change it to "navigate" for some silly reasons and push that changes so as community help us to make it "switch" again.
|
|
181
185
|
|
|
182
186
|
# 4. Edit your work-specific configs, then switch back — changes are saved automatically
|
|
183
187
|
dot-man navigate main
|
|
@@ -413,6 +417,31 @@ system file: api_key = "sk-abc123..." ← restored on deploy
|
|
|
413
417
|
|
|
414
418
|
Run `dot-man audit` to scan at any time. Use `dot-man audit --strict` in CI/CD pipelines.
|
|
415
419
|
|
|
420
|
+
### Custom Secret Patterns
|
|
421
|
+
|
|
422
|
+
You can define custom regular expressions to match sensitive patterns, either globally in `~/.config/dot-man/global.toml` or for a specific repository in `dot-man.toml`.
|
|
423
|
+
|
|
424
|
+
#### Global custom secrets (`global.toml`)
|
|
425
|
+
```toml
|
|
426
|
+
[security]
|
|
427
|
+
use_default_patterns = true # Set to false to disable built-in patterns
|
|
428
|
+
|
|
429
|
+
[[security.patterns]]
|
|
430
|
+
name = "My Custom API Token"
|
|
431
|
+
pattern = 'my_api_key_[a-zA-Z0-9]{32}'
|
|
432
|
+
severity = "HIGH"
|
|
433
|
+
description = "Internal service API key"
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
#### Repository custom secrets (`dot-man.toml`)
|
|
437
|
+
```toml
|
|
438
|
+
[secrets]
|
|
439
|
+
use_default_patterns = true # Set to false to disable built-in patterns
|
|
440
|
+
patterns = [
|
|
441
|
+
{ name = "Special Repo Key", pattern = 'special_[0-9]+', severity = "CRITICAL" }
|
|
442
|
+
]
|
|
443
|
+
```
|
|
444
|
+
|
|
416
445
|
---
|
|
417
446
|
|
|
418
447
|
## Multi-Machine Profiles
|
|
@@ -451,10 +480,10 @@ System variables (`{{HOSTNAME}}`, `{{USER}}`, `{{SHELL}}`, etc.) are auto-popula
|
|
|
451
480
|
|
|
452
481
|
| Metric | Value |
|
|
453
482
|
|--------|-------|
|
|
454
|
-
| Version | `1.1.
|
|
483
|
+
| Version | `1.1.1` |
|
|
455
484
|
| Python | `3.9+` |
|
|
456
485
|
| Platforms | Linux, macOS |
|
|
457
|
-
| Test Coverage |
|
|
486
|
+
| Test Coverage | 59% (869 tests) |
|
|
458
487
|
| Commands | 30+ |
|
|
459
488
|
| PyPI | [`dotman-git`](https://pypi.org/project/dotman-git/) |
|
|
460
489
|
|
|
@@ -504,4 +533,4 @@ All contributions must pass the pre-push quality checklist: `black`, `ruff`, `my
|
|
|
504
533
|
|
|
505
534
|
## License
|
|
506
535
|
|
|
507
|
-
[MIT](LICENSE) © [Bishoy Ehab](https://github.com/BeshoyEhab)
|
|
536
|
+
[MIT](LICENSE) © [Bishoy Ehab](https://github.com/BeshoyEhab) & [ZVAXEROWS](https://github.com/ZVAXEROWS)
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://github.com/psf/black)
|
|
13
13
|
[](https://github.com/astral-sh/ruff)
|
|
14
14
|
[](https://mypy-lang.org/)
|
|
15
|
-
[](https://github.com/BeshoyEhab/dot-man)
|
|
16
16
|
[](https://github.com/BeshoyEhab/dot-man/pulls)
|
|
17
17
|
|
|
18
18
|
---
|
|
@@ -34,7 +34,7 @@ Your Machine dot-man Repo Any Machine
|
|
|
34
34
|
────────────── ──────────── ───────────
|
|
35
35
|
~/.bashrc ──── save ──► branch: main ── deploy ──► ~/.bashrc
|
|
36
36
|
~/.config/nvim ──── save ──► branch: work ── deploy ──► ~/.config/nvim
|
|
37
|
-
~/.gitconfig ──── save ──► branch: server
|
|
37
|
+
~/.gitconfig ──── save ──► branch: server ~/.gitconfig
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
**Secrets are never committed.** API keys, tokens and passwords are automatically detected, encrypted locally in a vault, and replaced with hashes in the repository before any commit.
|
|
@@ -124,6 +124,7 @@ dot-man add ~/.config/nvim
|
|
|
124
124
|
|
|
125
125
|
# 3. Create a "work" profile (branch)
|
|
126
126
|
dot-man navigate work
|
|
127
|
+
# btw it was "switch" instead of "navigate" untill Bishoy decided to change it to "navigate" for some silly reasons and push that changes so as community help us to make it "switch" again.
|
|
127
128
|
|
|
128
129
|
# 4. Edit your work-specific configs, then switch back — changes are saved automatically
|
|
129
130
|
dot-man navigate main
|
|
@@ -359,6 +360,31 @@ system file: api_key = "sk-abc123..." ← restored on deploy
|
|
|
359
360
|
|
|
360
361
|
Run `dot-man audit` to scan at any time. Use `dot-man audit --strict` in CI/CD pipelines.
|
|
361
362
|
|
|
363
|
+
### Custom Secret Patterns
|
|
364
|
+
|
|
365
|
+
You can define custom regular expressions to match sensitive patterns, either globally in `~/.config/dot-man/global.toml` or for a specific repository in `dot-man.toml`.
|
|
366
|
+
|
|
367
|
+
#### Global custom secrets (`global.toml`)
|
|
368
|
+
```toml
|
|
369
|
+
[security]
|
|
370
|
+
use_default_patterns = true # Set to false to disable built-in patterns
|
|
371
|
+
|
|
372
|
+
[[security.patterns]]
|
|
373
|
+
name = "My Custom API Token"
|
|
374
|
+
pattern = 'my_api_key_[a-zA-Z0-9]{32}'
|
|
375
|
+
severity = "HIGH"
|
|
376
|
+
description = "Internal service API key"
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
#### Repository custom secrets (`dot-man.toml`)
|
|
380
|
+
```toml
|
|
381
|
+
[secrets]
|
|
382
|
+
use_default_patterns = true # Set to false to disable built-in patterns
|
|
383
|
+
patterns = [
|
|
384
|
+
{ name = "Special Repo Key", pattern = 'special_[0-9]+', severity = "CRITICAL" }
|
|
385
|
+
]
|
|
386
|
+
```
|
|
387
|
+
|
|
362
388
|
---
|
|
363
389
|
|
|
364
390
|
## Multi-Machine Profiles
|
|
@@ -397,10 +423,10 @@ System variables (`{{HOSTNAME}}`, `{{USER}}`, `{{SHELL}}`, etc.) are auto-popula
|
|
|
397
423
|
|
|
398
424
|
| Metric | Value |
|
|
399
425
|
|--------|-------|
|
|
400
|
-
| Version | `1.1.
|
|
426
|
+
| Version | `1.1.1` |
|
|
401
427
|
| Python | `3.9+` |
|
|
402
428
|
| Platforms | Linux, macOS |
|
|
403
|
-
| Test Coverage |
|
|
429
|
+
| Test Coverage | 59% (869 tests) |
|
|
404
430
|
| Commands | 30+ |
|
|
405
431
|
| PyPI | [`dotman-git`](https://pypi.org/project/dotman-git/) |
|
|
406
432
|
|
|
@@ -450,4 +476,4 @@ All contributions must pass the pre-push quality checklist: `black`, `ruff`, `my
|
|
|
450
476
|
|
|
451
477
|
## License
|
|
452
478
|
|
|
453
|
-
[MIT](LICENSE) © [Bishoy Ehab](https://github.com/BeshoyEhab)
|
|
479
|
+
[MIT](LICENSE) © [Bishoy Ehab](https://github.com/BeshoyEhab) & [ZVAXEROWS](https://github.com/ZVAXEROWS)
|
|
@@ -131,6 +131,13 @@ class BranchMixin:
|
|
|
131
131
|
result["saved_count"] = save_result["saved"]
|
|
132
132
|
result["secrets_redacted"] = len(save_result["secrets"])
|
|
133
133
|
result["errors"].extend(save_result["errors"])
|
|
134
|
+
# Warn about symlinked paths
|
|
135
|
+
symlinks: list[Path] = save_result.get("symlinks", [])
|
|
136
|
+
for sym_path in symlinks:
|
|
137
|
+
result["errors"].append(
|
|
138
|
+
f"⚠ {sym_path} is a symlink → {sym_path.resolve()}. "
|
|
139
|
+
f"Edits affect the symlink target, not the config folder."
|
|
140
|
+
)
|
|
134
141
|
|
|
135
142
|
commit_msg = f"Auto-save from '{current_branch}' before switch to '{target_branch}'"
|
|
136
143
|
self.git.commit(commit_msg)
|
|
@@ -195,7 +202,7 @@ class BranchMixin:
|
|
|
195
202
|
self.global_config.current_branch = target_branch
|
|
196
203
|
self.global_config.save()
|
|
197
204
|
|
|
198
|
-
|
|
205
|
+
# Clear file comparison cache since files have likely changed
|
|
199
206
|
clear_comparison_cache()
|
|
200
207
|
|
|
201
208
|
return result
|
|
@@ -299,9 +306,17 @@ class BranchMixin:
|
|
|
299
306
|
|
|
300
307
|
for pattern, hook_name in FILE_TO_HOOK_MAP.items():
|
|
301
308
|
if pattern in file_str or file_str.endswith(pattern):
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
309
|
+
# Try to resolve the hook alias first
|
|
310
|
+
from .constants import HOOK_ALIASES
|
|
311
|
+
|
|
312
|
+
if hook_name in HOOK_ALIASES:
|
|
313
|
+
resolved_cmd: str = HOOK_ALIASES[hook_name]
|
|
314
|
+
hooks_to_run.add(resolved_cmd)
|
|
315
|
+
else:
|
|
316
|
+
# Fallback to get_hook_for_config
|
|
317
|
+
fallback_cmd = get_hook_for_config(pattern)
|
|
318
|
+
if fallback_cmd:
|
|
319
|
+
hooks_to_run.add(fallback_cmd)
|
|
305
320
|
break
|
|
306
321
|
|
|
307
322
|
section_name = self._find_section_for_file(file_path)
|
|
@@ -10,6 +10,7 @@ from .backup_cmd import backup
|
|
|
10
10
|
from .branch_cmd import branch
|
|
11
11
|
from .clean_cmd import clean
|
|
12
12
|
from .common import (
|
|
13
|
+
BRANCH,
|
|
13
14
|
DotManGroup,
|
|
14
15
|
_clear_completion_cache,
|
|
15
16
|
_set_git_runner,
|
|
@@ -17,6 +18,7 @@ from .common import (
|
|
|
17
18
|
complete_commits,
|
|
18
19
|
complete_config_keys,
|
|
19
20
|
complete_profiles,
|
|
21
|
+
complete_sections,
|
|
20
22
|
complete_switch_args,
|
|
21
23
|
complete_tags,
|
|
22
24
|
complete_template_keys,
|
|
@@ -72,7 +74,9 @@ __all__ = [
|
|
|
72
74
|
"complete_template_keys",
|
|
73
75
|
"complete_config_keys",
|
|
74
76
|
"complete_profiles",
|
|
77
|
+
"complete_sections",
|
|
75
78
|
"complete_switch_args",
|
|
79
|
+
"BRANCH",
|
|
76
80
|
"parse_branch_arg",
|
|
77
81
|
"get_secret_handler",
|
|
78
82
|
"_set_git_runner",
|
|
@@ -9,11 +9,18 @@ from ..config import DotManConfig, GlobalConfig
|
|
|
9
9
|
from ..constants import REPO_DIR
|
|
10
10
|
from ..exceptions import DotManError
|
|
11
11
|
from ..files import copy_directory, copy_file
|
|
12
|
-
from .common import
|
|
12
|
+
from .common import (
|
|
13
|
+
AliasedCommand,
|
|
14
|
+
error,
|
|
15
|
+
get_secret_handler,
|
|
16
|
+
require_init,
|
|
17
|
+
success,
|
|
18
|
+
warn,
|
|
19
|
+
)
|
|
13
20
|
from .interface import cli as main
|
|
14
21
|
|
|
15
22
|
|
|
16
|
-
@main.command()
|
|
23
|
+
@main.command("add", cls=AliasedCommand, aliases=["add"])
|
|
17
24
|
@click.argument("path", type=click.Path(exists=True))
|
|
18
25
|
@click.option(
|
|
19
26
|
"--section", "-s", help="Section name (default: auto-generated from path)"
|
|
@@ -6,12 +6,12 @@ from .. import ui
|
|
|
6
6
|
from ..constants import REPO_DIR
|
|
7
7
|
from ..core import GitManager
|
|
8
8
|
from ..exceptions import DotManError
|
|
9
|
-
from ..secrets import PermanentRedactGuard, SecretGuard, SecretMatch,
|
|
10
|
-
from .common import error, handle_exception, require_init, success
|
|
9
|
+
from ..secrets import PermanentRedactGuard, SecretGuard, SecretMatch, get_custom_scanner
|
|
10
|
+
from .common import AliasedCommand, error, handle_exception, require_init, success
|
|
11
11
|
from .interface import cli as main
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
@main.command()
|
|
14
|
+
@main.command("audit", cls=AliasedCommand, aliases=["aud"])
|
|
15
15
|
@click.option(
|
|
16
16
|
"--strict", is_flag=True, help="Exit with error if secrets found (for CI/CD)"
|
|
17
17
|
)
|
|
@@ -26,7 +26,7 @@ def audit(strict: bool, fix: bool):
|
|
|
26
26
|
Use --strict in CI/CD pipelines to fail builds if secrets are found.
|
|
27
27
|
"""
|
|
28
28
|
try:
|
|
29
|
-
scanner =
|
|
29
|
+
scanner = get_custom_scanner()
|
|
30
30
|
guard = SecretGuard()
|
|
31
31
|
permanent_guard = PermanentRedactGuard()
|
|
32
32
|
|
|
@@ -4,11 +4,11 @@ import click
|
|
|
4
4
|
|
|
5
5
|
from .. import ui
|
|
6
6
|
from ..constants import REPO_DIR
|
|
7
|
-
from .common import error, require_init, success
|
|
7
|
+
from .common import AliasedCommand, error, require_init, success
|
|
8
8
|
from .interface import cli as main
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@main.command("clean")
|
|
11
|
+
@main.command("clean", cls=AliasedCommand, aliases=["cln"])
|
|
12
12
|
@click.option("--backups", is_flag=True, help="Clean old backups")
|
|
13
13
|
@click.option("--orphans", is_flag=True, help="Clean orphaned files from repo")
|
|
14
14
|
@click.option("--all", "clean_all", is_flag=True, help="Clean both backups and orphans")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Common utilities for dot-man CLI commands."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import logging
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
6
7
|
import subprocess
|
|
@@ -57,9 +58,50 @@ def handle_exception(exc: BaseException, context: str = "Operation") -> None:
|
|
|
57
58
|
raise SystemExit(1)
|
|
58
59
|
|
|
59
60
|
|
|
61
|
+
class AliasedCommand(click.Command):
|
|
62
|
+
"""Custom Command class that supports aliases."""
|
|
63
|
+
|
|
64
|
+
def __init__(self, *args, aliases=None, **kwargs):
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
self._aliases = aliases or []
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def aliases(self):
|
|
70
|
+
return self._aliases
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def command(name=None, cls=None, aliases=None, **kwargs):
|
|
74
|
+
"""Decorator to create a command with optional aliases.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
name: Command name (default: function name)
|
|
78
|
+
cls: Command class (default: AliasedCommand)
|
|
79
|
+
aliases: List of alias names for the command
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
@command("navigate", aliases=["nav", "go"])
|
|
83
|
+
def navigate():
|
|
84
|
+
...
|
|
85
|
+
"""
|
|
86
|
+
if cls is None:
|
|
87
|
+
cls = AliasedCommand
|
|
88
|
+
|
|
89
|
+
def decorator(f):
|
|
90
|
+
return click.command(name=name, cls=cls, aliases=aliases, **kwargs)(f)
|
|
91
|
+
|
|
92
|
+
return decorator
|
|
93
|
+
|
|
94
|
+
|
|
60
95
|
class DotManGroup(click.Group):
|
|
61
96
|
"""Custom Click Group to provide suggestions for typos."""
|
|
62
97
|
|
|
98
|
+
def add_command(self, command, name=None):
|
|
99
|
+
name = name or command.name
|
|
100
|
+
super().add_command(command, name)
|
|
101
|
+
if hasattr(command, "aliases") and command.aliases:
|
|
102
|
+
for alias in command.aliases:
|
|
103
|
+
super().add_command(command, alias)
|
|
104
|
+
|
|
63
105
|
def get_command(self, ctx, cmd_name):
|
|
64
106
|
rv = click.Group.get_command(self, ctx, cmd_name)
|
|
65
107
|
if rv is not None:
|
|
@@ -86,6 +128,20 @@ def require_init(func):
|
|
|
86
128
|
ui.console.print()
|
|
87
129
|
ui.console.print("[bold]The Dotfile Manager for Professionals[/bold]")
|
|
88
130
|
ui.console.print()
|
|
131
|
+
ui.console.print("[bold cyan]Coming from another tool?[/bold cyan]")
|
|
132
|
+
ui.console.print(
|
|
133
|
+
" [cyan]dot-man import chezmoi[/cyan] - Import from chezmoi"
|
|
134
|
+
)
|
|
135
|
+
ui.console.print(
|
|
136
|
+
" [cyan]dot-man import yadm[/cyan] - Import from yadm"
|
|
137
|
+
)
|
|
138
|
+
ui.console.print(
|
|
139
|
+
" [cyan]dot-man import stow[/cyan] - Import from GNU Stow"
|
|
140
|
+
)
|
|
141
|
+
ui.console.print(
|
|
142
|
+
" [cyan]dot-man import all[/cyan] - Auto-detect and import"
|
|
143
|
+
)
|
|
144
|
+
ui.console.print()
|
|
89
145
|
ui.console.print("[bold cyan]Get started:[/bold cyan]")
|
|
90
146
|
ui.console.print(
|
|
91
147
|
" [cyan]dot-man init[/cyan] - Initialize your dotfiles repository"
|
|
@@ -173,6 +229,7 @@ def _get_completion_cache() -> dict:
|
|
|
173
229
|
_memory_cache_time = time.time()
|
|
174
230
|
return _memory_cache
|
|
175
231
|
except Exception:
|
|
232
|
+
logging.debug("Failed to load completion cache from file")
|
|
176
233
|
pass
|
|
177
234
|
|
|
178
235
|
_memory_cache = {}
|
|
@@ -192,6 +249,7 @@ def _save_completion_cache(data: dict) -> None:
|
|
|
192
249
|
_COMPLETION_CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
193
250
|
_COMPLETION_CACHE_FILE.write_text(json.dumps(data))
|
|
194
251
|
except Exception:
|
|
252
|
+
logging.debug("Failed to save completion cache to file")
|
|
195
253
|
pass
|
|
196
254
|
|
|
197
255
|
|
|
@@ -205,6 +263,9 @@ def _clear_completion_cache() -> None:
|
|
|
205
263
|
if _COMPLETION_CACHE_FILE.exists():
|
|
206
264
|
_COMPLETION_CACHE_FILE.unlink()
|
|
207
265
|
except Exception:
|
|
266
|
+
logging.debug(
|
|
267
|
+
"Failed to unlink completion cache file in _clear_completion_cache"
|
|
268
|
+
)
|
|
208
269
|
pass
|
|
209
270
|
|
|
210
271
|
|
|
@@ -223,6 +284,7 @@ def _clear_all_caches() -> None:
|
|
|
223
284
|
if _COMPLETION_CACHE_FILE.exists():
|
|
224
285
|
_COMPLETION_CACHE_FILE.unlink()
|
|
225
286
|
except Exception:
|
|
287
|
+
logging.debug("Failed to unlink completion cache file in _clear_all_caches")
|
|
226
288
|
pass
|
|
227
289
|
|
|
228
290
|
|
|
@@ -263,6 +325,20 @@ def parse_branch_arg(arg: str) -> dict:
|
|
|
263
325
|
return {"type": "branch", "base": arg, "target": arg}
|
|
264
326
|
|
|
265
327
|
|
|
328
|
+
class BranchParamType(click.ParamType):
|
|
329
|
+
"""Parameter type that accepts branch, branch@tag, or commit SHA."""
|
|
330
|
+
|
|
331
|
+
name = "branch"
|
|
332
|
+
|
|
333
|
+
def convert(self, value, param, ctx):
|
|
334
|
+
if not value:
|
|
335
|
+
return None
|
|
336
|
+
return parse_branch_arg(value)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
BRANCH = BranchParamType()
|
|
340
|
+
|
|
341
|
+
|
|
266
342
|
def complete_switch_args(ctx, param, incomplete):
|
|
267
343
|
"""Shell completion callback for switch (branches, tags, commits).
|
|
268
344
|
|
|
@@ -378,6 +454,7 @@ def _complete_navigate_items(
|
|
|
378
454
|
_save_completion_cache(cache)
|
|
379
455
|
return items
|
|
380
456
|
except Exception:
|
|
457
|
+
logging.debug("Failed to complete navigate items")
|
|
381
458
|
return []
|
|
382
459
|
|
|
383
460
|
|
|
@@ -396,6 +473,7 @@ def complete_branches(ctx, param, incomplete):
|
|
|
396
473
|
branches = cache["branches"]
|
|
397
474
|
return [b for b in branches if b.startswith(incomplete)]
|
|
398
475
|
except Exception:
|
|
476
|
+
logging.debug("Failed to complete branches")
|
|
399
477
|
return []
|
|
400
478
|
|
|
401
479
|
|
|
@@ -412,6 +490,7 @@ def complete_tags(ctx, param, incomplete):
|
|
|
412
490
|
tags = cache["tags"]
|
|
413
491
|
return [t for t in tags if t.startswith(incomplete)]
|
|
414
492
|
except Exception:
|
|
493
|
+
logging.debug("Failed to complete tags")
|
|
415
494
|
return []
|
|
416
495
|
|
|
417
496
|
|
|
@@ -432,6 +511,7 @@ def complete_commits(ctx, param, incomplete):
|
|
|
432
511
|
commits = cache["commits_all"]
|
|
433
512
|
return [c for c in commits if c.startswith(incomplete)]
|
|
434
513
|
except Exception:
|
|
514
|
+
logging.debug("Failed to complete commits")
|
|
435
515
|
return []
|
|
436
516
|
|
|
437
517
|
|
|
@@ -450,6 +530,7 @@ def complete_template_keys(ctx, param, incomplete):
|
|
|
450
530
|
_template_cache = list(templates.keys())
|
|
451
531
|
return [k for k in _template_cache if k.startswith(incomplete)]
|
|
452
532
|
except Exception:
|
|
533
|
+
logging.debug("Failed to complete template keys")
|
|
453
534
|
return []
|
|
454
535
|
|
|
455
536
|
|
|
@@ -471,6 +552,7 @@ def complete_config_keys(ctx, param, incomplete):
|
|
|
471
552
|
_config_keys_cache = keys
|
|
472
553
|
return [k for k in keys if k.startswith(incomplete)]
|
|
473
554
|
except Exception:
|
|
555
|
+
logging.debug("Failed to complete config keys")
|
|
474
556
|
return []
|
|
475
557
|
|
|
476
558
|
|
|
@@ -489,6 +571,22 @@ def complete_profiles(ctx, param, incomplete):
|
|
|
489
571
|
_profiles_cache = list(profiles.keys())
|
|
490
572
|
return [k for k in _profiles_cache if k.startswith(incomplete)]
|
|
491
573
|
except Exception:
|
|
574
|
+
logging.debug("Failed to complete profiles")
|
|
575
|
+
return []
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def complete_sections(ctx, param, incomplete):
|
|
579
|
+
"""Shell completion callback for section names."""
|
|
580
|
+
try:
|
|
581
|
+
from ..dotman_config import DotManConfig
|
|
582
|
+
|
|
583
|
+
config = DotManConfig()
|
|
584
|
+
config.load()
|
|
585
|
+
return [
|
|
586
|
+
name for name in config.get_section_names() if name.startswith(incomplete)
|
|
587
|
+
]
|
|
588
|
+
except Exception:
|
|
589
|
+
logging.debug("Failed to complete sections")
|
|
492
590
|
return []
|
|
493
591
|
|
|
494
592
|
|
|
@@ -5,10 +5,11 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
|
|
8
|
+
from .common import AliasedCommand
|
|
8
9
|
from .interface import cli as main
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
@main.command("completions")
|
|
12
|
+
@main.command("completions", cls=AliasedCommand, aliases=["cpl"])
|
|
12
13
|
@click.option(
|
|
13
14
|
"--shell",
|
|
14
15
|
type=click.Choice(["bash", "zsh", "fish", "all"]),
|