repopy 0.2.0__tar.gz → 0.3.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.
- {repopy-0.2.0/src/repopy.egg-info → repopy-0.3.0}/PKG-INFO +33 -23
- {repopy-0.2.0 → repopy-0.3.0}/README.md +32 -22
- {repopy-0.2.0 → repopy-0.3.0}/pyproject.toml +1 -1
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/__main__.py +10 -8
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/cli.py +16 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/file_system.py +58 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/git_engine.py +2 -34
- repopy-0.3.0/src/repopy/orchestrators.py +149 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/prompts.py +23 -1
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/templates.py +14 -14
- {repopy-0.2.0 → repopy-0.3.0/src/repopy.egg-info}/PKG-INFO +33 -23
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy.egg-info/SOURCES.txt +4 -0
- repopy-0.3.0/tests/test_cli.py +46 -0
- repopy-0.3.0/tests/test_file_system.py +27 -0
- repopy-0.3.0/tests/test_git_engine.py +106 -0
- repopy-0.3.0/tests/test_orchestrators.py +179 -0
- repopy-0.3.0/tests/test_prompts.py +106 -0
- repopy-0.2.0/src/repopy/orchestrators.py +0 -160
- repopy-0.2.0/tests/test_prompts.py +0 -45
- {repopy-0.2.0 → repopy-0.3.0}/LICENSE +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/setup.cfg +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/__init__.py +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/dependencies.py +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/os_detector.py +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy/validators.py +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy.egg-info/dependency_links.txt +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy.egg-info/entry_points.txt +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/src/repopy.egg-info/top_level.txt +0 -0
- {repopy-0.2.0 → repopy-0.3.0}/tests/test_validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: repopy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A modular CLI workspace initializer and cloning automation tool.
|
|
5
5
|
Author-email: Janitha Manatunga <janithamanatunga@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -36,17 +36,17 @@ A modular, cross-platform Python CLI workspace manager and developer tooling sui
|
|
|
36
36
|
|
|
37
37
|
## 🌟 Key Features
|
|
38
38
|
|
|
39
|
-
- **Lifecycle Project Scaffolding (`repopy init`)**:
|
|
39
|
+
- **Lifecycle Project Scaffolding (`repopy init`)**: Provisions clean directory structures, dedicated virtual environments, automated `.gitignore` templates, and PEP 621 `pyproject.toml` or `requirements.txt` configs.
|
|
40
40
|
|
|
41
|
-
- **Remote Cloning (`repopy clone`)**: Clones remote Git repositories
|
|
41
|
+
- **Smart Remote Cloning (`repopy clone`)**: Clones remote Git repositories, creates isolated `.venv` environments, previews declared dependencies, and installs them interactively or via flags.
|
|
42
42
|
|
|
43
43
|
- **Upstream Linking (`repopy link`)**: Instantly connects an unlinked local workspace to a remote Git upstream, staging files, executing initial commits, and pushing to the default branch in one step.
|
|
44
44
|
|
|
45
|
-
- **Architectural Themes**:
|
|
45
|
+
- **Architectural Themes**: Built-in layout presets (`minimal`, `web_api`, `cli_package`, `data_science`) tailored to modern packaging standards.
|
|
46
46
|
|
|
47
|
-
- **Transactional Cleanups**: Employs defensive rollback mechanisms (`shutil.rmtree`) to purge
|
|
47
|
+
- **Transactional Cleanups**: Employs defensive rollback mechanisms (`shutil.rmtree`) to safely purge half-baked directories if setup fails midway.
|
|
48
48
|
|
|
49
|
-
- **
|
|
49
|
+
- **Isolated & Tested**: Fully decoupled architecture backed by comprehensive unit and mock test suites.
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
@@ -55,11 +55,11 @@ A modular, cross-platform Python CLI workspace manager and developer tooling sui
|
|
|
55
55
|
`repopy` is organized into single-responsibility layers:
|
|
56
56
|
- **Presentation & Routing (`cli.py`, `prompts.py`, `__main__.py`)**: Evaluates native terminal arguments via `argparse`, orchestrates interactive prompt flows, and handles OS interrupts gracefully.
|
|
57
57
|
|
|
58
|
-
- **Supervision (`orchestrators.py`)**: Broker layer executing lifecycle validation checks, branch sequencing, and command handoffs.
|
|
58
|
+
- **Supervision (`orchestrators.py`)**: Broker layer executing lifecycle validation checks, branch sequencing, rollback triggers and command handoffs.
|
|
59
59
|
|
|
60
|
-
- **Low-Level Engines (`file_system.py`, `git_engine.py`)**: Decoupled engines handling transactional directory allocations, venv isolation, and direct subprocess execution.
|
|
60
|
+
- **Low-Level Engines (`file_system.py`, `git_engine.py`)**: Decoupled engines handling transactional directory allocations, venv isolation, dependency installation and direct subprocess execution.
|
|
61
61
|
|
|
62
|
-
- **Validation & Translation (`validators.py`, `dependencies.py`, `os_detector.py`)**: Sanitizes paths,
|
|
62
|
+
- **Validation & Translation (`validators.py`, `dependencies.py`, `os_detector.py`)**: Sanitizes names and paths, validates Git URLs, verifies host binaries (`python`, `git`), and resolves cross-platform paths.
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
@@ -100,15 +100,25 @@ repopy init my-cli -t cli_package --link <git-url>
|
|
|
100
100
|
- `minimal`: Bare directory setup with `requirements.txt`.
|
|
101
101
|
- `web_api`: Layered service layout prepared for modern frameworks.
|
|
102
102
|
- `cli_package`: Modular structure configured with PEP 621 compliant `pyproject.toml`.
|
|
103
|
-
- `data_science`: Notebooks, data
|
|
104
|
-
|
|
103
|
+
- `data_science`: Notebooks, data directories (raw, processed), and pipeline layout.
|
|
104
|
+
|
|
105
105
|
<br>
|
|
106
106
|
|
|
107
107
|
### 2. Clone a Remote Project (`repopy clone`)
|
|
108
|
-
Fetch a remote repository
|
|
108
|
+
Fetch a remote repository, provision a virtual environment, and manage dependencies:
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
|
|
111
|
+
# Interactive mode (previews requirements.txt and prompts for installation)
|
|
112
|
+
repopy clone <git-url>
|
|
113
|
+
|
|
114
|
+
# Custom directory name
|
|
115
|
+
repopy clone <git-url> -n custom-folder-name
|
|
116
|
+
|
|
117
|
+
# Auto-install dependencies without prompting
|
|
118
|
+
repopy clone <git-url> -i
|
|
119
|
+
|
|
120
|
+
# Fetch and provision environment only (skip dependency installation)
|
|
121
|
+
repopy clone <git-url> --no-install
|
|
112
122
|
```
|
|
113
123
|
|
|
114
124
|
<br>
|
|
@@ -117,7 +127,11 @@ repopy clone <git-url> -n [custom-project-name]
|
|
|
117
127
|
Run inside an existing workspace to push tracking history to an upstream origin:
|
|
118
128
|
|
|
119
129
|
```bash
|
|
120
|
-
|
|
130
|
+
# Link with default initial commit message
|
|
131
|
+
repopy link <git-url>
|
|
132
|
+
|
|
133
|
+
# Link with custom commit message
|
|
134
|
+
repopy link <git-url> -m "feat: initial project structure"
|
|
121
135
|
```
|
|
122
136
|
|
|
123
137
|
---
|
|
@@ -130,8 +144,6 @@ Run the test suite using `pytest`:
|
|
|
130
144
|
pytest -v
|
|
131
145
|
```
|
|
132
146
|
|
|
133
|
-
All contributions and pull requests are validated through our GitHub Actions workflow matrix (`.github/workflows/tests.yml`), running integration and validation tests across supported Python versions.
|
|
134
|
-
|
|
135
147
|
---
|
|
136
148
|
|
|
137
149
|
## 🗂️ Project Structure
|
|
@@ -155,11 +167,16 @@ repopy/
|
|
|
155
167
|
│ ├── templates.py # Static asset definitions & manifests
|
|
156
168
|
│ └── validators.py # Input sanitation & regex filters
|
|
157
169
|
├── tests/
|
|
170
|
+
│ ├── test_cli.py # CLI argument parser & flag exclusivity tests
|
|
171
|
+
│ ├── test_file_system.py # Workspace scaffolding & requirements parsing tests
|
|
172
|
+
│ ├── test_git_engine.py # Subprocess Git mocking & exit code tests
|
|
173
|
+
│ ├── test_orchestrators.py # Pipeline logic, prompts, and rollback tests
|
|
158
174
|
│ ├── test_prompts.py # Input loop & default manifest tests
|
|
159
175
|
│ └── test_validators.py # Path and schema validation tests
|
|
160
176
|
├── pyproject.toml # PEP 621 packaging configuration
|
|
161
177
|
├── LICENSE # MIT License
|
|
162
178
|
└── README.md
|
|
179
|
+
|
|
163
180
|
```
|
|
164
181
|
|
|
165
182
|
---
|
|
@@ -167,10 +184,3 @@ repopy/
|
|
|
167
184
|
## 📄 License
|
|
168
185
|
|
|
169
186
|
Distributed under the MIT License. See `LICENSE` for details.
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Key Review Notes
|
|
174
|
-
|
|
175
|
-
* Verify the GitHub Actions workflow file path (`.github/workflows/tests.yml`) matches your actual workflow file name so the build badge renders correctly.
|
|
176
|
-
* In the `repopy init` theme section, verify that the theme descriptions match what you have mapped out in `templates.py`.
|
|
@@ -11,17 +11,17 @@ A modular, cross-platform Python CLI workspace manager and developer tooling sui
|
|
|
11
11
|
|
|
12
12
|
## 🌟 Key Features
|
|
13
13
|
|
|
14
|
-
- **Lifecycle Project Scaffolding (`repopy init`)**:
|
|
14
|
+
- **Lifecycle Project Scaffolding (`repopy init`)**: Provisions clean directory structures, dedicated virtual environments, automated `.gitignore` templates, and PEP 621 `pyproject.toml` or `requirements.txt` configs.
|
|
15
15
|
|
|
16
|
-
- **Remote Cloning (`repopy clone`)**: Clones remote Git repositories
|
|
16
|
+
- **Smart Remote Cloning (`repopy clone`)**: Clones remote Git repositories, creates isolated `.venv` environments, previews declared dependencies, and installs them interactively or via flags.
|
|
17
17
|
|
|
18
18
|
- **Upstream Linking (`repopy link`)**: Instantly connects an unlinked local workspace to a remote Git upstream, staging files, executing initial commits, and pushing to the default branch in one step.
|
|
19
19
|
|
|
20
|
-
- **Architectural Themes**:
|
|
20
|
+
- **Architectural Themes**: Built-in layout presets (`minimal`, `web_api`, `cli_package`, `data_science`) tailored to modern packaging standards.
|
|
21
21
|
|
|
22
|
-
- **Transactional Cleanups**: Employs defensive rollback mechanisms (`shutil.rmtree`) to purge
|
|
22
|
+
- **Transactional Cleanups**: Employs defensive rollback mechanisms (`shutil.rmtree`) to safely purge half-baked directories if setup fails midway.
|
|
23
23
|
|
|
24
|
-
- **
|
|
24
|
+
- **Isolated & Tested**: Fully decoupled architecture backed by comprehensive unit and mock test suites.
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
@@ -30,11 +30,11 @@ A modular, cross-platform Python CLI workspace manager and developer tooling sui
|
|
|
30
30
|
`repopy` is organized into single-responsibility layers:
|
|
31
31
|
- **Presentation & Routing (`cli.py`, `prompts.py`, `__main__.py`)**: Evaluates native terminal arguments via `argparse`, orchestrates interactive prompt flows, and handles OS interrupts gracefully.
|
|
32
32
|
|
|
33
|
-
- **Supervision (`orchestrators.py`)**: Broker layer executing lifecycle validation checks, branch sequencing, and command handoffs.
|
|
33
|
+
- **Supervision (`orchestrators.py`)**: Broker layer executing lifecycle validation checks, branch sequencing, rollback triggers and command handoffs.
|
|
34
34
|
|
|
35
|
-
- **Low-Level Engines (`file_system.py`, `git_engine.py`)**: Decoupled engines handling transactional directory allocations, venv isolation, and direct subprocess execution.
|
|
35
|
+
- **Low-Level Engines (`file_system.py`, `git_engine.py`)**: Decoupled engines handling transactional directory allocations, venv isolation, dependency installation and direct subprocess execution.
|
|
36
36
|
|
|
37
|
-
- **Validation & Translation (`validators.py`, `dependencies.py`, `os_detector.py`)**: Sanitizes paths,
|
|
37
|
+
- **Validation & Translation (`validators.py`, `dependencies.py`, `os_detector.py`)**: Sanitizes names and paths, validates Git URLs, verifies host binaries (`python`, `git`), and resolves cross-platform paths.
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
@@ -75,15 +75,25 @@ repopy init my-cli -t cli_package --link <git-url>
|
|
|
75
75
|
- `minimal`: Bare directory setup with `requirements.txt`.
|
|
76
76
|
- `web_api`: Layered service layout prepared for modern frameworks.
|
|
77
77
|
- `cli_package`: Modular structure configured with PEP 621 compliant `pyproject.toml`.
|
|
78
|
-
- `data_science`: Notebooks, data
|
|
79
|
-
|
|
78
|
+
- `data_science`: Notebooks, data directories (raw, processed), and pipeline layout.
|
|
79
|
+
|
|
80
80
|
<br>
|
|
81
81
|
|
|
82
82
|
### 2. Clone a Remote Project (`repopy clone`)
|
|
83
|
-
Fetch a remote repository
|
|
83
|
+
Fetch a remote repository, provision a virtual environment, and manage dependencies:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
|
|
86
|
+
# Interactive mode (previews requirements.txt and prompts for installation)
|
|
87
|
+
repopy clone <git-url>
|
|
88
|
+
|
|
89
|
+
# Custom directory name
|
|
90
|
+
repopy clone <git-url> -n custom-folder-name
|
|
91
|
+
|
|
92
|
+
# Auto-install dependencies without prompting
|
|
93
|
+
repopy clone <git-url> -i
|
|
94
|
+
|
|
95
|
+
# Fetch and provision environment only (skip dependency installation)
|
|
96
|
+
repopy clone <git-url> --no-install
|
|
87
97
|
```
|
|
88
98
|
|
|
89
99
|
<br>
|
|
@@ -92,7 +102,11 @@ repopy clone <git-url> -n [custom-project-name]
|
|
|
92
102
|
Run inside an existing workspace to push tracking history to an upstream origin:
|
|
93
103
|
|
|
94
104
|
```bash
|
|
95
|
-
|
|
105
|
+
# Link with default initial commit message
|
|
106
|
+
repopy link <git-url>
|
|
107
|
+
|
|
108
|
+
# Link with custom commit message
|
|
109
|
+
repopy link <git-url> -m "feat: initial project structure"
|
|
96
110
|
```
|
|
97
111
|
|
|
98
112
|
---
|
|
@@ -105,8 +119,6 @@ Run the test suite using `pytest`:
|
|
|
105
119
|
pytest -v
|
|
106
120
|
```
|
|
107
121
|
|
|
108
|
-
All contributions and pull requests are validated through our GitHub Actions workflow matrix (`.github/workflows/tests.yml`), running integration and validation tests across supported Python versions.
|
|
109
|
-
|
|
110
122
|
---
|
|
111
123
|
|
|
112
124
|
## 🗂️ Project Structure
|
|
@@ -130,11 +142,16 @@ repopy/
|
|
|
130
142
|
│ ├── templates.py # Static asset definitions & manifests
|
|
131
143
|
│ └── validators.py # Input sanitation & regex filters
|
|
132
144
|
├── tests/
|
|
145
|
+
│ ├── test_cli.py # CLI argument parser & flag exclusivity tests
|
|
146
|
+
│ ├── test_file_system.py # Workspace scaffolding & requirements parsing tests
|
|
147
|
+
│ ├── test_git_engine.py # Subprocess Git mocking & exit code tests
|
|
148
|
+
│ ├── test_orchestrators.py # Pipeline logic, prompts, and rollback tests
|
|
133
149
|
│ ├── test_prompts.py # Input loop & default manifest tests
|
|
134
150
|
│ └── test_validators.py # Path and schema validation tests
|
|
135
151
|
├── pyproject.toml # PEP 621 packaging configuration
|
|
136
152
|
├── LICENSE # MIT License
|
|
137
153
|
└── README.md
|
|
154
|
+
|
|
138
155
|
```
|
|
139
156
|
|
|
140
157
|
---
|
|
@@ -142,10 +159,3 @@ repopy/
|
|
|
142
159
|
## 📄 License
|
|
143
160
|
|
|
144
161
|
Distributed under the MIT License. See `LICENSE` for details.
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## Key Review Notes
|
|
149
|
-
|
|
150
|
-
* Verify the GitHub Actions workflow file path (`.github/workflows/tests.yml`) matches your actual workflow file name so the build badge renders correctly.
|
|
151
|
-
* In the `repopy init` theme section, verify that the theme descriptions match what you have mapped out in `templates.py`.
|
|
@@ -12,32 +12,34 @@ def main() -> None:
|
|
|
12
12
|
args = parse_arguments()
|
|
13
13
|
|
|
14
14
|
if args.command == 'init':
|
|
15
|
-
print('Working on it...\n')
|
|
15
|
+
print('⌛ Working on it...\n')
|
|
16
16
|
manifest = capture_project_manifests(args)
|
|
17
17
|
initializer = LocalInitializer(manifest)
|
|
18
18
|
success = initializer.run()
|
|
19
19
|
|
|
20
20
|
if success and args.link:
|
|
21
|
-
print('Initiating automated repository link shortcut...')
|
|
21
|
+
print('⌛ Initiating automated repository link shortcut...')
|
|
22
22
|
link_initializer = LinkInitializer(args.link, args.message)
|
|
23
23
|
link_initializer.run()
|
|
24
24
|
|
|
25
25
|
elif args.command == 'clone':
|
|
26
|
-
print('Working on it...\n')
|
|
27
|
-
initializer = CloneInitializer(
|
|
26
|
+
print('⌛ Working on it...\n')
|
|
27
|
+
initializer = CloneInitializer(
|
|
28
|
+
args.repo_url, args.name, args.install, args.no_install
|
|
29
|
+
)
|
|
28
30
|
initializer.run()
|
|
29
31
|
|
|
30
32
|
elif args.command == 'link':
|
|
31
|
-
print('Working on it...\n')
|
|
33
|
+
print('⌛ Working on it...\n')
|
|
32
34
|
initializer = LinkInitializer(args.repo_url, args.message)
|
|
33
35
|
initializer.run()
|
|
34
36
|
|
|
35
37
|
else:
|
|
36
|
-
print('Usage: repopy [
|
|
37
|
-
print('Error: Please specify a subcommand')
|
|
38
|
+
print('Usage: repopy [init | clone | link] --help')
|
|
39
|
+
print('⚠️ Error: Please specify a subcommand')
|
|
38
40
|
|
|
39
41
|
except KeyboardInterrupt:
|
|
40
|
-
print('\n Workspace operation cancelled by user.')
|
|
42
|
+
print('\n ✖️ Workspace operation cancelled by user.')
|
|
41
43
|
sys.exit(1)
|
|
42
44
|
|
|
43
45
|
|
|
@@ -76,6 +76,22 @@ def parse_arguments() -> argparse.Namespace:
|
|
|
76
76
|
help='Name of the project folder'
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
+
# Mutually exclusive commands regarding installing dependencies:
|
|
80
|
+
# install flag (-i / --install) for installing dependencies, bypassing prompt
|
|
81
|
+
# download-only flag (-d / --download-only) for only fetching dependencies without installing, bypassing prompt
|
|
82
|
+
deps_group = clone_parser.add_mutually_exclusive_group()
|
|
83
|
+
deps_group.add_argument(
|
|
84
|
+
'-i', '--install',
|
|
85
|
+
action='store_true',
|
|
86
|
+
help='Automatically install dependencies without confirmation'
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
deps_group.add_argument(
|
|
90
|
+
'--no-install',
|
|
91
|
+
action='store_true',
|
|
92
|
+
help='Skip dependency installation completely (fetch-only safe mode)'
|
|
93
|
+
)
|
|
94
|
+
|
|
79
95
|
#---------------------------------------------------------------------------------
|
|
80
96
|
# `repopy link` subcommand
|
|
81
97
|
#---------------------------------------------------------------------------------
|
|
@@ -10,6 +10,7 @@ import logging
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
|
|
13
|
+
from repopy.os_detector import is_windows
|
|
13
14
|
from repopy.templates import GITIGNORE_TEMPLATE, generate_pyproject_toml
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -105,6 +106,63 @@ class FileSystemEngine:
|
|
|
105
106
|
return False
|
|
106
107
|
|
|
107
108
|
|
|
109
|
+
def read_requirements(self) -> list[str]:
|
|
110
|
+
'''Read repository dependencies if requirements.txt exists and displays them'''
|
|
111
|
+
req_path = self.project_path / 'requirements.txt'
|
|
112
|
+
if not req_path.is_file():
|
|
113
|
+
return []
|
|
114
|
+
|
|
115
|
+
with open(req_path, 'r') as f:
|
|
116
|
+
lines = [
|
|
117
|
+
cleaned_line
|
|
118
|
+
for line in f
|
|
119
|
+
if (cleaned_line := line.strip()) and not cleaned_line.startswith('#')
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
return lines
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def install_dependencies(self) -> bool:
|
|
126
|
+
'''Install repository dependencies if requirements.txt is present'''
|
|
127
|
+
req_file = self.project_path / 'requirements.txt'
|
|
128
|
+
|
|
129
|
+
if not req_file.exists():
|
|
130
|
+
return True
|
|
131
|
+
|
|
132
|
+
if is_windows():
|
|
133
|
+
pip_path = self.project_path / '.venv' / 'Scripts' / 'pip.exe'
|
|
134
|
+
else:
|
|
135
|
+
pip_path = self.project_path / '.venv' / 'bin' / 'pip'
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
result = subprocess.run([str(pip_path), 'install', '-r', str(req_file)], capture_output=True, text=True)
|
|
139
|
+
return result.returncode == 0
|
|
140
|
+
|
|
141
|
+
except OSError as e:
|
|
142
|
+
logger.error(f'Failed to install dependencies at {self.project_path}: {e}')
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_activation_guide(self) -> str:
|
|
147
|
+
if is_windows():
|
|
148
|
+
venv_activation = '''
|
|
149
|
+
.\\.venv\\Scripts\\Activate.ps1 (if on Powershell)
|
|
150
|
+
.\\.venv\\Scripts\\activate.bat (if on cmd)\n
|
|
151
|
+
'''
|
|
152
|
+
else:
|
|
153
|
+
venv_activation = '''
|
|
154
|
+
source .venv/bin/activate\n
|
|
155
|
+
'''
|
|
156
|
+
|
|
157
|
+
return f'''
|
|
158
|
+
To activate the virtual environment and get started, enter the following:
|
|
159
|
+
|
|
160
|
+
cd {self.project_path}
|
|
161
|
+
|
|
162
|
+
{venv_activation}
|
|
163
|
+
'''
|
|
164
|
+
|
|
165
|
+
|
|
108
166
|
def cleanup(self) -> None:
|
|
109
167
|
'''Removes any partially created project directories if a failure occurs'''
|
|
110
168
|
if self.project_path.exists():
|
|
@@ -4,13 +4,10 @@ holds the methods required to cone a GitHub repository and install
|
|
|
4
4
|
dependencies.
|
|
5
5
|
'''
|
|
6
6
|
|
|
7
|
-
import shutil
|
|
8
7
|
import logging
|
|
9
8
|
import subprocess
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
|
|
12
|
-
from repopy.os_detector import is_windows
|
|
13
|
-
|
|
14
11
|
logger = logging.getLogger(__name__)
|
|
15
12
|
|
|
16
13
|
class GitEngine:
|
|
@@ -27,42 +24,13 @@ class GitEngine:
|
|
|
27
24
|
def clone_repository(self) -> bool:
|
|
28
25
|
'''Clone GitHub repository using the given url to the given directory name'''
|
|
29
26
|
try:
|
|
30
|
-
result = subprocess.run(['git', 'clone', self.repo_url, str(self.project_path)],
|
|
27
|
+
result = subprocess.run(['git', 'clone', self.repo_url, str(self.project_path)], text=True)
|
|
31
28
|
return result.returncode == 0
|
|
32
29
|
except OSError as e:
|
|
33
30
|
logger.error(f'Failed to clone repository at {self.project_path}: {e}')
|
|
34
31
|
return False
|
|
35
32
|
|
|
36
33
|
|
|
37
|
-
def install_dependencies(self) -> bool:
|
|
38
|
-
'''Install repository dependencies if requirements.txt is present'''
|
|
39
|
-
req_file = self.project_path / 'requirements.txt'
|
|
40
|
-
|
|
41
|
-
if not req_file.exists():
|
|
42
|
-
return True
|
|
43
|
-
|
|
44
|
-
if is_windows():
|
|
45
|
-
pip_path = self.project_path / '.venv' / 'Scripts' / 'pip.exe'
|
|
46
|
-
else:
|
|
47
|
-
pip_path = self.project_path / '.venv' / 'bin' / 'pip'
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
result = subprocess.run([str(pip_path), 'install', '-r', str(req_file)], capture_output=True, text=True)
|
|
51
|
-
return result.returncode == 0
|
|
52
|
-
except OSError as e:
|
|
53
|
-
logger.error(f'Failed to install dependencies at {self.project_path}: {e}')
|
|
54
|
-
return False
|
|
55
|
-
|
|
56
|
-
def cleanup(self) -> None:
|
|
57
|
-
'''Removes any partially created project directories if a failure occurs'''
|
|
58
|
-
if self.project_path.exists():
|
|
59
|
-
try:
|
|
60
|
-
shutil.rmtree(self.project_path)
|
|
61
|
-
logger.info(f'Successfully cleaned up half-baked workspace at {self.project_path}')
|
|
62
|
-
except OSError as e:
|
|
63
|
-
logger.error(f'Failed to clean up directory at {self.project_path}: {e}')
|
|
64
|
-
|
|
65
|
-
|
|
66
34
|
class GitLinkEngine:
|
|
67
35
|
|
|
68
36
|
def __init__(self, repo_url: str, message: str):
|
|
@@ -97,6 +65,6 @@ class GitLinkEngine:
|
|
|
97
65
|
return True
|
|
98
66
|
|
|
99
67
|
except OSError as e:
|
|
100
|
-
logger.error(f'Failed to execute Git linking
|
|
68
|
+
logger.error(f'Failed to execute Git linking operations: {e}')
|
|
101
69
|
return False
|
|
102
70
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Orchestration Layer. Core supervisor that amalgamates all inspectors and building
|
|
3
|
+
tools and manages the entire user request loop from start to finish.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from repopy.file_system import FileSystemEngine
|
|
10
|
+
from repopy.git_engine import GitEngine, GitLinkEngine
|
|
11
|
+
from repopy.dependencies import has_python, has_git
|
|
12
|
+
from repopy.prompts import confirm_dependency_installation, confirm_cleanup
|
|
13
|
+
from repopy.validators import is_valid_project_name, is_valid_git_url
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class LocalInitializer:
|
|
18
|
+
|
|
19
|
+
def __init__(self, manifest: dict):
|
|
20
|
+
self.manifest = manifest
|
|
21
|
+
self.project_name = manifest['project_name']
|
|
22
|
+
self.project_path = Path(self.project_name).resolve()
|
|
23
|
+
self.theme = manifest['theme']
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run(self) -> bool:
|
|
27
|
+
'''Runs when `repopy local` is called'''
|
|
28
|
+
if not has_python():
|
|
29
|
+
print('⚠️ Python is not installed in the local machine.')
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
if not is_valid_project_name(self.project_name):
|
|
33
|
+
print(f'⚠️ "{self.project_name}" is not a valid directory name.')
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
fs_engine = FileSystemEngine(self.project_path)
|
|
37
|
+
if fs_engine.build_workspace(self.theme, self.manifest):
|
|
38
|
+
print(f'✅ Success! Workspace provisioned inside: {self.project_path}')
|
|
39
|
+
print(f'✅ Layout theme configured: [{self.theme.upper()}]')
|
|
40
|
+
print(fs_engine.get_activation_guide())
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
else:
|
|
44
|
+
print(f'❌ Construction failed. Triggering cleanup on half-baked directories...')
|
|
45
|
+
fs_engine.cleanup()
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class CloneInitializer:
|
|
50
|
+
|
|
51
|
+
def __init__(self, repo_url: str, dir_name: str | None, install: bool, no_install: bool):
|
|
52
|
+
self.repo_url = repo_url
|
|
53
|
+
self.dir_name = dir_name if dir_name else None
|
|
54
|
+
self.auto_install = bool(install)
|
|
55
|
+
self.no_install = bool(no_install)
|
|
56
|
+
|
|
57
|
+
def run(self) -> bool:
|
|
58
|
+
'''Runs when `repopy clone` is called'''
|
|
59
|
+
if not has_python() or not has_git():
|
|
60
|
+
print(f'❌ Failed to initiate repopy due to absence of Python or Git on local machine.')
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
if not is_valid_git_url(self.repo_url):
|
|
64
|
+
print('⚠️ Invalid Git URL format')
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
if self.dir_name:
|
|
68
|
+
predicted_name = self.dir_name
|
|
69
|
+
else:
|
|
70
|
+
cleaned_url = self.repo_url.removesuffix('.git')
|
|
71
|
+
predicted_name = Path(cleaned_url).name
|
|
72
|
+
|
|
73
|
+
if not is_valid_project_name(str(predicted_name)):
|
|
74
|
+
print(f'⚠️ "{predicted_name}" is not a valid directory name.')
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
git_engine = GitEngine(self.repo_url, self.dir_name)
|
|
78
|
+
fs_engine = FileSystemEngine(git_engine.project_path)
|
|
79
|
+
|
|
80
|
+
if not git_engine.clone_repository():
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
if not fs_engine.create_virtual_environment():
|
|
84
|
+
print(f'❌ Failed to create virtual environment at {git_engine.project_path}. Cleaning up half-baked files...')
|
|
85
|
+
fs_engine.cleanup()
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
print('☑️ Cloning complete and .venv has been created.')
|
|
89
|
+
deps = fs_engine.read_requirements()
|
|
90
|
+
|
|
91
|
+
if not deps:
|
|
92
|
+
print('No dependencies to install in repository (or requirements.txt is absent).')
|
|
93
|
+
should_install = False
|
|
94
|
+
|
|
95
|
+
elif self.no_install:
|
|
96
|
+
print('Skipping installation of dependencies.')
|
|
97
|
+
should_install = False
|
|
98
|
+
|
|
99
|
+
elif self.auto_install:
|
|
100
|
+
print('Installing dependencies...')
|
|
101
|
+
should_install = True
|
|
102
|
+
|
|
103
|
+
else:
|
|
104
|
+
should_install = confirm_dependency_installation(deps)
|
|
105
|
+
|
|
106
|
+
if should_install:
|
|
107
|
+
if not fs_engine.install_dependencies():
|
|
108
|
+
print(f'✅ GitHub repository is ready at {git_engine.project_path}.')
|
|
109
|
+
print(f'⚠️ Failed to install dependencies from requirements.txt at {git_engine.project_path}')
|
|
110
|
+
print(fs_engine.get_activation_guide())
|
|
111
|
+
print('To install dependencies, run the following on your terminal:\n')
|
|
112
|
+
print(' pip install -r requirements.txt\n')
|
|
113
|
+
print('Or install them individually by `pip install <module>`\n')
|
|
114
|
+
|
|
115
|
+
if confirm_cleanup():
|
|
116
|
+
fs_engine.cleanup()
|
|
117
|
+
print('Half-baked directory has been cleaned up.')
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
print(f'✅ Success! GitHub repository is ready at {git_engine.project_path}.')
|
|
121
|
+
print(fs_engine.get_activation_guide())
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class LinkInitializer:
|
|
126
|
+
|
|
127
|
+
def __init__(self, repo_url: str, message: str | None):
|
|
128
|
+
self.repo_url = repo_url
|
|
129
|
+
self.message = message if message else 'Initial commit: Workspace structured by repopy'
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def run(self) -> bool:
|
|
133
|
+
'''Runs when `repopy link` is called'''
|
|
134
|
+
if not has_git():
|
|
135
|
+
print('⚠️ Git is not installed on this machine.')
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
if not Path('.git').exists():
|
|
139
|
+
print('❌ No local Git repository detected. Please initialize this project first.')
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
git_link_engine = GitLinkEngine(self.repo_url, self.message)
|
|
143
|
+
if git_link_engine.link_and_push():
|
|
144
|
+
print(f'✅ Success! Your local repository has been linked to {self.repo_url}.')
|
|
145
|
+
print(f'Commit message baseline: "{self.message}"')
|
|
146
|
+
return True
|
|
147
|
+
else:
|
|
148
|
+
print(f'❌ Failed to establish communication or push to remote repository at {self.repo_url}.')
|
|
149
|
+
return False
|
|
@@ -5,7 +5,7 @@ loops, and fallback default configurations.
|
|
|
5
5
|
|
|
6
6
|
import re
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from repopy.
|
|
8
|
+
from repopy.validators import is_valid_project_name
|
|
9
9
|
|
|
10
10
|
THEME_OPTIONS = {
|
|
11
11
|
'1': 'minimal',
|
|
@@ -106,4 +106,26 @@ def capture_project_manifests(cli_args) -> dict:
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
def confirm_dependency_installation(dependencies: list[str]) -> bool:
|
|
110
|
+
print('\nThis repository contains the following dependencies:')
|
|
111
|
+
for dep in dependencies:
|
|
112
|
+
print(f' • {dep}')
|
|
113
|
+
|
|
114
|
+
print()
|
|
109
115
|
|
|
116
|
+
try:
|
|
117
|
+
choice = input('Do you want to install these dependencies? [y/N]: ').strip().lower()
|
|
118
|
+
return choice in ('y', 'yes')
|
|
119
|
+
|
|
120
|
+
except (KeyboardInterrupt, EOFError):
|
|
121
|
+
print('\nSkipping installation.')
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def confirm_cleanup() -> bool:
|
|
126
|
+
try:
|
|
127
|
+
answer = input('If not, would you like to cleanup the new directory? [y/N]: ').strip().lower()
|
|
128
|
+
return answer in ('y', 'yes')
|
|
129
|
+
|
|
130
|
+
except (KeyboardInterrupt, EOFError):
|
|
131
|
+
return False
|