git-repokit 0.9.10__py3-none-any.whl
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.
- git_repokit-0.9.10.dist-info/METADATA +422 -0
- git_repokit-0.9.10.dist-info/RECORD +174 -0
- git_repokit-0.9.10.dist-info/WHEEL +5 -0
- git_repokit-0.9.10.dist-info/entry_points.txt +2 -0
- git_repokit-0.9.10.dist-info/licenses/LICENSE +9 -0
- git_repokit-0.9.10.dist-info/top_level.txt +1 -0
- repokit/__init__.py +8 -0
- repokit/__main__.py +12 -0
- repokit/__version__.py +4 -0
- repokit/_vendor/__init__.py +0 -0
- repokit/_vendor/ailib/__init__.py +1 -0
- repokit/_vendor/ailib/analyzer.py +177 -0
- repokit/_vendor/ailib/backend.py +95 -0
- repokit/_vendor/ailib/cache.py +97 -0
- repokit/_vendor/ailib/parsers.py +52 -0
- repokit/_vendor/ailib/transports/__init__.py +16 -0
- repokit/_vendor/ailib/transports/cli.py +312 -0
- repokit/_vendor/ailib/transports/openai_compat.py +383 -0
- repokit/_vendor/ailib/transports/prompt_file.py +41 -0
- repokit/_vendor/ailib/types.py +224 -0
- repokit/auth_integration.py +244 -0
- repokit/badges.py +183 -0
- repokit/branch_utils.py +364 -0
- repokit/cli.py +2997 -0
- repokit/common_kit.py +202 -0
- repokit/config.py +686 -0
- repokit/defaults.py +220 -0
- repokit/directory_analyzer.py +1244 -0
- repokit/directory_profiles.py +423 -0
- repokit/git_config.py +370 -0
- repokit/github_create.py +142 -0
- repokit/github_setup.py +248 -0
- repokit/guardrails.py +707 -0
- repokit/history_cleaner.py +449 -0
- repokit/history_protection.py +434 -0
- repokit/hooks/pre-commit-guardrails +122 -0
- repokit/hooks/pre-commit-template +28 -0
- repokit/layers.py +510 -0
- repokit/licenses/0bsd.txt +14 -0
- repokit/licenses/LICENSES.md +27 -0
- repokit/licenses/__init__.py +142 -0
- repokit/licenses/agpl-3.0.txt +661 -0
- repokit/licenses/all-rights-reserved.txt +10 -0
- repokit/licenses/apache-2.0.txt +201 -0
- repokit/licenses/bsd-2-clause.txt +24 -0
- repokit/licenses/bsd-3-clause.txt +28 -0
- repokit/licenses/bsl-1.0.txt +23 -0
- repokit/licenses/cc-by-4.0.txt +395 -0
- repokit/licenses/cc-by-sa-4.0.txt +427 -0
- repokit/licenses/cc0-1.0.txt +121 -0
- repokit/licenses/epl-2.0.txt +277 -0
- repokit/licenses/gpl-2.0.txt +339 -0
- repokit/licenses/gpl-3.0.txt +674 -0
- repokit/licenses/isc.txt +15 -0
- repokit/licenses/lgpl-2.1.txt +504 -0
- repokit/licenses/lgpl-3.0.txt +165 -0
- repokit/licenses/mit.txt +21 -0
- repokit/licenses/mpl-2.0.txt +373 -0
- repokit/licenses/unlicense.txt +24 -0
- repokit/readme_blocks.py +112 -0
- repokit/remote_integration.py +627 -0
- repokit/repo_manager.py +2796 -0
- repokit/template_engine.py +391 -0
- repokit/templates/ai/claude/CLAUDE-nested.md.template +25 -0
- repokit/templates/ai/claude/CLAUDE.md.template +98 -0
- repokit/templates/ai/claude/instructions/step1_context_rebuilder.md +33 -0
- repokit/templates/ai/claude/instructions/step2_dev_workflow_process.md +67 -0
- repokit/templates/ai/claude/instructions/step3_context_bridge.md +52 -0
- repokit/templates/common/CHANGELOG.md.template +15 -0
- repokit/templates/common/CONTRIBUTING.md.template +31 -0
- repokit/templates/common/README.md.template +30 -0
- repokit/templates/common/gitattributes.template +17 -0
- repokit/templates/common/gitignore.template +141 -0
- repokit/templates/extras/FUNDING.yml.template +13 -0
- repokit/templates/extras/ROADMAP.md.template +17 -0
- repokit/templates/extras/dependabot.yml.template +22 -0
- repokit/templates/extras/one-offs-README.md.template +28 -0
- repokit/templates/extras/platform-support.md.template +11 -0
- repokit/templates/extras/pull_request_template.md.template +25 -0
- repokit/templates/extras/release-template.md.template +34 -0
- repokit/templates/extras/settings.json.template +11 -0
- repokit/templates/extras/stale.yml.template +14 -0
- repokit/templates/github/CODEOWNERS.template +11 -0
- repokit/templates/github/ISSUE_TEMPLATE/bug-report.md.template +29 -0
- repokit/templates/github/ISSUE_TEMPLATE/feature-request.md.template +19 -0
- repokit/templates/github/issues/notes.md.template +18 -0
- repokit/templates/github/issues/roadmap.md.template +15 -0
- repokit/templates/hooks/pre-commit.template +64 -0
- repokit/templates/layers/archetypes/any/claude-plugin/.claude-plugin/marketplace.json +20 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/.claude-plugin/plugin.json +15 -0
- repokit/templates/layers/archetypes/any/claude-plugin/plugin/README.md +28 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.github/workflows/ci-c-cpp.yml +24 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.repokit-common.toml.rkappend +4 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/launch.json +29 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/.vscode/tasks.json +26 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakeLists.txt +47 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/CMakePresets.json +23 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/cmake/GenerateStamp.cmake +45 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/src/main.cpp +27 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/tests/test_version.cpp +20 -0
- repokit/templates/layers/archetypes/c-cpp/cmake-app/vcpkg.json +5 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/.vscode/launch.json +27 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/Makefile +10 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/build.bat +7 -0
- repokit/templates/layers/archetypes/c-cpp/minimal/src/__RK_SNAKE__.c +26 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/launch.json +17 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/.vscode/tasks.json +20 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.sln +19 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/__RK_PROJECT__.vcxproj +41 -0
- repokit/templates/layers/archetypes/c-cpp/msbuild/src/main.cpp +26 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.repokit-common.toml +28 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/hooks/scripts/session-start.mjs +23 -0
- repokit/templates/layers/archetypes/js-ts/claude-plugin/plugin/tests/manifest.test.js +30 -0
- repokit/templates/layers/archetypes/js-ts/node/.repokit-common.toml.rkappend +6 -0
- repokit/templates/layers/archetypes/js-ts/node/package.json +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/.github/workflows/release.yml +39 -0
- repokit/templates/layers/archetypes/js-ts/node-package/package.json +32 -0
- repokit/templates/layers/archetypes/js-ts/node-package/src/index.ts +13 -0
- repokit/templates/layers/archetypes/js-ts/node-package/tests/index.test.ts +11 -0
- repokit/templates/layers/archetypes/js-ts/script/.vscode/launch.json +34 -0
- repokit/templates/layers/archetypes/js-ts/script/__RK_PROJECT__.js +37 -0
- repokit/templates/layers/archetypes/js-ts/script/tests/version.test.js +26 -0
- repokit/templates/layers/archetypes/js-ts/ts/package.json +18 -0
- repokit/templates/layers/archetypes/js-ts/ts/tsconfig.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.github/workflows/ci-python.yml +44 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.repokit-common.toml +21 -0
- repokit/templates/layers/archetypes/python/claude-plugin/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/claude-plugin/VERSION +1 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/hooks.json +15 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/run-hook.mjs +42 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/hooks/scripts/session_start.py +31 -0
- repokit/templates/layers/archetypes/python/claude-plugin/plugin/tests/test_manifest.py +42 -0
- repokit/templates/layers/archetypes/python/pypi/.github/workflows/release.yml +63 -0
- repokit/templates/layers/archetypes/python/pypi/.vscode/launch.json +43 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__init__.py +5 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/__main__.py +6 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/_version.py +96 -0
- repokit/templates/layers/archetypes/python/pypi/__RK_SNAKE__/cli.py +18 -0
- repokit/templates/layers/archetypes/python/pypi/pyproject.toml +71 -0
- repokit/templates/layers/archetypes/python/pypi/tests/conftest.py +11 -0
- repokit/templates/layers/archetypes/python/pypi/tests/test_version.py +50 -0
- repokit/templates/layers/archetypes/python/script/.github/workflows/ci-python.yml +43 -0
- repokit/templates/layers/archetypes/python/script/.repokit-common.toml +14 -0
- repokit/templates/layers/archetypes/python/script/.vscode/launch.json +33 -0
- repokit/templates/layers/archetypes/python/script/VERSION +1 -0
- repokit/templates/layers/archetypes/python/script/__RK_PROJECT__.py +40 -0
- repokit/templates/layers/archetypes/python/script/tests/test_version.py +25 -0
- repokit/templates/layers/archetypes/python/setuppy/requirements.txt +8 -0
- repokit/templates/layers/archetypes/python/setuppy/setup.py +23 -0
- repokit/templates/layers/languages/c-cpp/.gitignore.rkappend +20 -0
- repokit/templates/layers/languages/c-cpp/.repokit-common.toml +7 -0
- repokit/templates/layers/languages/c-cpp/include/__RK_SNAKE__/version.h +20 -0
- repokit/templates/layers/languages/c-cpp/src/app.rc +25 -0
- repokit/templates/layers/languages/js-ts/.editorconfig +15 -0
- repokit/templates/layers/languages/js-ts/.github/workflows/ci-js-ts.yml +41 -0
- repokit/templates/layers/languages/js-ts/.gitignore.rkappend +13 -0
- repokit/templates/layers/languages/js-ts/.repokit-common.toml +19 -0
- repokit/templates/layers/languages/js-ts/.vscode/launch.json +24 -0
- repokit/templates/layers/languages/js-ts/VERSION +1 -0
- repokit/templates/layers/languages/python/.github/workflows/ci-python.yml +76 -0
- repokit/templates/layers/languages/python/.gitignore.rkappend +15 -0
- repokit/templates/layers/languages/python/.vscode/launch.json +33 -0
- repokit/templates/layers/manifest.toml +302 -0
- repokit/templates/vault/CLAUDE.md +29 -0
- repokit/templates/vault/claude/_maps/MOC-project.md +15 -0
- repokit/templates/vault/claude/_oracle/backlinks.md +3 -0
- repokit/templates/vault/claude/_oracle/concepts.md +12 -0
- repokit/templates/vault/claude/_oracle/manifest.md +10 -0
- repokit/templates/vault/claude/_todo/ACTIVE-WORK.md +18 -0
- repokit/templates/vault/claude/_todo/OPEN-LOOPS.md +14 -0
- repokit/utils.py +316 -0
- repokit/vault.py +248 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Directory Profiles for RepoKit.
|
|
4
|
+
|
|
5
|
+
Implements a flexible directory structure system with profiles, groups, and types.
|
|
6
|
+
This allows for customized directory setups based on project needs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Dict, List, Set, Any, Optional
|
|
14
|
+
|
|
15
|
+
from .defaults import (
|
|
16
|
+
DEFAULT_PRIVATE_DIRS as CENTRALIZED_PRIVATE_DIRS,
|
|
17
|
+
DEFAULT_DIRECTORY_PROFILES,
|
|
18
|
+
DEFAULT_DIRECTORY_GROUPS,
|
|
19
|
+
DEFAULT_DIRECTORY_TYPE_MAPPING,
|
|
20
|
+
DEFAULT_PRIVATE_DIR_SETS
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Set up logging
|
|
24
|
+
logger = logging.getLogger("repokit.directories")
|
|
25
|
+
|
|
26
|
+
# Use centralized configurations from defaults.py
|
|
27
|
+
# These are now imported directly, eliminating duplication
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DirectoryProfileManager:
|
|
31
|
+
"""Manages directory profiles, groups, and types for project setup."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, config_file: Optional[str] = None, verbose: int = 0):
|
|
34
|
+
"""
|
|
35
|
+
Initialize the directory profile manager.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
config_file: Optional path to configuration file
|
|
39
|
+
verbose: Verbosity level
|
|
40
|
+
"""
|
|
41
|
+
self.verbose = verbose
|
|
42
|
+
|
|
43
|
+
# Initialize defaults using centralized configurations
|
|
44
|
+
self.profiles = DEFAULT_DIRECTORY_PROFILES.copy()
|
|
45
|
+
self.groups = DEFAULT_DIRECTORY_GROUPS.copy()
|
|
46
|
+
self.private_dirs = DEFAULT_PRIVATE_DIR_SETS.copy()
|
|
47
|
+
self.type_mapping = DEFAULT_DIRECTORY_TYPE_MAPPING.copy()
|
|
48
|
+
|
|
49
|
+
# Load custom configuration if provided
|
|
50
|
+
if config_file:
|
|
51
|
+
self.load_config(config_file)
|
|
52
|
+
|
|
53
|
+
def load_config(self, config_file: str) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Load directory configuration from a file.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
config_file: Path to configuration file
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
True if loaded successfully, False otherwise
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
with open(config_file, "r") as f:
|
|
65
|
+
config = json.load(f)
|
|
66
|
+
|
|
67
|
+
# Update with custom configuration
|
|
68
|
+
if "directory_profiles" in config:
|
|
69
|
+
self.profiles.update(config["directory_profiles"])
|
|
70
|
+
|
|
71
|
+
if "directory_groups" in config:
|
|
72
|
+
self.groups.update(config["directory_groups"])
|
|
73
|
+
|
|
74
|
+
if "private_dirs" in config:
|
|
75
|
+
self.private_dirs.update(config["private_dirs"])
|
|
76
|
+
|
|
77
|
+
if "directory_types" in config:
|
|
78
|
+
self.type_mapping.update(config["directory_types"])
|
|
79
|
+
|
|
80
|
+
logger.info(f"Loaded directory configuration from {config_file}")
|
|
81
|
+
return True
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.error(f"Failed to load directory configuration: {str(e)}")
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def save_config(self, config_file: str) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Save directory configuration to a file.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
config_file: Path to configuration file
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
True if saved successfully, False otherwise
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
config = {
|
|
98
|
+
"directory_profiles": self.profiles,
|
|
99
|
+
"directory_groups": self.groups,
|
|
100
|
+
"private_dirs": self.private_dirs,
|
|
101
|
+
"directory_types": self.type_mapping,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Create parent directory if it doesn't exist
|
|
105
|
+
os.makedirs(os.path.dirname(os.path.abspath(config_file)), exist_ok=True)
|
|
106
|
+
|
|
107
|
+
with open(config_file, "w") as f:
|
|
108
|
+
json.dump(config, f, indent=4)
|
|
109
|
+
|
|
110
|
+
logger.info(f"Saved directory configuration to {config_file}")
|
|
111
|
+
return True
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.error(f"Failed to save directory configuration: {str(e)}")
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def get_directories_for_profile(self, profile: str) -> List[str]:
|
|
117
|
+
"""
|
|
118
|
+
Get directory types for a specific profile.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
profile: Profile name (minimal, standard, complete, or custom)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
List of directory types in the profile
|
|
125
|
+
"""
|
|
126
|
+
return self.profiles.get(profile, self.profiles["standard"]).copy()
|
|
127
|
+
|
|
128
|
+
def get_directories_for_groups(self, groups: List[str]) -> List[str]:
|
|
129
|
+
"""
|
|
130
|
+
Get directory types for specific groups.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
groups: List of group names
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of directory types in the groups
|
|
137
|
+
"""
|
|
138
|
+
result = set()
|
|
139
|
+
for group in groups:
|
|
140
|
+
if group in self.groups:
|
|
141
|
+
result.update(self.groups[group])
|
|
142
|
+
return sorted(list(result))
|
|
143
|
+
|
|
144
|
+
def get_private_directories(self, private_set: str = "standard") -> List[str]:
|
|
145
|
+
"""
|
|
146
|
+
Get list of private directories.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
private_set: Private directory set name
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of private directory types
|
|
153
|
+
"""
|
|
154
|
+
return self.private_dirs.get(private_set, self.private_dirs["standard"]).copy()
|
|
155
|
+
|
|
156
|
+
def get_actual_directory_name(
|
|
157
|
+
self, dir_type: str, package_name: Optional[str] = None
|
|
158
|
+
) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Get actual directory name for a directory type.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
dir_type: Directory type (src, tests, etc.)
|
|
164
|
+
package_name: Optional package name to use for src directory
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Actual directory name to create
|
|
168
|
+
"""
|
|
169
|
+
# Special handling for source directory
|
|
170
|
+
if dir_type == "src" and package_name:
|
|
171
|
+
return package_name
|
|
172
|
+
|
|
173
|
+
return self.type_mapping.get(dir_type, dir_type)
|
|
174
|
+
|
|
175
|
+
def get_all_directories(
|
|
176
|
+
self,
|
|
177
|
+
profile: str,
|
|
178
|
+
groups: Optional[List[str]] = None,
|
|
179
|
+
private_set: str = "standard",
|
|
180
|
+
package_name: Optional[str] = None,
|
|
181
|
+
) -> Dict[str, List[str]]:
|
|
182
|
+
"""
|
|
183
|
+
Get all directories to create, mapped to actual names.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
profile: Profile name
|
|
187
|
+
groups: Optional additional groups
|
|
188
|
+
private_set: Private directory set name
|
|
189
|
+
package_name: Optional package name for src directory
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Dictionary with categories and actual directory names
|
|
193
|
+
"""
|
|
194
|
+
# Start with profile directories
|
|
195
|
+
dir_types = set(self.get_directories_for_profile(profile))
|
|
196
|
+
|
|
197
|
+
# Add group directories if specified
|
|
198
|
+
if groups:
|
|
199
|
+
dir_types.update(self.get_directories_for_groups(groups))
|
|
200
|
+
|
|
201
|
+
# Get private directories
|
|
202
|
+
private_types = set(self.get_private_directories(private_set))
|
|
203
|
+
|
|
204
|
+
# Convert types to actual directory names
|
|
205
|
+
standard_dirs = []
|
|
206
|
+
private_dirs = []
|
|
207
|
+
|
|
208
|
+
for dir_type in dir_types:
|
|
209
|
+
actual_name = self.get_actual_directory_name(dir_type, package_name)
|
|
210
|
+
if dir_type in private_types:
|
|
211
|
+
private_dirs.append(actual_name)
|
|
212
|
+
else:
|
|
213
|
+
standard_dirs.append(actual_name)
|
|
214
|
+
|
|
215
|
+
# Add all private directories from the private set (even if not in profile)
|
|
216
|
+
for private_type in private_types:
|
|
217
|
+
actual_name = self.get_actual_directory_name(private_type, package_name)
|
|
218
|
+
if actual_name not in private_dirs:
|
|
219
|
+
private_dirs.append(actual_name)
|
|
220
|
+
|
|
221
|
+
return {"standard": sorted(standard_dirs), "private": sorted(private_dirs)}
|
|
222
|
+
|
|
223
|
+
def create_directories(
|
|
224
|
+
self,
|
|
225
|
+
base_path: str,
|
|
226
|
+
profile: str,
|
|
227
|
+
groups: Optional[List[str]] = None,
|
|
228
|
+
private_set: str = "standard",
|
|
229
|
+
package_name: Optional[str] = None,
|
|
230
|
+
) -> Dict[str, List[str]]:
|
|
231
|
+
"""
|
|
232
|
+
Create all directories for a project.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
base_path: Base path to create directories in
|
|
236
|
+
profile: Profile name
|
|
237
|
+
groups: Optional additional groups
|
|
238
|
+
private_set: Private directory set name
|
|
239
|
+
package_name: Optional package name for src directory
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Dictionary with created directories by category
|
|
243
|
+
"""
|
|
244
|
+
directories = self.get_all_directories(
|
|
245
|
+
profile, groups, private_set, package_name
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
created_dirs = {"standard": [], "private": []}
|
|
249
|
+
|
|
250
|
+
# Create standard directories
|
|
251
|
+
for dirname in directories["standard"]:
|
|
252
|
+
dir_path = os.path.join(base_path, dirname)
|
|
253
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
254
|
+
created_dirs["standard"].append(dirname)
|
|
255
|
+
|
|
256
|
+
# Create .gitkeep file
|
|
257
|
+
gitkeep_path = os.path.join(dir_path, ".gitkeep")
|
|
258
|
+
if not os.path.exists(gitkeep_path):
|
|
259
|
+
with open(gitkeep_path, "w") as f:
|
|
260
|
+
f.write("# This file ensures the directory is tracked by Git\n")
|
|
261
|
+
|
|
262
|
+
# Create private directories
|
|
263
|
+
for dirname in directories["private"]:
|
|
264
|
+
dir_path = os.path.join(base_path, dirname)
|
|
265
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
266
|
+
created_dirs["private"].append(dirname)
|
|
267
|
+
|
|
268
|
+
# Create .gitkeep file (will be ignored in .gitignore)
|
|
269
|
+
gitkeep_path = os.path.join(dir_path, ".gitkeep")
|
|
270
|
+
if not os.path.exists(gitkeep_path):
|
|
271
|
+
with open(gitkeep_path, "w") as f:
|
|
272
|
+
f.write("# This file ensures the directory exists\n")
|
|
273
|
+
|
|
274
|
+
logger.info(f"Created {len(created_dirs['standard'])} standard directories")
|
|
275
|
+
logger.info(f"Created {len(created_dirs['private'])} private directories")
|
|
276
|
+
|
|
277
|
+
return created_dirs
|
|
278
|
+
|
|
279
|
+
def validate_directories(self, base_path: str) -> Dict[str, List[str]]:
|
|
280
|
+
"""
|
|
281
|
+
Validate existing directories against all known directory types.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
base_path: Base path to check
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Dictionary with existing, missing, and unknown directories
|
|
288
|
+
"""
|
|
289
|
+
# Get all known directory types
|
|
290
|
+
all_known_types = set()
|
|
291
|
+
for profile in self.profiles.values():
|
|
292
|
+
all_known_types.update(profile)
|
|
293
|
+
|
|
294
|
+
# Get all directory names that could exist
|
|
295
|
+
all_possible_names = set()
|
|
296
|
+
for dir_type in all_known_types:
|
|
297
|
+
# Add with default name
|
|
298
|
+
all_possible_names.add(self.get_actual_directory_name(dir_type))
|
|
299
|
+
# Also add common package names for src
|
|
300
|
+
if dir_type == "src":
|
|
301
|
+
all_possible_names.update(["src", "app", "lib", "pkg"])
|
|
302
|
+
|
|
303
|
+
# Check existing directories
|
|
304
|
+
existing_dirs = []
|
|
305
|
+
for item in os.listdir(base_path):
|
|
306
|
+
item_path = os.path.join(base_path, item)
|
|
307
|
+
if os.path.isdir(item_path) and not item.startswith("."):
|
|
308
|
+
existing_dirs.append(item)
|
|
309
|
+
|
|
310
|
+
# Categorize directories
|
|
311
|
+
known_dirs = [d for d in existing_dirs if d in all_possible_names]
|
|
312
|
+
unknown_dirs = [d for d in existing_dirs if d not in all_possible_names]
|
|
313
|
+
missing_dirs = [d for d in all_possible_names if d not in existing_dirs]
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
"existing": sorted(known_dirs),
|
|
317
|
+
"unknown": sorted(unknown_dirs),
|
|
318
|
+
"missing": sorted(missing_dirs),
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
def suggest_profile(self, base_path: str) -> str:
|
|
322
|
+
"""
|
|
323
|
+
Suggest a profile based on existing directories.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
base_path: Base path to check
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Suggested profile name
|
|
330
|
+
"""
|
|
331
|
+
validation = self.validate_directories(base_path)
|
|
332
|
+
existing_dirs = set(validation["existing"])
|
|
333
|
+
|
|
334
|
+
# Check each profile to see how many directories match
|
|
335
|
+
profile_matches = {}
|
|
336
|
+
for profile_name, profile_dirs in self.profiles.items():
|
|
337
|
+
# Convert profile types to actual directory names
|
|
338
|
+
profile_names = set(self.get_actual_directory_name(d) for d in profile_dirs)
|
|
339
|
+
# Count matches
|
|
340
|
+
matches = len(existing_dirs.intersection(profile_names))
|
|
341
|
+
profile_matches[profile_name] = matches
|
|
342
|
+
|
|
343
|
+
# Find the profile with the most matches
|
|
344
|
+
best_profile = max(profile_matches.items(), key=lambda x: x[1])
|
|
345
|
+
|
|
346
|
+
# If minimal matches, default to standard
|
|
347
|
+
if best_profile[1] <= 1:
|
|
348
|
+
return "standard"
|
|
349
|
+
|
|
350
|
+
return best_profile[0]
|
|
351
|
+
|
|
352
|
+
def manage_gitkeep_files(self, base_path: str, recursive: bool = True) -> Dict[str, List[str]]:
|
|
353
|
+
"""
|
|
354
|
+
Manage .gitkeep files based on directory contents.
|
|
355
|
+
|
|
356
|
+
Adds .gitkeep to empty directories and removes .gitkeep from non-empty directories.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
base_path: Base path to manage
|
|
360
|
+
recursive: Whether to process subdirectories recursively
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
Dictionary with added and removed .gitkeep files
|
|
364
|
+
"""
|
|
365
|
+
result = {"added": [], "removed": []}
|
|
366
|
+
|
|
367
|
+
def process_directory(dir_path: str, relative_path: str = ""):
|
|
368
|
+
"""Process a single directory for .gitkeep management."""
|
|
369
|
+
if not os.path.isdir(dir_path):
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
gitkeep_path = os.path.join(dir_path, ".gitkeep")
|
|
373
|
+
gitkeep_exists = os.path.exists(gitkeep_path)
|
|
374
|
+
|
|
375
|
+
# Get all items in directory (excluding .gitkeep itself)
|
|
376
|
+
try:
|
|
377
|
+
items = [item for item in os.listdir(dir_path) if item != ".gitkeep"]
|
|
378
|
+
except PermissionError:
|
|
379
|
+
logger.warning(f"Permission denied accessing directory: {dir_path}")
|
|
380
|
+
return
|
|
381
|
+
|
|
382
|
+
# Filter out hidden files and directories that should be ignored
|
|
383
|
+
non_hidden_items = [item for item in items if not item.startswith(".")]
|
|
384
|
+
|
|
385
|
+
# Check if directory is effectively empty (only has hidden files or .gitkeep)
|
|
386
|
+
is_empty = len(non_hidden_items) == 0
|
|
387
|
+
|
|
388
|
+
# Manage .gitkeep based on directory state
|
|
389
|
+
if is_empty and not gitkeep_exists:
|
|
390
|
+
# Add .gitkeep to empty directory
|
|
391
|
+
try:
|
|
392
|
+
with open(gitkeep_path, "w") as f:
|
|
393
|
+
f.write("# This file ensures the directory is tracked by Git\n")
|
|
394
|
+
result["added"].append(os.path.join(relative_path, ".gitkeep") if relative_path else ".gitkeep")
|
|
395
|
+
logger.debug(f"Added .gitkeep to empty directory: {dir_path}")
|
|
396
|
+
except PermissionError:
|
|
397
|
+
logger.warning(f"Permission denied creating .gitkeep in: {dir_path}")
|
|
398
|
+
elif not is_empty and gitkeep_exists:
|
|
399
|
+
# Remove .gitkeep from non-empty directory
|
|
400
|
+
try:
|
|
401
|
+
os.remove(gitkeep_path)
|
|
402
|
+
result["removed"].append(os.path.join(relative_path, ".gitkeep") if relative_path else ".gitkeep")
|
|
403
|
+
logger.debug(f"Removed .gitkeep from non-empty directory: {dir_path}")
|
|
404
|
+
except PermissionError:
|
|
405
|
+
logger.warning(f"Permission denied removing .gitkeep from: {dir_path}")
|
|
406
|
+
|
|
407
|
+
# Process subdirectories recursively if requested
|
|
408
|
+
if recursive:
|
|
409
|
+
for item in items:
|
|
410
|
+
item_path = os.path.join(dir_path, item)
|
|
411
|
+
if os.path.isdir(item_path) and not item.startswith("."):
|
|
412
|
+
item_relative = os.path.join(relative_path, item) if relative_path else item
|
|
413
|
+
process_directory(item_path, item_relative)
|
|
414
|
+
|
|
415
|
+
# Start processing from base path
|
|
416
|
+
process_directory(base_path)
|
|
417
|
+
|
|
418
|
+
if result["added"] or result["removed"]:
|
|
419
|
+
logger.info(f"Managed .gitkeep files: {len(result['added'])} added, {len(result['removed'])} removed")
|
|
420
|
+
else:
|
|
421
|
+
logger.debug("No .gitkeep file changes needed")
|
|
422
|
+
|
|
423
|
+
return result
|