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.
Files changed (165) hide show
  1. {dotman_git-1.1.0 → dotman_git-1.2.0}/CHANGELOG.md +60 -4
  2. {dotman_git-1.1.0 → dotman_git-1.2.0}/CONTRIBUTING.md +1 -1
  3. {dotman_git-1.1.0 → dotman_git-1.2.0}/LICENSE +2 -2
  4. {dotman_git-1.1.0 → dotman_git-1.2.0}/PKG-INFO +35 -6
  5. {dotman_git-1.1.0 → dotman_git-1.2.0}/README.md +31 -5
  6. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/__init__.py +1 -1
  7. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/branch_ops.py +19 -4
  8. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/__init__.py +4 -0
  9. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/add_cmd.py +9 -2
  10. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/audit_cmd.py +4 -4
  11. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/backup_cmd.py +1 -1
  12. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/branch_cmd.py +1 -1
  13. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/clean_cmd.py +2 -2
  14. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/common.py +98 -0
  15. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/completions_cmd.py +2 -1
  16. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/config_cmd.py +1 -1
  17. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/deploy_cmd.py +2 -1
  18. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/discover_cmd.py +2 -2
  19. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/doctor_cmd.py +2 -2
  20. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/edit_cmd.py +2 -2
  21. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/encrypt_cmd.py +28 -8
  22. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/export_cmd.py +9 -2
  23. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/import_cmd.py +14 -8
  24. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/init_cmd.py +6 -4
  25. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/interface.py +10 -1
  26. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/log_cmd.py +14 -3
  27. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/navigate_cmd.py +36 -39
  28. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/profile_cmd.py +10 -4
  29. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/remote_cmd.py +3 -3
  30. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/restore_cmd.py +2 -2
  31. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/revert_cmd.py +2 -2
  32. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/rollback_cmd.py +16 -5
  33. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/status_cmd.py +4 -4
  34. dotman_git-1.2.0/dot_man/cli/switch_cmd.py +68 -0
  35. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/tag_cmd.py +1 -1
  36. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/template_cmd.py +1 -1
  37. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/verify_cmd.py +2 -2
  38. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/watch_cmd.py +6 -2
  39. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/constants.py +37 -9
  40. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/core.py +5 -3
  41. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/dotman_config.py +49 -76
  42. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/files.py +133 -0
  43. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/global_config.py +59 -21
  44. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/merge.py +4 -30
  45. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/save_deploy_ops.py +105 -48
  46. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/secrets.py +98 -1
  47. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/section.py +46 -20
  48. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/status_ops.py +3 -3
  49. {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/PKG-INFO +35 -6
  50. {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/SOURCES.txt +10 -0
  51. {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/requires.txt +3 -0
  52. {dotman_git-1.1.0 → dotman_git-1.2.0}/pyproject.toml +4 -1
  53. dotman_git-1.2.0/tests/test_branch_ops.py +577 -0
  54. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_commands.py +1 -1
  55. dotman_git-1.2.0/tests/test_completions_cmd.py +197 -0
  56. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_comprehensive.py +2 -2
  57. dotman_git-1.2.0/tests/test_config_preservation.py +134 -0
  58. dotman_git-1.2.0/tests/test_core_extended.py +485 -0
  59. dotman_git-1.2.0/tests/test_custom_secrets.py +166 -0
  60. dotman_git-1.2.0/tests/test_encryption.py +338 -0
  61. dotman_git-1.2.0/tests/test_files_comprehensive.py +682 -0
  62. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_hooks.py +1 -1
  63. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_init_cmd.py +1 -1
  64. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_integration.py +4 -4
  65. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_navigate_cmd.py +1 -1
  66. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_profile_cmd_v1.py +82 -0
  67. dotman_git-1.2.0/tests/test_save_deploy_ops.py +562 -0
  68. dotman_git-1.2.0/tests/test_section.py +504 -0
  69. dotman_git-1.2.0/tests/test_shell_completions.py +136 -0
  70. dotman_git-1.2.0/tests/test_status_ops.py +356 -0
  71. dotman_git-1.2.0/tests/test_symlink_deploy.py +173 -0
  72. dotman_git-1.1.0/dot_man/cli/switch_cmd.py +0 -387
  73. dotman_git-1.1.0/tests/test_encryption.py +0 -96
  74. dotman_git-1.1.0/tests/test_section.py +0 -66
  75. {dotman_git-1.1.0 → dotman_git-1.2.0}/MANIFEST.in +0 -0
  76. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/backups.py +0 -0
  77. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/main.py +0 -0
  78. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/onboarding.py +0 -0
  79. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/show_cmd.py +0 -0
  80. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/cli/tui_cmd.py +0 -0
  81. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/_dot-man.zsh +0 -0
  82. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/dot-man.bash +0 -0
  83. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/dot-man.fish +0 -0
  84. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/completions/install.sh +0 -0
  85. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/config.py +0 -0
  86. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/config_detector.py +0 -0
  87. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/encryption.py +0 -0
  88. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/exceptions.py +0 -0
  89. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/hooks.py +0 -0
  90. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/interactive.py +0 -0
  91. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/lock.py +0 -0
  92. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/operations.py +0 -0
  93. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/py.typed +0 -0
  94. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/tui_log.py +0 -0
  95. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/ui.py +0 -0
  96. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/utils.py +0 -0
  97. {dotman_git-1.1.0 → dotman_git-1.2.0}/dot_man/vault.py +0 -0
  98. {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/dependency_links.txt +0 -0
  99. {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/entry_points.txt +0 -0
  100. {dotman_git-1.1.0 → dotman_git-1.2.0}/dotman_git.egg-info/top_level.txt +0 -0
  101. {dotman_git-1.1.0 → dotman_git-1.2.0}/setup.cfg +0 -0
  102. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_add_cmd.py +0 -0
  103. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_audit_cmd.py +0 -0
  104. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_backup_cmd.py +0 -0
  105. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_backups.py +0 -0
  106. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_branch_cmd.py +0 -0
  107. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_clean.py +0 -0
  108. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_extra.py +0 -0
  109. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_help.py +0 -0
  110. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_revert.py +0 -0
  111. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_cli_v1.py +0 -0
  112. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_common_v1.py +0 -0
  113. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_completion.py +0 -0
  114. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config.py +0 -0
  115. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_cmd.py +0 -0
  116. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_cmd_v1.py +0 -0
  117. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_ops_v1.py +0 -0
  118. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_config_section.py +0 -0
  119. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_core.py +0 -0
  120. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_core_v1.py +0 -0
  121. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_deploy_cmd.py +0 -0
  122. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_discover_cmd.py +0 -0
  123. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_doctor_cmd.py +0 -0
  124. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_dotman_config.py +0 -0
  125. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_edit_cmd.py +0 -0
  126. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_encrypt_cmd.py +0 -0
  127. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_exceptions.py +0 -0
  128. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_export_cmd.py +0 -0
  129. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_files_atomic.py +0 -0
  130. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_files_extended.py +0 -0
  131. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_final_push.py +0 -0
  132. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_fixtures.py +0 -0
  133. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_git_manager.py +0 -0
  134. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_git_manager_extended.py +0 -0
  135. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_global_config.py +0 -0
  136. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_hooks_new.py +0 -0
  137. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_import_cmd.py +0 -0
  138. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_interactive.py +0 -0
  139. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_lock.py +0 -0
  140. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_log_checkout_tag.py +0 -0
  141. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_log_cmd.py +0 -0
  142. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_log_cmd_v1.py +0 -0
  143. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_merge.py +0 -0
  144. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_more_integration.py +0 -0
  145. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_navigate_switch_v1.py +0 -0
  146. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_onboarding.py +0 -0
  147. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_operations.py +0 -0
  148. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_ops_v1.py +0 -0
  149. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_performance_logic.py +0 -0
  150. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_profile_cmd.py +0 -0
  151. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_profile_extra.py +0 -0
  152. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_remote_cmd.py +0 -0
  153. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_restore_cmd.py +0 -0
  154. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_secrets.py +0 -0
  155. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_show_cmd.py +0 -0
  156. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_switch_cmd.py +0 -0
  157. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_switch_enhancements.py +0 -0
  158. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_tag_cmd.py +0 -0
  159. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_template_cmd.py +0 -0
  160. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_template_cmd_v1.py +0 -0
  161. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_template_extra.py +0 -0
  162. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_ui.py +0 -0
  163. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_utils.py +0 -0
  164. {dotman_git-1.1.0 → dotman_git-1.2.0}/tests/test_vault.py +0 -0
  165. {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
- ## [Unreleased]
113
+ ## [1.2.0] - 2026-06-09
114
114
 
115
115
  ### Added
116
- - **`dot-man watch`** auto-save tracked dotfiles on change (watchdog or polling backend, debounced commits, `--no-commit` / `--dry-run` flags)
117
- - **`dot-man rollback`**transaction-style rollback to any previous commit, tag, or `HEAD~N`; shows coloured diff, auto-backs up before rolling back
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
- - README completely overhauled: badges, shields, architecture diagram, full command reference tables, TOML/YAML config examples
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, 24 files)
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.1.0
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
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
67
70
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
68
71
  [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
69
- [![Coverage](https://img.shields.io/badge/coverage-61%25-yellow)](https://github.com/BeshoyEhab/dot-man)
72
+ [![Coverage](https://img.shields.io/badge/coverage-60%25-green)](https://github.com/BeshoyEhab/dot-man)
70
73
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 ~/.gitconfig
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.0` |
483
+ | Version | `1.1.1` |
455
484
  | Python | `3.9+` |
456
485
  | Platforms | Linux, macOS |
457
- | Test Coverage | 61% |
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
  [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
13
13
  [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
14
14
  [![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
15
- [![Coverage](https://img.shields.io/badge/coverage-61%25-yellow)](https://github.com/BeshoyEhab/dot-man)
15
+ [![Coverage](https://img.shields.io/badge/coverage-60%25-green)](https://github.com/BeshoyEhab/dot-man)
16
16
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](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 ~/.gitconfig
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.0` |
426
+ | Version | `1.1.1` |
401
427
  | Python | `3.9+` |
402
428
  | Platforms | Linux, macOS |
403
- | Test Coverage | 61% |
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)
@@ -1,4 +1,4 @@
1
1
  """dot-man: Dotfile manager with git-powered branching."""
2
2
 
3
- __version__ = "1.1.0"
3
+ __version__ = "1.2.0"
4
4
  __author__ = "Bishoy Ehab"
@@ -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
- # Clear file comparison cache since files have likely changed
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
- hook_cmd = get_hook_for_config(pattern)
303
- if hook_cmd:
304
- hooks_to_run.add(hook_cmd)
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 error, get_secret_handler, require_init, success, warn
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, SecretScanner
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 = SecretScanner()
29
+ scanner = get_custom_scanner()
30
30
  guard = SecretGuard()
31
31
  permanent_guard = PermanentRedactGuard()
32
32
 
@@ -8,7 +8,7 @@ from .common import error, require_init, success
8
8
  from .interface import cli as main
9
9
 
10
10
 
11
- @main.group()
11
+ @main.group("backup")
12
12
  def backup():
13
13
  """Manage local safety backups."""
14
14
  pass
@@ -11,7 +11,7 @@ from .common import complete_branches, error, handle_exception, require_init, su
11
11
  from .interface import cli as main
12
12
 
13
13
 
14
- @main.group()
14
+ @main.group("branch")
15
15
  def branch():
16
16
  """Manage configuration branches."""
17
17
  pass
@@ -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"]),
@@ -12,7 +12,7 @@ from .common import complete_config_keys, error, require_init, success
12
12
  from .interface import cli as main
13
13
 
14
14
 
15
- @main.group()
15
+ @main.group("config")
16
16
  def config():
17
17
  """Manage global configuration."""
18
18
  pass