codefetch-cli 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- codefetch_cli-1.2.0/LICENSE +21 -0
- codefetch_cli-1.2.0/PKG-INFO +182 -0
- codefetch_cli-1.2.0/README.md +160 -0
- codefetch_cli-1.2.0/codefetch/__init__.py +5 -0
- codefetch_cli-1.2.0/codefetch/cli.py +597 -0
- codefetch_cli-1.2.0/codefetch_cli.egg-info/PKG-INFO +182 -0
- codefetch_cli-1.2.0/codefetch_cli.egg-info/SOURCES.txt +11 -0
- codefetch_cli-1.2.0/codefetch_cli.egg-info/dependency_links.txt +1 -0
- codefetch_cli-1.2.0/codefetch_cli.egg-info/entry_points.txt +2 -0
- codefetch_cli-1.2.0/codefetch_cli.egg-info/top_level.txt +1 -0
- codefetch_cli-1.2.0/pyproject.toml +28 -0
- codefetch_cli-1.2.0/setup.cfg +4 -0
- codefetch_cli-1.2.0/setup.py +30 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Reaver101
|
|
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.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codefetch-cli
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Quickly fetch code files from GitHub repositories with simple commands
|
|
5
|
+
Home-page: https://github.com/InzamamulQureshi/Codefetch
|
|
6
|
+
Author: Inzamamul Qureshi
|
|
7
|
+
Author-email: Inzamamul Qureshi <inzamamulqureshi@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/InzamamulQureshi/Codefetch
|
|
10
|
+
Project-URL: Repository, https://github.com/InzamamulQureshi/Codefetch
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
|
|
23
|
+
# CodeFetch
|
|
24
|
+
|
|
25
|
+
Quickly fetch code files from GitHub repositories with simple commands.
|
|
26
|
+
|
|
27
|
+
CodeFetch is a lightweight command-line utility designed to quickly inspect, preview, and download individual source code files directly from any GitHub repository without needing to clone full repositories.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Any User & Repository**: Fetch files from any public GitHub repository using intuitive syntax (`codefetch <user>/<repo> <filename>`).
|
|
34
|
+
- **Terminal Preview**: Inspect code directly in your console with `--show` without saving it to disk.
|
|
35
|
+
- **Automatic Path & Case Resolution**: Automatically resolves case mismatches (e.g., `exp1.c` -> `EXP1.c`) and finds files nested inside subdirectories.
|
|
36
|
+
- **Repository Discovery**: List all public repositories for any GitHub user with `--repos`.
|
|
37
|
+
- **Tree Exploration**: List all supported code files within a repository with `--list`.
|
|
38
|
+
- **Persistent Defaults**: Configure your preferred default repository and user with `--set-default` and `--set-default-user`.
|
|
39
|
+
- **No Dependencies**: Built using the Python standard library with zero third-party runtime dependencies.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
### From PyPI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install codefetch-cli
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### From GitHub
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install git+https://github.com/InzamamulQureshi/Codefetch.git
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### From Local Source
|
|
58
|
+
|
|
59
|
+
Clone the repository and install locally:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/InzamamulQureshi/Codefetch.git
|
|
63
|
+
cd Codefetch
|
|
64
|
+
pip install .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quick Start & Usage
|
|
70
|
+
|
|
71
|
+
### 1. Download Files
|
|
72
|
+
|
|
73
|
+
Fetch a file from any GitHub repository:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Fetch from a specific user and repository
|
|
77
|
+
codefetch InzamamulQureshi/Codefetch setup.py
|
|
78
|
+
|
|
79
|
+
# Fetch using explicit flags
|
|
80
|
+
codefetch -u InzamamulQureshi -r Codefetch setup.py
|
|
81
|
+
|
|
82
|
+
# Fetch from a repository under the default user
|
|
83
|
+
codefetch DS EXP1.c
|
|
84
|
+
codefetch -r DS EXP1.c
|
|
85
|
+
|
|
86
|
+
# Shorthand path syntax
|
|
87
|
+
codefetch DS/EXP1.c
|
|
88
|
+
|
|
89
|
+
# Fetch from the default repository
|
|
90
|
+
codefetch 8.1.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Save to a custom filename or directory using `-o` / `--output`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
codefetch -r DS EXP1.c -o my_experiment.c
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Preview in Terminal (Without Downloading)
|
|
100
|
+
|
|
101
|
+
Display the contents of a file directly in stdout:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Preview file from default repository
|
|
105
|
+
codefetch --show 8.1.py
|
|
106
|
+
|
|
107
|
+
# Preview file from another repository
|
|
108
|
+
codefetch --show DS/EXP1.c
|
|
109
|
+
|
|
110
|
+
# Preview file from any user repository
|
|
111
|
+
codefetch --show InzamamulQureshi/Codefetch setup.py
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Explore Repositories and Files
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# List all public repositories for default user
|
|
118
|
+
codefetch --repos
|
|
119
|
+
|
|
120
|
+
# List all public repositories for a specific user
|
|
121
|
+
codefetch --repos -u InzamamulQureshi
|
|
122
|
+
codefetch --repos torvalds
|
|
123
|
+
|
|
124
|
+
# List code files in the default repository
|
|
125
|
+
codefetch --list
|
|
126
|
+
|
|
127
|
+
# List code files in another repository
|
|
128
|
+
codefetch -r DS --list
|
|
129
|
+
codefetch --list InzamamulQureshi/Codefetch
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Configuration and Defaults
|
|
133
|
+
|
|
134
|
+
Set persistent default preferences stored in `~/.codefetch.json`:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Set default repository
|
|
138
|
+
codefetch --set-default DS
|
|
139
|
+
|
|
140
|
+
# Set default user
|
|
141
|
+
codefetch --set-default-user InzamamulQureshi
|
|
142
|
+
|
|
143
|
+
# View current configuration
|
|
144
|
+
codefetch --config
|
|
145
|
+
|
|
146
|
+
# Reset configuration back to factory defaults
|
|
147
|
+
codefetch --reset-config
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Command Reference
|
|
153
|
+
|
|
154
|
+
| Flag | Description | Example |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `-r`, `--repo` | Target repository name or URL | `codefetch -r DS EXP1.c` |
|
|
157
|
+
| `-u`, `--user` | Target GitHub user or organization | `codefetch -u octocat -r Spoon-Knife --list` |
|
|
158
|
+
| `-b`, `--branch` | Branch name (default: `main`) | `codefetch -b main DS EXP1.c` |
|
|
159
|
+
| `-s`, `--show` | Print file contents to terminal | `codefetch --show 8.1.py` |
|
|
160
|
+
| `-l`, `--list` | List available code files | `codefetch --list DS` |
|
|
161
|
+
| `-R`, `--repos` | List public repositories for user | `codefetch --repos -u InzamamulQureshi` |
|
|
162
|
+
| `-o`, `--output` | Specify destination output filename | `codefetch -r DS EXP1.c -o local.c` |
|
|
163
|
+
| `--set-default` | Set persistent default repository | `codefetch --set-default DS` |
|
|
164
|
+
| `--set-default-user` | Set persistent default user | `codefetch --set-default-user InzamamulQureshi` |
|
|
165
|
+
| `--config` | Display active configuration | `codefetch --config` |
|
|
166
|
+
| `--reset-config` | Reset configuration to defaults | `codefetch --reset-config` |
|
|
167
|
+
| `-v`, `--version` | Display version information | `codefetch --version` |
|
|
168
|
+
| `-h`, `--help` | Display help and usage message | `codefetch --help` |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Supported File Extensions
|
|
173
|
+
|
|
174
|
+
CodeFetch recognizes and lists common source and document files:
|
|
175
|
+
|
|
176
|
+
`.py`, `.c`, `.cpp`, `.h`, `.hpp`, `.java`, `.js`, `.ts`, `.sql`, `.html`, `.css`, `.txt`, `.asm`, `.s`, `.hex`, `.json`, `.sh`, `.rs`, `.go`, `.cs`, `.ipynb`, `.md`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# CodeFetch
|
|
2
|
+
|
|
3
|
+
Quickly fetch code files from GitHub repositories with simple commands.
|
|
4
|
+
|
|
5
|
+
CodeFetch is a lightweight command-line utility designed to quickly inspect, preview, and download individual source code files directly from any GitHub repository without needing to clone full repositories.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Any User & Repository**: Fetch files from any public GitHub repository using intuitive syntax (`codefetch <user>/<repo> <filename>`).
|
|
12
|
+
- **Terminal Preview**: Inspect code directly in your console with `--show` without saving it to disk.
|
|
13
|
+
- **Automatic Path & Case Resolution**: Automatically resolves case mismatches (e.g., `exp1.c` -> `EXP1.c`) and finds files nested inside subdirectories.
|
|
14
|
+
- **Repository Discovery**: List all public repositories for any GitHub user with `--repos`.
|
|
15
|
+
- **Tree Exploration**: List all supported code files within a repository with `--list`.
|
|
16
|
+
- **Persistent Defaults**: Configure your preferred default repository and user with `--set-default` and `--set-default-user`.
|
|
17
|
+
- **No Dependencies**: Built using the Python standard library with zero third-party runtime dependencies.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
### From PyPI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install codefetch-cli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### From GitHub
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install git+https://github.com/InzamamulQureshi/Codefetch.git
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### From Local Source
|
|
36
|
+
|
|
37
|
+
Clone the repository and install locally:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/InzamamulQureshi/Codefetch.git
|
|
41
|
+
cd Codefetch
|
|
42
|
+
pip install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start & Usage
|
|
48
|
+
|
|
49
|
+
### 1. Download Files
|
|
50
|
+
|
|
51
|
+
Fetch a file from any GitHub repository:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Fetch from a specific user and repository
|
|
55
|
+
codefetch InzamamulQureshi/Codefetch setup.py
|
|
56
|
+
|
|
57
|
+
# Fetch using explicit flags
|
|
58
|
+
codefetch -u InzamamulQureshi -r Codefetch setup.py
|
|
59
|
+
|
|
60
|
+
# Fetch from a repository under the default user
|
|
61
|
+
codefetch DS EXP1.c
|
|
62
|
+
codefetch -r DS EXP1.c
|
|
63
|
+
|
|
64
|
+
# Shorthand path syntax
|
|
65
|
+
codefetch DS/EXP1.c
|
|
66
|
+
|
|
67
|
+
# Fetch from the default repository
|
|
68
|
+
codefetch 8.1.py
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Save to a custom filename or directory using `-o` / `--output`:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
codefetch -r DS EXP1.c -o my_experiment.c
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Preview in Terminal (Without Downloading)
|
|
78
|
+
|
|
79
|
+
Display the contents of a file directly in stdout:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Preview file from default repository
|
|
83
|
+
codefetch --show 8.1.py
|
|
84
|
+
|
|
85
|
+
# Preview file from another repository
|
|
86
|
+
codefetch --show DS/EXP1.c
|
|
87
|
+
|
|
88
|
+
# Preview file from any user repository
|
|
89
|
+
codefetch --show InzamamulQureshi/Codefetch setup.py
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. Explore Repositories and Files
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# List all public repositories for default user
|
|
96
|
+
codefetch --repos
|
|
97
|
+
|
|
98
|
+
# List all public repositories for a specific user
|
|
99
|
+
codefetch --repos -u InzamamulQureshi
|
|
100
|
+
codefetch --repos torvalds
|
|
101
|
+
|
|
102
|
+
# List code files in the default repository
|
|
103
|
+
codefetch --list
|
|
104
|
+
|
|
105
|
+
# List code files in another repository
|
|
106
|
+
codefetch -r DS --list
|
|
107
|
+
codefetch --list InzamamulQureshi/Codefetch
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 4. Configuration and Defaults
|
|
111
|
+
|
|
112
|
+
Set persistent default preferences stored in `~/.codefetch.json`:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Set default repository
|
|
116
|
+
codefetch --set-default DS
|
|
117
|
+
|
|
118
|
+
# Set default user
|
|
119
|
+
codefetch --set-default-user InzamamulQureshi
|
|
120
|
+
|
|
121
|
+
# View current configuration
|
|
122
|
+
codefetch --config
|
|
123
|
+
|
|
124
|
+
# Reset configuration back to factory defaults
|
|
125
|
+
codefetch --reset-config
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Command Reference
|
|
131
|
+
|
|
132
|
+
| Flag | Description | Example |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `-r`, `--repo` | Target repository name or URL | `codefetch -r DS EXP1.c` |
|
|
135
|
+
| `-u`, `--user` | Target GitHub user or organization | `codefetch -u octocat -r Spoon-Knife --list` |
|
|
136
|
+
| `-b`, `--branch` | Branch name (default: `main`) | `codefetch -b main DS EXP1.c` |
|
|
137
|
+
| `-s`, `--show` | Print file contents to terminal | `codefetch --show 8.1.py` |
|
|
138
|
+
| `-l`, `--list` | List available code files | `codefetch --list DS` |
|
|
139
|
+
| `-R`, `--repos` | List public repositories for user | `codefetch --repos -u InzamamulQureshi` |
|
|
140
|
+
| `-o`, `--output` | Specify destination output filename | `codefetch -r DS EXP1.c -o local.c` |
|
|
141
|
+
| `--set-default` | Set persistent default repository | `codefetch --set-default DS` |
|
|
142
|
+
| `--set-default-user` | Set persistent default user | `codefetch --set-default-user InzamamulQureshi` |
|
|
143
|
+
| `--config` | Display active configuration | `codefetch --config` |
|
|
144
|
+
| `--reset-config` | Reset configuration to defaults | `codefetch --reset-config` |
|
|
145
|
+
| `-v`, `--version` | Display version information | `codefetch --version` |
|
|
146
|
+
| `-h`, `--help` | Display help and usage message | `codefetch --help` |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Supported File Extensions
|
|
151
|
+
|
|
152
|
+
CodeFetch recognizes and lists common source and document files:
|
|
153
|
+
|
|
154
|
+
`.py`, `.c`, `.cpp`, `.h`, `.hpp`, `.java`, `.js`, `.ts`, `.sql`, `.html`, `.css`, `.txt`, `.asm`, `.s`, `.hex`, `.json`, `.sh`, `.rs`, `.go`, `.cs`, `.ipynb`, `.md`
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CodeFetch CLI
|
|
3
|
+
Quickly fetch code files from GitHub repositories with simple commands.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.request
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
DEFAULT_OWNER = "Reaver101"
|
|
16
|
+
DEFAULT_REPO = "MPL"
|
|
17
|
+
DEFAULT_BRANCH = "main"
|
|
18
|
+
|
|
19
|
+
CONFIG_FILE = Path.home() / ".codefetch.json"
|
|
20
|
+
|
|
21
|
+
# Supported code extensions
|
|
22
|
+
CODE_EXTENSIONS = {
|
|
23
|
+
".py", ".c", ".cpp", ".h", ".hpp", ".java", ".js", ".ts",
|
|
24
|
+
".sql", ".html", ".css", ".txt", ".asm", ".s", ".hex",
|
|
25
|
+
".json", ".sh", ".rs", ".go", ".cs", ".ipynb", ".md"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_config():
|
|
30
|
+
"""Load configuration from environment variables and local config file."""
|
|
31
|
+
config = {
|
|
32
|
+
"owner": os.environ.get("CODEFETCH_USER", DEFAULT_OWNER),
|
|
33
|
+
"repo": os.environ.get("CODEFETCH_REPO", DEFAULT_REPO),
|
|
34
|
+
"branch": os.environ.get("CODEFETCH_BRANCH", DEFAULT_BRANCH),
|
|
35
|
+
}
|
|
36
|
+
if CONFIG_FILE.exists():
|
|
37
|
+
try:
|
|
38
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
39
|
+
saved = json.load(f)
|
|
40
|
+
if isinstance(saved, dict):
|
|
41
|
+
if saved.get("owner"):
|
|
42
|
+
config["owner"] = saved["owner"]
|
|
43
|
+
if saved.get("repo"):
|
|
44
|
+
config["repo"] = saved["repo"]
|
|
45
|
+
if saved.get("branch"):
|
|
46
|
+
config["branch"] = saved["branch"]
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
return config
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def save_config(owner=None, repo=None, branch=None):
|
|
53
|
+
"""Save configuration to ~/.codefetch.json."""
|
|
54
|
+
config = load_config()
|
|
55
|
+
if owner:
|
|
56
|
+
config["owner"] = owner
|
|
57
|
+
if repo:
|
|
58
|
+
config["repo"] = repo
|
|
59
|
+
if branch:
|
|
60
|
+
config["branch"] = branch
|
|
61
|
+
try:
|
|
62
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
63
|
+
json.dump(config, f, indent=2)
|
|
64
|
+
return True
|
|
65
|
+
except Exception as e:
|
|
66
|
+
print(f"Error saving configuration: {e}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_headers():
|
|
71
|
+
"""Get HTTP headers for GitHub requests."""
|
|
72
|
+
headers = {
|
|
73
|
+
"User-Agent": "codefetch",
|
|
74
|
+
"Accept": "application/vnd.github+json",
|
|
75
|
+
}
|
|
76
|
+
token = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
77
|
+
if token:
|
|
78
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
79
|
+
return headers
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_json(url):
|
|
83
|
+
"""Fetch and decode JSON from a URL."""
|
|
84
|
+
request = urllib.request.Request(url, headers=get_headers())
|
|
85
|
+
with urllib.request.urlopen(request, timeout=15) as response:
|
|
86
|
+
return json.loads(response.read().decode("utf-8"))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def parse_repo_identifier(repo_str, default_owner=DEFAULT_OWNER):
|
|
90
|
+
"""Extract (owner, repo) from a repository flag, name, or URL."""
|
|
91
|
+
if not repo_str:
|
|
92
|
+
return default_owner, None
|
|
93
|
+
repo_str = repo_str.strip()
|
|
94
|
+
m = re.match(
|
|
95
|
+
r"https?://github\.com/([^/]+)/([^/]+?)(?:\.git|/.*)?$",
|
|
96
|
+
repo_str,
|
|
97
|
+
re.IGNORECASE,
|
|
98
|
+
)
|
|
99
|
+
if m:
|
|
100
|
+
return m.group(1), m.group(2)
|
|
101
|
+
if "/" in repo_str:
|
|
102
|
+
parts = repo_str.split("/", 1)
|
|
103
|
+
return parts[0], parts[1].removesuffix(".git")
|
|
104
|
+
return default_owner, repo_str.removesuffix(".git")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_repo_tree(owner, repo, branch="main"):
|
|
108
|
+
"""Fetch the Git tree for a repository, with fallback to master if main is absent."""
|
|
109
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1"
|
|
110
|
+
try:
|
|
111
|
+
return get_json(url)
|
|
112
|
+
except urllib.error.HTTPError as e:
|
|
113
|
+
if e.code == 404 and branch == "main":
|
|
114
|
+
url_master = f"https://api.github.com/repos/{owner}/{repo}/git/trees/master?recursive=1"
|
|
115
|
+
try:
|
|
116
|
+
return get_json(url_master)
|
|
117
|
+
except Exception:
|
|
118
|
+
pass
|
|
119
|
+
raise
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_user_repos(owner):
|
|
123
|
+
"""Fetch public repositories for a given GitHub owner."""
|
|
124
|
+
url = f"https://api.github.com/users/{owner}/repos?per_page=100&sort=updated"
|
|
125
|
+
try:
|
|
126
|
+
return get_json(url)
|
|
127
|
+
except Exception:
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def list_repos(owner=DEFAULT_OWNER, default_repo=DEFAULT_REPO):
|
|
132
|
+
"""List all public repositories for the owner."""
|
|
133
|
+
try:
|
|
134
|
+
repos = get_user_repos(owner)
|
|
135
|
+
if not repos:
|
|
136
|
+
print(f"No public repositories found for user '{owner}'.")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
print(f"Repositories for '{owner}':\n")
|
|
140
|
+
for r in repos:
|
|
141
|
+
name = r.get("name", "")
|
|
142
|
+
desc = r.get("description") or ""
|
|
143
|
+
is_def = " (default)" if name.lower() == default_repo.lower() else ""
|
|
144
|
+
marker = "*" if is_def else "-"
|
|
145
|
+
desc_text = f" - {desc}" if desc else ""
|
|
146
|
+
print(f" {marker} {name}{is_def}{desc_text}")
|
|
147
|
+
|
|
148
|
+
print("\nCommands:")
|
|
149
|
+
print(f" codefetch -u {owner} -r <repo> --list List files in a repository")
|
|
150
|
+
print(f" codefetch -u {owner} -r <repo> <filename> Download a file from a repository")
|
|
151
|
+
print(f" codefetch --set-default <repo> Set default repository")
|
|
152
|
+
|
|
153
|
+
except urllib.error.HTTPError as e:
|
|
154
|
+
print(f"Could not list repositories: HTTP {e.code}")
|
|
155
|
+
except urllib.error.URLError:
|
|
156
|
+
print("Could not connect to GitHub.")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(f"Could not list repositories: {e}")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def list_files(owner, repo, branch="main"):
|
|
162
|
+
"""List code files in a repository."""
|
|
163
|
+
try:
|
|
164
|
+
data = get_repo_tree(owner, repo, branch)
|
|
165
|
+
if data.get("truncated"):
|
|
166
|
+
print("Warning: GitHub returned a truncated file list.")
|
|
167
|
+
|
|
168
|
+
files = []
|
|
169
|
+
for item in data.get("tree", []):
|
|
170
|
+
if item.get("type") != "blob":
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
path = item.get("path", "")
|
|
174
|
+
suffix = ""
|
|
175
|
+
if "." in path.rsplit("/", 1)[-1]:
|
|
176
|
+
suffix = "." + path.rsplit("/", 1)[-1].rsplit(".", 1)[-1].lower()
|
|
177
|
+
|
|
178
|
+
if suffix in CODE_EXTENSIONS:
|
|
179
|
+
files.append(path)
|
|
180
|
+
|
|
181
|
+
files.sort(key=str.lower)
|
|
182
|
+
|
|
183
|
+
if not files:
|
|
184
|
+
print(f"No supported code files found in {owner}/{repo}.")
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
print(f"Available files in {owner}/{repo} ({branch}):\n")
|
|
188
|
+
for i, path in enumerate(files, 1):
|
|
189
|
+
print(f"{i:>3}. {path}")
|
|
190
|
+
|
|
191
|
+
print(f"\nTotal: {len(files)} files")
|
|
192
|
+
|
|
193
|
+
except urllib.error.HTTPError as e:
|
|
194
|
+
print(f"Could not list files for {owner}/{repo}: HTTP {e.code}")
|
|
195
|
+
except urllib.error.URLError:
|
|
196
|
+
print("Could not connect to GitHub.")
|
|
197
|
+
except Exception as e:
|
|
198
|
+
print(f"Could not list files for {owner}/{repo}: {e}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def get_stem(name):
|
|
202
|
+
"""Extract filename stem by stripping supported code extensions."""
|
|
203
|
+
name_lower = name.lower()
|
|
204
|
+
for ext in CODE_EXTENSIONS:
|
|
205
|
+
if name_lower.endswith(ext):
|
|
206
|
+
return name[:-len(ext)]
|
|
207
|
+
return name
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def resolve_file_in_tree(tree_data, target_filename):
|
|
211
|
+
"""
|
|
212
|
+
Search tree for target_filename:
|
|
213
|
+
1. Exact match (case-insensitive) on full path
|
|
214
|
+
2. Exact match (case-insensitive) on basename
|
|
215
|
+
3. Match without extension (e.g. 'exp1' -> 'EXP1.c')
|
|
216
|
+
"""
|
|
217
|
+
blobs = [item["path"] for item in tree_data.get("tree", []) if item.get("type") == "blob"]
|
|
218
|
+
target_clean = target_filename.strip("/\\").lower()
|
|
219
|
+
target_base = target_clean.rsplit("/", 1)[-1]
|
|
220
|
+
|
|
221
|
+
# 1. Exact match (case-insensitive) on full path
|
|
222
|
+
for p in blobs:
|
|
223
|
+
if p.lower() == target_clean:
|
|
224
|
+
return p
|
|
225
|
+
|
|
226
|
+
# 2. Exact match (case-insensitive) on basename
|
|
227
|
+
for p in blobs:
|
|
228
|
+
base = p.rsplit("/", 1)[-1].lower()
|
|
229
|
+
if base == target_base:
|
|
230
|
+
return p
|
|
231
|
+
|
|
232
|
+
# 3. Match without extension
|
|
233
|
+
target_stem = get_stem(target_base).lower()
|
|
234
|
+
candidates = []
|
|
235
|
+
for p in blobs:
|
|
236
|
+
base = p.rsplit("/", 1)[-1]
|
|
237
|
+
base_stem = get_stem(base).lower()
|
|
238
|
+
if base_stem == target_stem:
|
|
239
|
+
candidates.append(p)
|
|
240
|
+
|
|
241
|
+
if len(candidates) == 1:
|
|
242
|
+
return candidates[0]
|
|
243
|
+
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def find_file_in_other_repos(owner, current_repo, target_filename):
|
|
248
|
+
"""Search other public repositories of the user for the target file."""
|
|
249
|
+
try:
|
|
250
|
+
repos_data = get_user_repos(owner)
|
|
251
|
+
for r in repos_data:
|
|
252
|
+
r_name = r.get("name")
|
|
253
|
+
if not r_name or r_name.lower() == current_repo.lower():
|
|
254
|
+
continue
|
|
255
|
+
default_branch = r.get("default_branch", "main")
|
|
256
|
+
try:
|
|
257
|
+
tree_data = get_repo_tree(owner, r_name, default_branch)
|
|
258
|
+
match = resolve_file_in_tree(tree_data, target_filename)
|
|
259
|
+
if match:
|
|
260
|
+
return r_name, match
|
|
261
|
+
except Exception:
|
|
262
|
+
continue
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
return None, None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def fetch_file(filename, owner=DEFAULT_OWNER, repo=DEFAULT_REPO, branch=DEFAULT_BRANCH,
|
|
269
|
+
show=False, output=None):
|
|
270
|
+
"""Fetch or download a file from GitHub."""
|
|
271
|
+
clean_filename = filename.strip("/\\")
|
|
272
|
+
|
|
273
|
+
def download_url(url):
|
|
274
|
+
req = urllib.request.Request(url, headers={"User-Agent": "codefetch"})
|
|
275
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
276
|
+
return resp.read().decode("utf-8")
|
|
277
|
+
|
|
278
|
+
# 1. Direct raw fetch first (fastest, preserves GitHub API rate limit)
|
|
279
|
+
raw_base = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}"
|
|
280
|
+
url = f"{raw_base}/{clean_filename}"
|
|
281
|
+
|
|
282
|
+
content = None
|
|
283
|
+
resolved_path = clean_filename
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
content = download_url(url)
|
|
287
|
+
except urllib.error.HTTPError as e:
|
|
288
|
+
if e.code == 404:
|
|
289
|
+
# 2. Direct fetch 404ed. Try smart resolution with repo tree
|
|
290
|
+
try:
|
|
291
|
+
tree_data = get_repo_tree(owner, repo, branch)
|
|
292
|
+
matched = resolve_file_in_tree(tree_data, clean_filename)
|
|
293
|
+
if matched:
|
|
294
|
+
resolved_path = matched
|
|
295
|
+
content = download_url(f"{raw_base}/{resolved_path}")
|
|
296
|
+
if resolved_path != clean_filename:
|
|
297
|
+
print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
|
|
298
|
+
else:
|
|
299
|
+
# Check other public repositories for the same user
|
|
300
|
+
other_repo, other_match = find_file_in_other_repos(owner, repo, clean_filename)
|
|
301
|
+
if other_repo:
|
|
302
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
|
|
303
|
+
print(f"\n[Tip] Found '{other_match}' in repository '{other_repo}'!")
|
|
304
|
+
print(f"To fetch it, run:\n codefetch -u {owner} -r {other_repo} {other_match}")
|
|
305
|
+
return
|
|
306
|
+
else:
|
|
307
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
|
|
308
|
+
print(f"Run 'codefetch -u {owner} -r {repo} --list' to see available files.")
|
|
309
|
+
return
|
|
310
|
+
except Exception:
|
|
311
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
|
|
312
|
+
return
|
|
313
|
+
else:
|
|
314
|
+
print(f"Download failed: HTTP {e.code}")
|
|
315
|
+
return
|
|
316
|
+
except urllib.error.URLError:
|
|
317
|
+
print("Could not connect to GitHub.")
|
|
318
|
+
return
|
|
319
|
+
except UnicodeDecodeError:
|
|
320
|
+
print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
if content is None:
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
if show:
|
|
327
|
+
print(content)
|
|
328
|
+
else:
|
|
329
|
+
out_name = output or resolved_path.rsplit("/", 1)[-1]
|
|
330
|
+
try:
|
|
331
|
+
with open(out_name, "w", encoding="utf-8") as f:
|
|
332
|
+
f.write(content)
|
|
333
|
+
print(f"Downloaded: {out_name} (from {owner}/{repo})")
|
|
334
|
+
except Exception as e:
|
|
335
|
+
print(f"Error saving file '{out_name}': {e}")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def build_parser():
|
|
339
|
+
"""Build argument parser with full CLI options."""
|
|
340
|
+
parser = argparse.ArgumentParser(
|
|
341
|
+
prog="codefetch",
|
|
342
|
+
description="Quickly fetch code files from GitHub repositories with simple commands.",
|
|
343
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
344
|
+
epilog="""
|
|
345
|
+
Usage Examples:
|
|
346
|
+
|
|
347
|
+
Fetch from default repository:
|
|
348
|
+
codefetch 8.1.py
|
|
349
|
+
codefetch --show 8.1.py
|
|
350
|
+
codefetch --list
|
|
351
|
+
|
|
352
|
+
Fetch from other repositories:
|
|
353
|
+
codefetch -r DS EXP1.c
|
|
354
|
+
codefetch DS EXP1.c
|
|
355
|
+
codefetch DS/EXP1.c
|
|
356
|
+
codefetch -r DS --list
|
|
357
|
+
|
|
358
|
+
Fetch from any GitHub user and repository:
|
|
359
|
+
codefetch InzamamulQureshi/Codefetch setup.py
|
|
360
|
+
codefetch -u InzamamulQureshi -r Codefetch setup.py
|
|
361
|
+
codefetch --list InzamamulQureshi/Codefetch
|
|
362
|
+
codefetch --repos -u InzamamulQureshi
|
|
363
|
+
|
|
364
|
+
Configuration:
|
|
365
|
+
codefetch --set-default DS
|
|
366
|
+
codefetch --set-default-user InzamamulQureshi
|
|
367
|
+
codefetch --config
|
|
368
|
+
codefetch --reset-config
|
|
369
|
+
"""
|
|
370
|
+
)
|
|
371
|
+
parser.add_argument(
|
|
372
|
+
"args",
|
|
373
|
+
nargs="*",
|
|
374
|
+
help="Target specification: [user/repo] [filename] or [filename]",
|
|
375
|
+
)
|
|
376
|
+
parser.add_argument(
|
|
377
|
+
"-r", "--repo",
|
|
378
|
+
default=None,
|
|
379
|
+
help="Repository name or URL (e.g. DS, Codefetch, or user/repo)",
|
|
380
|
+
)
|
|
381
|
+
parser.add_argument(
|
|
382
|
+
"-u", "--user",
|
|
383
|
+
default=None,
|
|
384
|
+
help=f"GitHub user/org (default: {DEFAULT_OWNER})",
|
|
385
|
+
)
|
|
386
|
+
parser.add_argument(
|
|
387
|
+
"-b", "--branch",
|
|
388
|
+
default=None,
|
|
389
|
+
help=f"Branch name (default: {DEFAULT_BRANCH})",
|
|
390
|
+
)
|
|
391
|
+
parser.add_argument(
|
|
392
|
+
"-l", "--list",
|
|
393
|
+
action="store_true",
|
|
394
|
+
help="List available code files in the repository",
|
|
395
|
+
)
|
|
396
|
+
parser.add_argument(
|
|
397
|
+
"-R", "--repos",
|
|
398
|
+
action="store_true",
|
|
399
|
+
help="List all public repositories for the user",
|
|
400
|
+
)
|
|
401
|
+
parser.add_argument(
|
|
402
|
+
"-s", "--show",
|
|
403
|
+
action="store_true",
|
|
404
|
+
help="Display file content in terminal without saving to disk",
|
|
405
|
+
)
|
|
406
|
+
parser.add_argument(
|
|
407
|
+
"-o", "--output",
|
|
408
|
+
default=None,
|
|
409
|
+
help="Custom destination filename or output path",
|
|
410
|
+
)
|
|
411
|
+
parser.add_argument(
|
|
412
|
+
"--set-default",
|
|
413
|
+
metavar="REPO",
|
|
414
|
+
help="Set persistent default repository in local config",
|
|
415
|
+
)
|
|
416
|
+
parser.add_argument(
|
|
417
|
+
"--set-default-user",
|
|
418
|
+
metavar="USER",
|
|
419
|
+
help="Set persistent default GitHub user in local config",
|
|
420
|
+
)
|
|
421
|
+
parser.add_argument(
|
|
422
|
+
"--config",
|
|
423
|
+
action="store_true",
|
|
424
|
+
help="Display current CodeFetch configuration",
|
|
425
|
+
)
|
|
426
|
+
parser.add_argument(
|
|
427
|
+
"--reset-config",
|
|
428
|
+
action="store_true",
|
|
429
|
+
help="Reset configuration back to defaults",
|
|
430
|
+
)
|
|
431
|
+
parser.add_argument(
|
|
432
|
+
"-v", "--version",
|
|
433
|
+
action="version",
|
|
434
|
+
version="codefetch 1.2.0",
|
|
435
|
+
)
|
|
436
|
+
return parser
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def resolve_invocation(positional_args, repo_flag, user_flag, branch_flag,
|
|
440
|
+
default_owner, default_repo, default_branch, known_repos=None):
|
|
441
|
+
"""
|
|
442
|
+
Resolve arguments into (owner, repo, branch, filename).
|
|
443
|
+
"""
|
|
444
|
+
owner = user_flag or default_owner
|
|
445
|
+
repo = default_repo
|
|
446
|
+
branch = branch_flag or default_branch
|
|
447
|
+
filename = None
|
|
448
|
+
|
|
449
|
+
if repo_flag:
|
|
450
|
+
flag_owner, flag_repo = parse_repo_identifier(repo_flag, default_owner=owner)
|
|
451
|
+
owner = flag_owner
|
|
452
|
+
repo = flag_repo
|
|
453
|
+
if positional_args:
|
|
454
|
+
filename = positional_args[0]
|
|
455
|
+
return owner, repo, branch, filename
|
|
456
|
+
|
|
457
|
+
if not positional_args:
|
|
458
|
+
return owner, repo, branch, None
|
|
459
|
+
|
|
460
|
+
if len(positional_args) >= 2:
|
|
461
|
+
first, second = positional_args[0], positional_args[1]
|
|
462
|
+
p_owner, p_repo = parse_repo_identifier(first, default_owner=owner)
|
|
463
|
+
return p_owner, p_repo, branch, second
|
|
464
|
+
|
|
465
|
+
target = positional_args[0].strip()
|
|
466
|
+
|
|
467
|
+
# 1. Full GitHub web URL
|
|
468
|
+
m = re.match(r"https?://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.+)$", target, re.IGNORECASE)
|
|
469
|
+
if m:
|
|
470
|
+
return m.group(1), m.group(2).removesuffix(".git"), m.group(3), m.group(4)
|
|
471
|
+
|
|
472
|
+
# 2. Raw GitHub URL
|
|
473
|
+
m = re.match(r"https?://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/(.+)$", target, re.IGNORECASE)
|
|
474
|
+
if m:
|
|
475
|
+
return m.group(1), m.group(2), m.group(3), m.group(4)
|
|
476
|
+
|
|
477
|
+
# 3. Colon syntax: user/repo:filename or repo:filename
|
|
478
|
+
if ":" in target:
|
|
479
|
+
repo_part, file_part = target.split(":", 1)
|
|
480
|
+
p_owner, p_repo = parse_repo_identifier(repo_part, default_owner=owner)
|
|
481
|
+
return p_owner, p_repo, branch, file_part
|
|
482
|
+
|
|
483
|
+
# 4. Multi-part slash syntax: user/repo/filename
|
|
484
|
+
parts = target.split("/")
|
|
485
|
+
if len(parts) >= 3:
|
|
486
|
+
return parts[0], parts[1], branch, "/".join(parts[2:])
|
|
487
|
+
|
|
488
|
+
if len(parts) == 2:
|
|
489
|
+
return owner, parts[0], branch, parts[1]
|
|
490
|
+
|
|
491
|
+
# Single filename, e.g. 8.1.py
|
|
492
|
+
return owner, repo, branch, target
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def main():
|
|
496
|
+
parser = build_parser()
|
|
497
|
+
args = parser.parse_args()
|
|
498
|
+
|
|
499
|
+
config = load_config()
|
|
500
|
+
default_owner = config.get("owner", DEFAULT_OWNER)
|
|
501
|
+
default_repo = config.get("repo", DEFAULT_REPO)
|
|
502
|
+
default_branch = config.get("branch", DEFAULT_BRANCH)
|
|
503
|
+
|
|
504
|
+
# Handle --reset-config
|
|
505
|
+
if args.reset_config:
|
|
506
|
+
if CONFIG_FILE.exists():
|
|
507
|
+
try:
|
|
508
|
+
CONFIG_FILE.unlink()
|
|
509
|
+
except Exception as e:
|
|
510
|
+
print(f"Error removing config file: {e}")
|
|
511
|
+
return
|
|
512
|
+
print(f"Configuration reset to defaults (User: {DEFAULT_OWNER}, Repo: {DEFAULT_REPO}).")
|
|
513
|
+
return
|
|
514
|
+
|
|
515
|
+
# Handle --set-default-user
|
|
516
|
+
if args.set_default_user:
|
|
517
|
+
save_config(owner=args.set_default_user)
|
|
518
|
+
print(f"Default user updated to: '{args.set_default_user}'")
|
|
519
|
+
print(f"Saved to: {CONFIG_FILE}")
|
|
520
|
+
return
|
|
521
|
+
|
|
522
|
+
# Handle --set-default
|
|
523
|
+
if args.set_default:
|
|
524
|
+
owner_to_save = args.user if args.user else None
|
|
525
|
+
save_config(owner=owner_to_save, repo=args.set_default)
|
|
526
|
+
print(f"Default repository updated to: '{args.set_default}'")
|
|
527
|
+
if owner_to_save:
|
|
528
|
+
print(f"Default user updated to: '{owner_to_save}'")
|
|
529
|
+
print(f"Saved to: {CONFIG_FILE}")
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
# Handle --config
|
|
533
|
+
if args.config:
|
|
534
|
+
print("CodeFetch Configuration:")
|
|
535
|
+
print(f" Default User: {default_owner}")
|
|
536
|
+
print(f" Default Repository: {default_repo}")
|
|
537
|
+
print(f" Default Branch: {default_branch}")
|
|
538
|
+
print(f" Config File: {CONFIG_FILE}")
|
|
539
|
+
return
|
|
540
|
+
|
|
541
|
+
owner = args.user or default_owner
|
|
542
|
+
branch = args.branch or default_branch
|
|
543
|
+
|
|
544
|
+
# Handle --repos (list repositories)
|
|
545
|
+
if args.repos:
|
|
546
|
+
target_owner = owner
|
|
547
|
+
if args.args:
|
|
548
|
+
target_owner = args.args[0]
|
|
549
|
+
list_repos(owner=target_owner, default_repo=default_repo)
|
|
550
|
+
return
|
|
551
|
+
|
|
552
|
+
# Handle --list
|
|
553
|
+
if args.list:
|
|
554
|
+
target_owner = owner
|
|
555
|
+
target_repo = default_repo
|
|
556
|
+
if args.repo:
|
|
557
|
+
target_owner, target_repo = parse_repo_identifier(args.repo, default_owner=owner)
|
|
558
|
+
elif args.args:
|
|
559
|
+
target_owner, target_repo = parse_repo_identifier(args.args[0], default_owner=owner)
|
|
560
|
+
list_files(owner=target_owner, repo=target_repo, branch=branch)
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
# Handle fetching or viewing file
|
|
564
|
+
if not args.args and not args.repo:
|
|
565
|
+
parser.print_help()
|
|
566
|
+
return
|
|
567
|
+
|
|
568
|
+
known_repos = [default_repo, "DS", "MPL", "Discord-Chatbot", "Codefetch"]
|
|
569
|
+
|
|
570
|
+
resolved_owner, resolved_repo, resolved_branch, resolved_filename = resolve_invocation(
|
|
571
|
+
args.args,
|
|
572
|
+
args.repo,
|
|
573
|
+
args.user,
|
|
574
|
+
args.branch,
|
|
575
|
+
default_owner,
|
|
576
|
+
default_repo,
|
|
577
|
+
default_branch,
|
|
578
|
+
known_repos=known_repos,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if not resolved_filename:
|
|
582
|
+
print("Error: No filename specified.")
|
|
583
|
+
parser.print_help()
|
|
584
|
+
return
|
|
585
|
+
|
|
586
|
+
fetch_file(
|
|
587
|
+
filename=resolved_filename,
|
|
588
|
+
owner=resolved_owner,
|
|
589
|
+
repo=resolved_repo,
|
|
590
|
+
branch=resolved_branch,
|
|
591
|
+
show=args.show,
|
|
592
|
+
output=args.output,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
if __name__ == "__main__":
|
|
597
|
+
main()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codefetch-cli
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Quickly fetch code files from GitHub repositories with simple commands
|
|
5
|
+
Home-page: https://github.com/InzamamulQureshi/Codefetch
|
|
6
|
+
Author: Inzamamul Qureshi
|
|
7
|
+
Author-email: Inzamamul Qureshi <inzamamulqureshi@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/InzamamulQureshi/Codefetch
|
|
10
|
+
Project-URL: Repository, https://github.com/InzamamulQureshi/Codefetch
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
|
|
23
|
+
# CodeFetch
|
|
24
|
+
|
|
25
|
+
Quickly fetch code files from GitHub repositories with simple commands.
|
|
26
|
+
|
|
27
|
+
CodeFetch is a lightweight command-line utility designed to quickly inspect, preview, and download individual source code files directly from any GitHub repository without needing to clone full repositories.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- **Any User & Repository**: Fetch files from any public GitHub repository using intuitive syntax (`codefetch <user>/<repo> <filename>`).
|
|
34
|
+
- **Terminal Preview**: Inspect code directly in your console with `--show` without saving it to disk.
|
|
35
|
+
- **Automatic Path & Case Resolution**: Automatically resolves case mismatches (e.g., `exp1.c` -> `EXP1.c`) and finds files nested inside subdirectories.
|
|
36
|
+
- **Repository Discovery**: List all public repositories for any GitHub user with `--repos`.
|
|
37
|
+
- **Tree Exploration**: List all supported code files within a repository with `--list`.
|
|
38
|
+
- **Persistent Defaults**: Configure your preferred default repository and user with `--set-default` and `--set-default-user`.
|
|
39
|
+
- **No Dependencies**: Built using the Python standard library with zero third-party runtime dependencies.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
### From PyPI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install codefetch-cli
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### From GitHub
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install git+https://github.com/InzamamulQureshi/Codefetch.git
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### From Local Source
|
|
58
|
+
|
|
59
|
+
Clone the repository and install locally:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/InzamamulQureshi/Codefetch.git
|
|
63
|
+
cd Codefetch
|
|
64
|
+
pip install .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quick Start & Usage
|
|
70
|
+
|
|
71
|
+
### 1. Download Files
|
|
72
|
+
|
|
73
|
+
Fetch a file from any GitHub repository:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Fetch from a specific user and repository
|
|
77
|
+
codefetch InzamamulQureshi/Codefetch setup.py
|
|
78
|
+
|
|
79
|
+
# Fetch using explicit flags
|
|
80
|
+
codefetch -u InzamamulQureshi -r Codefetch setup.py
|
|
81
|
+
|
|
82
|
+
# Fetch from a repository under the default user
|
|
83
|
+
codefetch DS EXP1.c
|
|
84
|
+
codefetch -r DS EXP1.c
|
|
85
|
+
|
|
86
|
+
# Shorthand path syntax
|
|
87
|
+
codefetch DS/EXP1.c
|
|
88
|
+
|
|
89
|
+
# Fetch from the default repository
|
|
90
|
+
codefetch 8.1.py
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Save to a custom filename or directory using `-o` / `--output`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
codefetch -r DS EXP1.c -o my_experiment.c
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### 2. Preview in Terminal (Without Downloading)
|
|
100
|
+
|
|
101
|
+
Display the contents of a file directly in stdout:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Preview file from default repository
|
|
105
|
+
codefetch --show 8.1.py
|
|
106
|
+
|
|
107
|
+
# Preview file from another repository
|
|
108
|
+
codefetch --show DS/EXP1.c
|
|
109
|
+
|
|
110
|
+
# Preview file from any user repository
|
|
111
|
+
codefetch --show InzamamulQureshi/Codefetch setup.py
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 3. Explore Repositories and Files
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# List all public repositories for default user
|
|
118
|
+
codefetch --repos
|
|
119
|
+
|
|
120
|
+
# List all public repositories for a specific user
|
|
121
|
+
codefetch --repos -u InzamamulQureshi
|
|
122
|
+
codefetch --repos torvalds
|
|
123
|
+
|
|
124
|
+
# List code files in the default repository
|
|
125
|
+
codefetch --list
|
|
126
|
+
|
|
127
|
+
# List code files in another repository
|
|
128
|
+
codefetch -r DS --list
|
|
129
|
+
codefetch --list InzamamulQureshi/Codefetch
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 4. Configuration and Defaults
|
|
133
|
+
|
|
134
|
+
Set persistent default preferences stored in `~/.codefetch.json`:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Set default repository
|
|
138
|
+
codefetch --set-default DS
|
|
139
|
+
|
|
140
|
+
# Set default user
|
|
141
|
+
codefetch --set-default-user InzamamulQureshi
|
|
142
|
+
|
|
143
|
+
# View current configuration
|
|
144
|
+
codefetch --config
|
|
145
|
+
|
|
146
|
+
# Reset configuration back to factory defaults
|
|
147
|
+
codefetch --reset-config
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Command Reference
|
|
153
|
+
|
|
154
|
+
| Flag | Description | Example |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `-r`, `--repo` | Target repository name or URL | `codefetch -r DS EXP1.c` |
|
|
157
|
+
| `-u`, `--user` | Target GitHub user or organization | `codefetch -u octocat -r Spoon-Knife --list` |
|
|
158
|
+
| `-b`, `--branch` | Branch name (default: `main`) | `codefetch -b main DS EXP1.c` |
|
|
159
|
+
| `-s`, `--show` | Print file contents to terminal | `codefetch --show 8.1.py` |
|
|
160
|
+
| `-l`, `--list` | List available code files | `codefetch --list DS` |
|
|
161
|
+
| `-R`, `--repos` | List public repositories for user | `codefetch --repos -u InzamamulQureshi` |
|
|
162
|
+
| `-o`, `--output` | Specify destination output filename | `codefetch -r DS EXP1.c -o local.c` |
|
|
163
|
+
| `--set-default` | Set persistent default repository | `codefetch --set-default DS` |
|
|
164
|
+
| `--set-default-user` | Set persistent default user | `codefetch --set-default-user InzamamulQureshi` |
|
|
165
|
+
| `--config` | Display active configuration | `codefetch --config` |
|
|
166
|
+
| `--reset-config` | Reset configuration to defaults | `codefetch --reset-config` |
|
|
167
|
+
| `-v`, `--version` | Display version information | `codefetch --version` |
|
|
168
|
+
| `-h`, `--help` | Display help and usage message | `codefetch --help` |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Supported File Extensions
|
|
173
|
+
|
|
174
|
+
CodeFetch recognizes and lists common source and document files:
|
|
175
|
+
|
|
176
|
+
`.py`, `.c`, `.cpp`, `.h`, `.hpp`, `.java`, `.js`, `.ts`, `.sql`, `.html`, `.css`, `.txt`, `.asm`, `.s`, `.hex`, `.json`, `.sh`, `.rs`, `.go`, `.cs`, `.ipynb`, `.md`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
codefetch/__init__.py
|
|
6
|
+
codefetch/cli.py
|
|
7
|
+
codefetch_cli.egg-info/PKG-INFO
|
|
8
|
+
codefetch_cli.egg-info/SOURCES.txt
|
|
9
|
+
codefetch_cli.egg-info/dependency_links.txt
|
|
10
|
+
codefetch_cli.egg-info/entry_points.txt
|
|
11
|
+
codefetch_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codefetch
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codefetch-cli"
|
|
7
|
+
version = "1.2.0"
|
|
8
|
+
description = "Quickly fetch code files from GitHub repositories with simple commands"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Inzamamul Qureshi", email = "inzamamulqureshi@gmail.com" }
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">=3.8"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/InzamamulQureshi/Codefetch"
|
|
25
|
+
Repository = "https://github.com/InzamamulQureshi/Codefetch"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
codefetch = "codefetch.cli:main"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
this_directory = Path(__file__).parent
|
|
5
|
+
readme_path = this_directory / "README.md"
|
|
6
|
+
long_description = readme_path.read_text(encoding="utf-8") if readme_path.exists() else ""
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="codefetch-cli",
|
|
10
|
+
version="1.2.0",
|
|
11
|
+
description="Quickly fetch code files from GitHub repositories with simple commands",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
author="Inzamamul Qureshi",
|
|
15
|
+
author_email="inzamamulqureshi@gmail.com",
|
|
16
|
+
url="https://github.com/InzamamulQureshi/Codefetch",
|
|
17
|
+
packages=find_packages(),
|
|
18
|
+
entry_points={
|
|
19
|
+
"console_scripts": [
|
|
20
|
+
"codefetch = codefetch.cli:main",
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
python_requires=">=3.8",
|
|
24
|
+
classifiers=[
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Topic :: Utilities",
|
|
29
|
+
],
|
|
30
|
+
)
|