repopy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- repopy-0.1.0/LICENSE +21 -0
- repopy-0.1.0/PKG-INFO +108 -0
- repopy-0.1.0/README.md +95 -0
- repopy-0.1.0/pyproject.toml +19 -0
- repopy-0.1.0/setup.cfg +4 -0
- repopy-0.1.0/src/repopy/__init__.py +0 -0
- repopy-0.1.0/src/repopy/__main__.py +27 -0
- repopy-0.1.0/src/repopy/cli.py +19 -0
- repopy-0.1.0/src/repopy/dependencies.py +13 -0
- repopy-0.1.0/src/repopy/file_system.py +108 -0
- repopy-0.1.0/src/repopy/git_engine.py +64 -0
- repopy-0.1.0/src/repopy/orchestrators.py +126 -0
- repopy-0.1.0/src/repopy/os_detector.py +25 -0
- repopy-0.1.0/src/repopy/validators.py +53 -0
- repopy-0.1.0/src/repopy.egg-info/PKG-INFO +108 -0
- repopy-0.1.0/src/repopy.egg-info/SOURCES.txt +17 -0
- repopy-0.1.0/src/repopy.egg-info/dependency_links.txt +1 -0
- repopy-0.1.0/src/repopy.egg-info/entry_points.txt +2 -0
- repopy-0.1.0/src/repopy.egg-info/top_level.txt +1 -0
repopy-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Janitha Manatunga
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
repopy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repopy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular CLI workspace initializer and cloning automation tool.
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# repopy
|
|
15
|
+
|
|
16
|
+
A highly modular, cross-platform Python CLI scaffolding tool and library designed to automate the initial configuration of local Python developer workspaces and remote Python repository deployment.
|
|
17
|
+
|
|
18
|
+
[](https://pypi.org)
|
|
19
|
+
[](https://pypi.org)
|
|
20
|
+
[](https://opensource.org)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🌟 Key Features
|
|
25
|
+
|
|
26
|
+
- **Local Scaffolding (`repopy local`)**: Instantly generates standardized directory trees, configures automated multi-line `.gitignore` presets, and initializes clean local Git repositories.
|
|
27
|
+
|
|
28
|
+
- **Remote Automation (`repopy clone`)**: Downloads remote repositories, parses project structures, and automatically updates localized environment constraints.
|
|
29
|
+
|
|
30
|
+
- **Isolated Virtual Environments**: Automatically builds localized `.venv` footprints using Python's native compilation layer.
|
|
31
|
+
|
|
32
|
+
- **Cross-Platform Compatibility**: Automatically detects structural variances between Windows, macOS, and Linux to run direct executions seamlessly.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🛠️ Architectural Highlights
|
|
37
|
+
|
|
38
|
+
This package was designed following production-grade software engineering principles:
|
|
39
|
+
- **Decoupled Low-Level Engines**: Features standalone layers (`FileSystemEngine` & `GitEngine`) that are entirely decoupled from CLI routing logic, making them fully reusable as an importable library API.
|
|
40
|
+
|
|
41
|
+
- **Single Responsibility Validation**: Separates terminal argument compilation (`argparse`) from explicit data sanitization filters (`validators.py`) and environmental prerequisite verification (`dependencies.py`).
|
|
42
|
+
|
|
43
|
+
- **Defensive Error Handling & Auto-Rollback**: Leverages short-circuit evaluations and interactive terminal input cascades. In the event of network or environment breaks during deployment, it offers automated transactional folder cleanups (`shutil.rmtree`) to prevent workspace pollution.
|
|
44
|
+
|
|
45
|
+
- **Direct-Execution Strategy**: Bypasses unstable, platform-dependent shell environment injections by directly target-executing localized virtual environment binaries (`pip`).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📥 Installation
|
|
50
|
+
|
|
51
|
+
You can install `repopy` globally from PyPI using pip:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install repopy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Alternatively, you can install the latest development version directly from source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/manatunga/repopy.git
|
|
61
|
+
cd repopy
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🚀 Usage Guide
|
|
68
|
+
|
|
69
|
+
### 1. Initialize a Local Project from Scratch
|
|
70
|
+
Generate a clean, standardized local environment with an operational virtual environment and pre-seeded `.gitignore` file:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
repopy local my-new-app
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Clone and Auto-Provision a Remote Codebase
|
|
77
|
+
Clone an external codebase, create a virtual environment, scan for a `requirements.txt` file, and automatically install its dependencies:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
repopy clone https://github.comencode/httpx.git -n my-custom-workspace
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🗂️ Project Structure
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
repopy/
|
|
89
|
+
├── src/
|
|
90
|
+
│ └── repopy/
|
|
91
|
+
│ ├── __init__.py # Package initialization
|
|
92
|
+
│ ├── __main__.py # Application root entry point
|
|
93
|
+
│ ├── cli.py # CLI Argument Parsing layer
|
|
94
|
+
│ ├── orchestrators.py # Architectural workflow supervision
|
|
95
|
+
│ ├── os_detector.py # Cross-platform OS translation utility
|
|
96
|
+
│ ├── dependencies.py # Binary prerequisite verification
|
|
97
|
+
│ ├── validators.py # Data input and path path verification
|
|
98
|
+
│ ├── file_system.py # Low-level file system engine
|
|
99
|
+
│ └── git_engine.py # Low-level shell compilation engine
|
|
100
|
+
├── pyproject.toml # Build-system manifest & CLI script configuration
|
|
101
|
+
├── .gitignore
|
|
102
|
+
├── LICENSE # MIT License
|
|
103
|
+
└── README.md # Project documentation
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 📄 License
|
|
107
|
+
|
|
108
|
+
Distributed under the MIT License. See `LICENSE` for more details.
|
repopy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# repopy
|
|
2
|
+
|
|
3
|
+
A highly modular, cross-platform Python CLI scaffolding tool and library designed to automate the initial configuration of local Python developer workspaces and remote Python repository deployment.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org)
|
|
6
|
+
[](https://pypi.org)
|
|
7
|
+
[](https://opensource.org)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 🌟 Key Features
|
|
12
|
+
|
|
13
|
+
- **Local Scaffolding (`repopy local`)**: Instantly generates standardized directory trees, configures automated multi-line `.gitignore` presets, and initializes clean local Git repositories.
|
|
14
|
+
|
|
15
|
+
- **Remote Automation (`repopy clone`)**: Downloads remote repositories, parses project structures, and automatically updates localized environment constraints.
|
|
16
|
+
|
|
17
|
+
- **Isolated Virtual Environments**: Automatically builds localized `.venv` footprints using Python's native compilation layer.
|
|
18
|
+
|
|
19
|
+
- **Cross-Platform Compatibility**: Automatically detects structural variances between Windows, macOS, and Linux to run direct executions seamlessly.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 🛠️ Architectural Highlights
|
|
24
|
+
|
|
25
|
+
This package was designed following production-grade software engineering principles:
|
|
26
|
+
- **Decoupled Low-Level Engines**: Features standalone layers (`FileSystemEngine` & `GitEngine`) that are entirely decoupled from CLI routing logic, making them fully reusable as an importable library API.
|
|
27
|
+
|
|
28
|
+
- **Single Responsibility Validation**: Separates terminal argument compilation (`argparse`) from explicit data sanitization filters (`validators.py`) and environmental prerequisite verification (`dependencies.py`).
|
|
29
|
+
|
|
30
|
+
- **Defensive Error Handling & Auto-Rollback**: Leverages short-circuit evaluations and interactive terminal input cascades. In the event of network or environment breaks during deployment, it offers automated transactional folder cleanups (`shutil.rmtree`) to prevent workspace pollution.
|
|
31
|
+
|
|
32
|
+
- **Direct-Execution Strategy**: Bypasses unstable, platform-dependent shell environment injections by directly target-executing localized virtual environment binaries (`pip`).
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 📥 Installation
|
|
37
|
+
|
|
38
|
+
You can install `repopy` globally from PyPI using pip:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install repopy
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Alternatively, you can install the latest development version directly from source:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
git clone https://github.com/manatunga/repopy.git
|
|
48
|
+
cd repopy
|
|
49
|
+
pip install -e .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 🚀 Usage Guide
|
|
55
|
+
|
|
56
|
+
### 1. Initialize a Local Project from Scratch
|
|
57
|
+
Generate a clean, standardized local environment with an operational virtual environment and pre-seeded `.gitignore` file:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
repopy local my-new-app
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 2. Clone and Auto-Provision a Remote Codebase
|
|
64
|
+
Clone an external codebase, create a virtual environment, scan for a `requirements.txt` file, and automatically install its dependencies:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
repopy clone https://github.comencode/httpx.git -n my-custom-workspace
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 🗂️ Project Structure
|
|
73
|
+
|
|
74
|
+
```text
|
|
75
|
+
repopy/
|
|
76
|
+
├── src/
|
|
77
|
+
│ └── repopy/
|
|
78
|
+
│ ├── __init__.py # Package initialization
|
|
79
|
+
│ ├── __main__.py # Application root entry point
|
|
80
|
+
│ ├── cli.py # CLI Argument Parsing layer
|
|
81
|
+
│ ├── orchestrators.py # Architectural workflow supervision
|
|
82
|
+
│ ├── os_detector.py # Cross-platform OS translation utility
|
|
83
|
+
│ ├── dependencies.py # Binary prerequisite verification
|
|
84
|
+
│ ├── validators.py # Data input and path path verification
|
|
85
|
+
│ ├── file_system.py # Low-level file system engine
|
|
86
|
+
│ └── git_engine.py # Low-level shell compilation engine
|
|
87
|
+
├── pyproject.toml # Build-system manifest & CLI script configuration
|
|
88
|
+
├── .gitignore
|
|
89
|
+
├── LICENSE # MIT License
|
|
90
|
+
└── README.md # Project documentation
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 📄 License
|
|
94
|
+
|
|
95
|
+
Distributed under the MIT License. See `LICENSE` for more details.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "repopy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A modular CLI workspace initializer and cloning automation tool."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
repopy = "repopy.__main__:main"
|
repopy-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Main entrypoint of repopy. Brings together the argument parser and
|
|
3
|
+
the orchestrators together under a main function.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
from repopy.cli import parse_arguments
|
|
7
|
+
from repopy.orchestrators import LocalInitializer, CloneInitializer
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
args = parse_arguments()
|
|
11
|
+
|
|
12
|
+
if args.command == 'local':
|
|
13
|
+
print('Working on it...')
|
|
14
|
+
initializer = LocalInitializer(args.project_name)
|
|
15
|
+
initializer.run()
|
|
16
|
+
|
|
17
|
+
elif args.command == 'clone':
|
|
18
|
+
print('Working on it...')
|
|
19
|
+
initializer = CloneInitializer(args.repo_url, args.name)
|
|
20
|
+
initializer.run()
|
|
21
|
+
|
|
22
|
+
else:
|
|
23
|
+
print('Usage: repopy [local | clone] --help')
|
|
24
|
+
print('Error: Please specify a subcommand')
|
|
25
|
+
|
|
26
|
+
if __name__ == '__main__':
|
|
27
|
+
main()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Parser layer. Imports Python's built-in argparse module
|
|
3
|
+
to identify user intent and any extra information passed.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
|
|
8
|
+
def parse_arguments() -> argparse.Namespace:
|
|
9
|
+
parser = argparse.ArgumentParser()
|
|
10
|
+
subparsers = parser.add_subparsers(dest='command', required=True)
|
|
11
|
+
|
|
12
|
+
local_parser = subparsers.add_parser('local')
|
|
13
|
+
local_parser.add_argument('project_name', help='Name of the project folder')
|
|
14
|
+
|
|
15
|
+
clone_parser = subparsers.add_parser('clone')
|
|
16
|
+
clone_parser.add_argument('repo_url', help='Repository URL from Github to clone')
|
|
17
|
+
clone_parser.add_argument('-n', '--name', action='store', help='Name of the project folder')
|
|
18
|
+
|
|
19
|
+
return parser.parse_args()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Layer for Dependency-checking. Contains functions to check whether
|
|
3
|
+
expected dependencies (such as python and git) are installed.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
|
|
8
|
+
def has_python() -> bool:
|
|
9
|
+
return bool(shutil.which('python') or shutil.which('python3'))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def has_git() -> bool:
|
|
13
|
+
return bool(shutil.which('git'))
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Core Engine for the `repopy local` command. Houses the FileSystemEngine class
|
|
3
|
+
that holds the methods required to manipulate directories, create virtural
|
|
4
|
+
environments and create .gitignore and README.md templates.
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
import venv
|
|
8
|
+
import shutil
|
|
9
|
+
import logging
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class FileSystemEngine:
|
|
16
|
+
|
|
17
|
+
GITIGNORE_TEMPLATE = '''# Compiled Python files
|
|
18
|
+
__pycache__/
|
|
19
|
+
*.pyc
|
|
20
|
+
|
|
21
|
+
# Virtual environments
|
|
22
|
+
# .venv/
|
|
23
|
+
venv/
|
|
24
|
+
env/
|
|
25
|
+
ENV/
|
|
26
|
+
|
|
27
|
+
# IDEs and Editors
|
|
28
|
+
.vscode/
|
|
29
|
+
.idea/
|
|
30
|
+
*.swp
|
|
31
|
+
*.swo
|
|
32
|
+
|
|
33
|
+
# OS files
|
|
34
|
+
.DS_Store
|
|
35
|
+
Thumbs_db'''
|
|
36
|
+
|
|
37
|
+
def __init__(self, project_path: Path):
|
|
38
|
+
self.project_path = project_path
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def create_root_directory(self) -> bool:
|
|
42
|
+
'''Creates new project directory with user-specified project name'''
|
|
43
|
+
try:
|
|
44
|
+
self.project_path.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
except OSError as e:
|
|
48
|
+
logger.error(f'Failed to create directory at {self.project_path}: {e}')
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def create_gitignore(self) -> bool:
|
|
53
|
+
'''Generates a .gitignore file with a basic python gitignore template'''
|
|
54
|
+
gitignore_file = self.project_path / '.gitignore'
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
with open(gitignore_file, 'w') as f:
|
|
58
|
+
f.write(self.GITIGNORE_TEMPLATE)
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
except OSError as e:
|
|
62
|
+
logger.error(f'Failed to create gitignore file at {self.project_path}: {e}')
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def create_virtual_environment(self) -> bool:
|
|
67
|
+
'''Initializes a python virtual environment'''
|
|
68
|
+
venv_dir = self.project_path / '.venv'
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
venv.create(venv_dir, with_pip=True)
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.exception(f'Failed to create virtual environment at {self.project_path}: {e}')
|
|
76
|
+
return False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def initialize_git(self) -> bool:
|
|
80
|
+
'''Initializes git within the newly created project directory'''
|
|
81
|
+
try:
|
|
82
|
+
result = subprocess.run(['git', 'init'], cwd=self.project_path, capture_output=True, text=True)
|
|
83
|
+
return result.returncode == 0
|
|
84
|
+
except OSError as e:
|
|
85
|
+
logger.error(f'Unable to initialize git at {self.project_path}: {e}')
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cleanup(self) -> None:
|
|
90
|
+
'''Removes any partially created project directories if a failure occurs'''
|
|
91
|
+
if self.project_path.exists():
|
|
92
|
+
try:
|
|
93
|
+
shutil.rmtree(self.project_path)
|
|
94
|
+
logger.info(f'Successfully cleaned up half-baked workspace at {self.project_path}')
|
|
95
|
+
except OSError as e:
|
|
96
|
+
logger.error(f'Failed to clean up directory at {self.project_path}: {e}')
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_workspace(self) -> bool:
|
|
100
|
+
'''Orchestrate entire file-system creation sequence'''
|
|
101
|
+
|
|
102
|
+
if self.create_root_directory():
|
|
103
|
+
if self.initialize_git():
|
|
104
|
+
if self.create_gitignore():
|
|
105
|
+
if self.create_virtual_environment():
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
return False
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Core Engine for the `repopy clone` command. Houses the GitEngine class that
|
|
3
|
+
holds the methods required to cone a GitHub repository and install
|
|
4
|
+
dependencies.
|
|
5
|
+
'''
|
|
6
|
+
|
|
7
|
+
import shutil
|
|
8
|
+
import logging
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from repopy.os_detector import is_windows
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class GitEngine:
|
|
17
|
+
|
|
18
|
+
def __init__(self, repo_url: str, dir_name: str | None):
|
|
19
|
+
self.repo_url = repo_url
|
|
20
|
+
|
|
21
|
+
cleaned_url = repo_url.removesuffix('.git')
|
|
22
|
+
final_name = dir_name if dir_name else Path(cleaned_url).name
|
|
23
|
+
|
|
24
|
+
self.project_path = Path(final_name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def clone_repository(self) -> bool:
|
|
28
|
+
'''Clone GitHub repository using the given url to the given directory name'''
|
|
29
|
+
try:
|
|
30
|
+
result = subprocess.run(['git', 'clone', self.repo_url, str(self.project_path)], capture_output=True, text=True)
|
|
31
|
+
return result.returncode == 0
|
|
32
|
+
except OSError as e:
|
|
33
|
+
logger.error(f'Failed to clone repository at {self.project_path}: {e}')
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
|
|
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
|
+
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from repopy.file_system import FileSystemEngine
|
|
9
|
+
from repopy.git_engine import GitEngine
|
|
10
|
+
from repopy.dependencies import has_python, has_git
|
|
11
|
+
from repopy.validators import is_valid_project_name, is_valid_git_url
|
|
12
|
+
from repopy.os_detector import is_windows
|
|
13
|
+
|
|
14
|
+
class LocalInitializer:
|
|
15
|
+
|
|
16
|
+
def __init__(self, project_name: str):
|
|
17
|
+
self.project_name = project_name
|
|
18
|
+
self.project_path = Path(project_name)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(self) -> bool:
|
|
22
|
+
'''Runs when `repopy local` is called'''
|
|
23
|
+
if has_python():
|
|
24
|
+
if is_valid_project_name(self.project_name):
|
|
25
|
+
fs_engine = FileSystemEngine(self.project_path)
|
|
26
|
+
if fs_engine.build_workspace():
|
|
27
|
+
print(f'Success! Your project directory has been created at {self.project_path}.')
|
|
28
|
+
print('Git has been initialized.\n.gitignore has been generated.')
|
|
29
|
+
print('To activate your venv, enter the following on the terminal:\n')
|
|
30
|
+
print(f'cd {self.project_path}\n')
|
|
31
|
+
if is_windows():
|
|
32
|
+
print('.\\.venv\\Scripts\\Activate.ps1 (On Powershell)')
|
|
33
|
+
print('.\\.venv\\Scripts\\activate.bat (On cmd)\n')
|
|
34
|
+
return True
|
|
35
|
+
else:
|
|
36
|
+
print('source .venv/bin/activate\n')
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
else:
|
|
40
|
+
print(f'\nFailed to create {self.project_path} directory. Cleaning up half-baked files...')
|
|
41
|
+
fs_engine.cleanup()
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
else:
|
|
45
|
+
print(f'"\n{self.project_name}" is not a valid directory name.')
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
else:
|
|
49
|
+
print('\nPython is not installed in the local machine.')
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CloneInitializer:
|
|
54
|
+
|
|
55
|
+
def __init__(self, repo_url: str, dir_name: str | None):
|
|
56
|
+
self.repo_url = repo_url
|
|
57
|
+
self.dir_name = dir_name if dir_name else None
|
|
58
|
+
|
|
59
|
+
def run(self) -> bool:
|
|
60
|
+
'''Runs when `repopy clone` is called'''
|
|
61
|
+
if has_python() and has_git():
|
|
62
|
+
if not is_valid_git_url(self.repo_url):
|
|
63
|
+
print('Invalid Git URL format')
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
if self.dir_name:
|
|
67
|
+
predicted_name = self.dir_name
|
|
68
|
+
else:
|
|
69
|
+
cleaned_url = self.repo_url.removesuffix('.git')
|
|
70
|
+
predicted_name = Path(cleaned_url).name
|
|
71
|
+
|
|
72
|
+
if is_valid_project_name(str(predicted_name)):
|
|
73
|
+
git_engine = GitEngine(self.repo_url, self.dir_name)
|
|
74
|
+
fs_engine = FileSystemEngine(git_engine.project_path)
|
|
75
|
+
|
|
76
|
+
if git_engine.clone_repository():
|
|
77
|
+
if fs_engine.create_virtual_environment():
|
|
78
|
+
if git_engine.install_dependencies():
|
|
79
|
+
print(f'Success! GitHub repository has been cloned at {git_engine.project_path}.')
|
|
80
|
+
print(f'All dependancies have been installed from requirements.txt (if requirements.txt exists)')
|
|
81
|
+
print('To activate your venv, enter the following on the terminal:\n')
|
|
82
|
+
print(f'cd {git_engine.project_path}\n')
|
|
83
|
+
if is_windows():
|
|
84
|
+
print('.\\.venv\\Scripts\\Activate.ps1 (On Powershell)')
|
|
85
|
+
print('.\\.venv\\Scripts\\activate.bat (On cmd)\n')
|
|
86
|
+
return True
|
|
87
|
+
else:
|
|
88
|
+
print('source .venv/bin/activate\n')
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
print(f'GitHub repository has been clones at {git_engine.project_path}')
|
|
93
|
+
print(f'Failed to install dependencies from requirements.txt at {git_engine.project_path}')
|
|
94
|
+
print('To install dependencies, run the following on your terminal:\n')
|
|
95
|
+
print(f'cd {git_engine.project_path}\n')
|
|
96
|
+
if is_windows():
|
|
97
|
+
print('.\\.venv\\Scripts\\Activate.ps1 (On Powershell)')
|
|
98
|
+
print('.\\.venv\\Scripts\\activate.bat (On cmd)\n')
|
|
99
|
+
else:
|
|
100
|
+
print('source .venv/bin/activate\n')
|
|
101
|
+
print('pip install -r requirements.txt\n')
|
|
102
|
+
print('Or install them individually by `pip install <module>`\n')
|
|
103
|
+
answer = input('If not, would you like to cleanup the new directory? (Y/n): ')
|
|
104
|
+
|
|
105
|
+
if answer.strip().lower() in ['y', 'yes']:
|
|
106
|
+
print('Cleaning up half-baked files...')
|
|
107
|
+
git_engine.cleanup()
|
|
108
|
+
return False
|
|
109
|
+
else:
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
print(f'Failed to create {git_engine.project_path} directory. Cleaning up half-baked files...')
|
|
114
|
+
git_engine.cleanup()
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
else:
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
else:
|
|
121
|
+
print(f'"{predicted_name}" is not a valid directory name.')
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
else:
|
|
125
|
+
print(f'Failed to initiate repopy due to absence of Python or Git on local machine.')
|
|
126
|
+
return False
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'''
|
|
2
|
+
OS Detection layer. Contains the importable logic required to
|
|
3
|
+
find the local machine's underlying operating system.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import platform
|
|
7
|
+
|
|
8
|
+
def get_os() -> str:
|
|
9
|
+
system = platform.system()
|
|
10
|
+
|
|
11
|
+
if system == 'Windows':
|
|
12
|
+
return 'windows'
|
|
13
|
+
if system == 'Darwin':
|
|
14
|
+
return 'mac'
|
|
15
|
+
|
|
16
|
+
return 'linux'
|
|
17
|
+
|
|
18
|
+
def is_windows() -> bool:
|
|
19
|
+
return get_os() == 'windows'
|
|
20
|
+
|
|
21
|
+
def is_mac() -> bool:
|
|
22
|
+
return get_os() == 'mac'
|
|
23
|
+
|
|
24
|
+
def is_linux() -> bool:
|
|
25
|
+
return get_os() == 'linux'
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'''
|
|
2
|
+
Validation layer. Checks the validity of positional arguments
|
|
3
|
+
for both `repopy local` and `repopy clone`.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from urllib.parse import urlsplit
|
|
8
|
+
|
|
9
|
+
def is_valid_project_name(name: str) -> bool:
|
|
10
|
+
if not name or name.strip() == '':
|
|
11
|
+
return False
|
|
12
|
+
|
|
13
|
+
illegal_chars = {'<', '>', '/', '\\', ':', '*', '?', '"', '|', '!', '#'}
|
|
14
|
+
if not illegal_chars.isdisjoint(name):
|
|
15
|
+
return False
|
|
16
|
+
|
|
17
|
+
target_path = Path(name)
|
|
18
|
+
if target_path.is_dir():
|
|
19
|
+
return False
|
|
20
|
+
|
|
21
|
+
return True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_valid_git_url(url: str) -> bool:
|
|
25
|
+
if not url or not url.strip():
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
if url.startswith('git@'):
|
|
29
|
+
if not url.startswith('git@github.com:'):
|
|
30
|
+
return False
|
|
31
|
+
try:
|
|
32
|
+
_, path = url.split(':', 1)
|
|
33
|
+
except ValueError:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
elif url.startswith(('https://', 'http://')):
|
|
37
|
+
if not url.startswith('https://github.com'):
|
|
38
|
+
return False
|
|
39
|
+
path = urlsplit(url).path
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
return False
|
|
43
|
+
|
|
44
|
+
cleaned_path = path.rstrip('/')
|
|
45
|
+
|
|
46
|
+
if not cleaned_path.endswith('.git'):
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
path_segments = [seg for seg in cleaned_path.split('/') if seg]
|
|
50
|
+
if len(path_segments) < 2 or path_segments[-1] == '.git':
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
return True
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: repopy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A modular CLI workspace initializer and cloning automation tool.
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# repopy
|
|
15
|
+
|
|
16
|
+
A highly modular, cross-platform Python CLI scaffolding tool and library designed to automate the initial configuration of local Python developer workspaces and remote Python repository deployment.
|
|
17
|
+
|
|
18
|
+
[](https://pypi.org)
|
|
19
|
+
[](https://pypi.org)
|
|
20
|
+
[](https://opensource.org)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 🌟 Key Features
|
|
25
|
+
|
|
26
|
+
- **Local Scaffolding (`repopy local`)**: Instantly generates standardized directory trees, configures automated multi-line `.gitignore` presets, and initializes clean local Git repositories.
|
|
27
|
+
|
|
28
|
+
- **Remote Automation (`repopy clone`)**: Downloads remote repositories, parses project structures, and automatically updates localized environment constraints.
|
|
29
|
+
|
|
30
|
+
- **Isolated Virtual Environments**: Automatically builds localized `.venv` footprints using Python's native compilation layer.
|
|
31
|
+
|
|
32
|
+
- **Cross-Platform Compatibility**: Automatically detects structural variances between Windows, macOS, and Linux to run direct executions seamlessly.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🛠️ Architectural Highlights
|
|
37
|
+
|
|
38
|
+
This package was designed following production-grade software engineering principles:
|
|
39
|
+
- **Decoupled Low-Level Engines**: Features standalone layers (`FileSystemEngine` & `GitEngine`) that are entirely decoupled from CLI routing logic, making them fully reusable as an importable library API.
|
|
40
|
+
|
|
41
|
+
- **Single Responsibility Validation**: Separates terminal argument compilation (`argparse`) from explicit data sanitization filters (`validators.py`) and environmental prerequisite verification (`dependencies.py`).
|
|
42
|
+
|
|
43
|
+
- **Defensive Error Handling & Auto-Rollback**: Leverages short-circuit evaluations and interactive terminal input cascades. In the event of network or environment breaks during deployment, it offers automated transactional folder cleanups (`shutil.rmtree`) to prevent workspace pollution.
|
|
44
|
+
|
|
45
|
+
- **Direct-Execution Strategy**: Bypasses unstable, platform-dependent shell environment injections by directly target-executing localized virtual environment binaries (`pip`).
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 📥 Installation
|
|
50
|
+
|
|
51
|
+
You can install `repopy` globally from PyPI using pip:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install repopy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Alternatively, you can install the latest development version directly from source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/manatunga/repopy.git
|
|
61
|
+
cd repopy
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🚀 Usage Guide
|
|
68
|
+
|
|
69
|
+
### 1. Initialize a Local Project from Scratch
|
|
70
|
+
Generate a clean, standardized local environment with an operational virtual environment and pre-seeded `.gitignore` file:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
repopy local my-new-app
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Clone and Auto-Provision a Remote Codebase
|
|
77
|
+
Clone an external codebase, create a virtual environment, scan for a `requirements.txt` file, and automatically install its dependencies:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
repopy clone https://github.comencode/httpx.git -n my-custom-workspace
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🗂️ Project Structure
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
repopy/
|
|
89
|
+
├── src/
|
|
90
|
+
│ └── repopy/
|
|
91
|
+
│ ├── __init__.py # Package initialization
|
|
92
|
+
│ ├── __main__.py # Application root entry point
|
|
93
|
+
│ ├── cli.py # CLI Argument Parsing layer
|
|
94
|
+
│ ├── orchestrators.py # Architectural workflow supervision
|
|
95
|
+
│ ├── os_detector.py # Cross-platform OS translation utility
|
|
96
|
+
│ ├── dependencies.py # Binary prerequisite verification
|
|
97
|
+
│ ├── validators.py # Data input and path path verification
|
|
98
|
+
│ ├── file_system.py # Low-level file system engine
|
|
99
|
+
│ └── git_engine.py # Low-level shell compilation engine
|
|
100
|
+
├── pyproject.toml # Build-system manifest & CLI script configuration
|
|
101
|
+
├── .gitignore
|
|
102
|
+
├── LICENSE # MIT License
|
|
103
|
+
└── README.md # Project documentation
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 📄 License
|
|
107
|
+
|
|
108
|
+
Distributed under the MIT License. See `LICENSE` for more details.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/repopy/__init__.py
|
|
5
|
+
src/repopy/__main__.py
|
|
6
|
+
src/repopy/cli.py
|
|
7
|
+
src/repopy/dependencies.py
|
|
8
|
+
src/repopy/file_system.py
|
|
9
|
+
src/repopy/git_engine.py
|
|
10
|
+
src/repopy/orchestrators.py
|
|
11
|
+
src/repopy/os_detector.py
|
|
12
|
+
src/repopy/validators.py
|
|
13
|
+
src/repopy.egg-info/PKG-INFO
|
|
14
|
+
src/repopy.egg-info/SOURCES.txt
|
|
15
|
+
src/repopy.egg-info/dependency_links.txt
|
|
16
|
+
src/repopy.egg-info/entry_points.txt
|
|
17
|
+
src/repopy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
repopy
|