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.
@@ -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,5 @@
1
+ """
2
+ CodeFetch - CLI tool to fetch code files from GitHub repositories.
3
+ """
4
+
5
+ __version__ = "1.2.0"
@@ -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,2 @@
1
+ [console_scripts]
2
+ codefetch = codefetch.cli:main
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ )