codefetch-cli 1.2.1__py3-none-any.whl → 1.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
codefetch/__init__.py CHANGED
@@ -1,5 +1,146 @@
1
1
  """
2
- CodeFetch - CLI tool to fetch code files from GitHub repositories.
2
+ CodeFetch - Easily preview and download code files from GitHub repositories.
3
+
4
+ Usage in Python:
5
+ import codefetch as cf
6
+
7
+ # 1. Fetch file content as string:
8
+ content = cf.get("torvalds/linux", "Makefile")
9
+ # or shorthand target string:
10
+ content = cf.get("torvalds/linux/Makefile")
11
+
12
+ # 2. Download file to local disk:
13
+ cf.download("torvalds/linux", "Makefile", output="linux_makefile.txt")
14
+
15
+ # 3. List available code files:
16
+ files = cf.list("torvalds/linux")
3
17
  """
4
18
 
5
- __version__ = "1.2.1"
19
+ __version__ = "1.2.3"
20
+
21
+
22
+ def get(target, filename=None, branch="main"):
23
+ """
24
+ Fetch and return the text content of a file from GitHub.
25
+
26
+ Args:
27
+ target: 'owner/repo' or full path 'owner/repo/path/to/file' or full GitHub URL.
28
+ filename: Optional path to file in repo if target is 'owner/repo'.
29
+ branch: Branch name (default: 'main', automatically falls back to 'master').
30
+
31
+ Returns:
32
+ str: File content.
33
+
34
+ Raises:
35
+ FileNotFoundError: If the file does not exist in the repository.
36
+ ValueError: If arguments cannot be resolved into a repository and filename.
37
+ """
38
+ import urllib.request
39
+ from .cli import (
40
+ fetch_raw_direct,
41
+ get_repo_tree_with_branch,
42
+ resolve_file_in_tree,
43
+ parse_repo_identifier,
44
+ resolve_invocation,
45
+ )
46
+
47
+ if filename:
48
+ owner, repo = parse_repo_identifier(target)
49
+ file_path = filename.strip("/\\")
50
+ else:
51
+ owner, repo, resolved_branch, file_path = resolve_invocation(
52
+ [target], None, None, branch, None, None, "main"
53
+ )
54
+ if resolved_branch:
55
+ branch = resolved_branch
56
+
57
+ if not owner or not repo or not file_path:
58
+ raise ValueError(f"Could not resolve owner, repo, and filename from target='{target}', filename='{filename}'")
59
+
60
+ # 1. Try fast direct raw fetch
61
+ found_branch, content = fetch_raw_direct(owner, repo, branch, file_path)
62
+ if content is not None:
63
+ return content
64
+
65
+ # 2. Try repository tree search
66
+ actual_branch, tree_data = get_repo_tree_with_branch(owner, repo, branch)
67
+ matched = resolve_file_in_tree(tree_data, file_path)
68
+ if matched:
69
+ url = f"https://raw.githubusercontent.com/{owner}/{repo}/{actual_branch}/{matched}"
70
+ req = urllib.request.Request(url, headers={"User-Agent": "codefetch"})
71
+ with urllib.request.urlopen(req, timeout=15) as resp:
72
+ return resp.read().decode("utf-8")
73
+
74
+ raise FileNotFoundError(f"File '{file_path}' not found in {owner}/{repo} ({actual_branch})")
75
+
76
+
77
+ def download(target, filename=None, output=None, branch="main"):
78
+ """
79
+ Download a file from GitHub and save it locally.
80
+
81
+ Args:
82
+ target: 'owner/repo' or full path 'owner/repo/path/to/file'.
83
+ filename: Optional path to file in repo if target is 'owner/repo'.
84
+ output: Destination filename/path on local disk (default: original filename).
85
+ branch: Branch name (default: 'main').
86
+
87
+ Returns:
88
+ str: Path to the downloaded file.
89
+ """
90
+ content = get(target, filename=filename, branch=branch)
91
+ dest_name = output
92
+ if not dest_name:
93
+ resolved_file = filename if filename else target.rsplit("/", 1)[-1]
94
+ dest_name = resolved_file.rsplit("/", 1)[-1]
95
+
96
+ with open(dest_name, "w", encoding="utf-8") as f:
97
+ f.write(content)
98
+
99
+ return dest_name
100
+
101
+
102
+ def list(target, branch="main"):
103
+ """
104
+ List supported code files in a repository.
105
+
106
+ Args:
107
+ target: 'owner/repo' or repository URL.
108
+ branch: Branch name (default: 'main').
109
+
110
+ Returns:
111
+ list[str]: Relative paths of code files in the repository.
112
+ """
113
+ from .cli import (
114
+ parse_repo_identifier,
115
+ get_repo_tree_with_branch,
116
+ CODE_EXTENSIONS,
117
+ NAMED_FILES,
118
+ )
119
+
120
+ owner, repo = parse_repo_identifier(target)
121
+ if not owner or not repo:
122
+ raise ValueError(f"Invalid repository identifier: '{target}'")
123
+
124
+ actual_branch, tree_data = get_repo_tree_with_branch(owner, repo, branch)
125
+ files = []
126
+ for item in tree_data.get("tree", []):
127
+ if item.get("type") != "blob":
128
+ continue
129
+ path = item.get("path", "")
130
+ base_name = path.rsplit("/", 1)[-1].lower()
131
+ suffix = ""
132
+ if "." in base_name:
133
+ suffix = "." + base_name.rsplit(".", 1)[-1]
134
+ if suffix in CODE_EXTENSIONS or base_name in NAMED_FILES:
135
+ files.append(path)
136
+
137
+ files.sort(key=str.lower)
138
+ return files
139
+
140
+
141
+ __all__ = [
142
+ "__version__",
143
+ "get",
144
+ "download",
145
+ "list",
146
+ ]
codefetch/cli.py CHANGED
@@ -12,12 +12,14 @@ import urllib.error
12
12
  import urllib.request
13
13
  from pathlib import Path
14
14
 
15
- DEFAULT_OWNER = "Reaver101"
16
- DEFAULT_REPO = "MPL"
15
+ DEFAULT_OWNER = None
16
+ DEFAULT_REPO = None
17
17
  DEFAULT_BRANCH = "main"
18
18
 
19
19
  CONFIG_FILE = Path.home() / ".codefetch.json"
20
20
 
21
+ __version__ = "1.2.3"
22
+
21
23
  # Supported code, data, and config extensions
22
24
  CODE_EXTENSIONS = {
23
25
  # Data & Tables
@@ -55,91 +57,82 @@ HELP_MESSAGE = """\
55
57
  CodeFetch - Download single code files from GitHub without cloning entire repos.
56
58
 
57
59
  USAGE:
58
- codefetch <filename> Download a file from default repo (MPL)
59
- codefetch <repo> <filename> Download from another repo (e.g. DS EXP1.c)
60
- codefetch <user>/<repo> <filename> Download from any GitHub user/repo
61
-
62
- QUICK EXAMPLES:
63
- Download a file:
64
- codefetch 8.1.py Save 8.1.py to current directory
65
- codefetch data.csv Save data.csv to current directory
66
- codefetch DS EXP1.c Save EXP1.c from repo 'DS'
67
- codefetch DS EXP1.c -o my_code.c Save with a custom output filename
68
- codefetch InzamamulQureshi/Codefetch setup.py
69
-
70
- View code in terminal (without downloading):
71
- codefetch -s 8.1.py Display 8.1.py in terminal
72
- codefetch -s DS EXP1.c Display EXP1.c in terminal
73
-
74
- Browse files and repositories:
75
- codefetch -l List all code files in default repo (MPL)
76
- codefetch -l DS List all code files in repo 'DS'
77
- codefetch -l user/repo List code files in any user repository
78
- codefetch -R List all repositories of default user (Reaver101)
79
- codefetch -R -u <username> List all repositories of any GitHub user
80
-
81
- SETTINGS & DEFAULTS:
82
- codefetch --set-default <repo> Set permanent default repo (e.g. DS)
83
- codefetch --set-default-user <user> Set permanent default GitHub user
84
- codefetch --config View current active settings
85
- codefetch --reset-config Reset all settings back to default
86
-
87
- OPTIONS:
88
- -s, --show View file contents in terminal instead of saving
89
- -o, --output <file> Custom filename or path to save the downloaded file
90
- -l, --list List available code files in the repository
91
- -R, --repos List all public repositories for the user
92
- -r, --repo <name> Specify repository name or URL
93
- -u, --user <name> Specify GitHub username (default: Reaver101)
94
- -b, --branch <name> Specify branch name (default: main)
95
- -v, --version Show version number
96
- -h, --help Show this help guide
60
+ codefetch <owner>/<repo> <filename> Download a file from any GitHub repository
61
+ codefetch <filename> Download a file (uses saved default repo)
62
+ codefetch -s <owner>/<repo> <filename> Preview file in terminal without saving
63
+ codefetch -l <owner>/<repo> List all code files in a repository
64
+ codefetch -R <username> List public repositories for a user
65
+
66
+ POPULAR EXAMPLES:
67
+ # Download from any repository:
68
+ codefetch owner/repo main.py
69
+ codefetch torvalds/linux Makefile
70
+
71
+ # Preview code directly in terminal:
72
+ codefetch -s owner/repo app.js
73
+ codefetch -s torvalds/linux Makefile
74
+
75
+ # Save with a custom name or path:
76
+ codefetch owner/repo data.csv -o my_data.csv
77
+
78
+ # Explore repositories and files:
79
+ codefetch -l torvalds/linux # list files in a repo
80
+ codefetch -R torvalds # list repos belonging to user
81
+
82
+ SETTING A DEFAULT REPOSITORY (OPTIONAL):
83
+ If you frequently work with the same repo, you can set it as default:
84
+ codefetch --set-default owner/repo # sets default repo
85
+ codefetch script.py # now you don't need to type owner/repo!
86
+ codefetch --config # view active configuration
87
+ codefetch --reset-config # reset all defaults
88
+
89
+ FLAGS & OPTIONS:
90
+ -s, --show Print file content to terminal instead of downloading
91
+ -o, --output <file> Specify output filename or path
92
+ -l, --list List available code files in the repository
93
+ -R, --repos List public repositories for a GitHub user
94
+ -r, --repo <repo> Specify repository name or URL
95
+ -u, --user <user> Specify GitHub username / organization
96
+ -b, --branch <branch> Specify branch name (default: main)
97
+ --set-default <repo> Save default repository (e.g. owner/repo)
98
+ --set-default-user <user> Save default user/organization
99
+ --config Show current saved configuration
100
+ --reset-config Reset configuration back to defaults
101
+ -v, --version Show version number
102
+ -h, --help Show this help message
97
103
  """
98
104
 
99
105
 
100
106
  def load_config():
101
- """Load configuration from environment variables and local config file."""
102
- config = {
103
- "owner": os.environ.get("CODEFETCH_USER", DEFAULT_OWNER),
104
- "repo": os.environ.get("CODEFETCH_REPO", DEFAULT_REPO),
105
- "branch": os.environ.get("CODEFETCH_BRANCH", DEFAULT_BRANCH),
106
- }
107
+ """Load user configuration from ~/.codefetch.json."""
107
108
  if CONFIG_FILE.exists():
108
109
  try:
109
110
  with open(CONFIG_FILE, "r", encoding="utf-8") as f:
110
- saved = json.load(f)
111
- if isinstance(saved, dict):
112
- if saved.get("owner"):
113
- config["owner"] = saved["owner"]
114
- if saved.get("repo"):
115
- config["repo"] = saved["repo"]
116
- if saved.get("branch"):
117
- config["branch"] = saved["branch"]
111
+ return json.load(f)
118
112
  except Exception:
119
- pass
120
- return config
113
+ return {}
114
+ return {}
121
115
 
122
116
 
123
117
  def save_config(owner=None, repo=None, branch=None):
124
- """Save configuration to ~/.codefetch.json."""
125
- config = load_config()
126
- if owner:
127
- config["owner"] = owner
128
- if repo:
129
- config["repo"] = repo
130
- if branch:
131
- config["branch"] = branch
118
+ """Save user configuration to ~/.codefetch.json."""
119
+ cfg = load_config()
120
+ if owner is not None:
121
+ cfg["owner"] = owner
122
+ if repo is not None:
123
+ cfg["repo"] = repo
124
+ if branch is not None:
125
+ cfg["branch"] = branch
126
+
132
127
  try:
133
128
  with open(CONFIG_FILE, "w", encoding="utf-8") as f:
134
- json.dump(config, f, indent=2)
135
- return True
129
+ json.dump(cfg, f, indent=2)
136
130
  except Exception as e:
137
- print(f"Error saving configuration: {e}")
138
- return False
131
+ print(f"Warning: Could not save configuration: {e}")
139
132
 
140
133
 
141
134
  def get_headers():
142
- """Get HTTP headers for GitHub requests."""
135
+ """Build request headers, attaching GITHUB_TOKEN if present in environment."""
143
136
  headers = {
144
137
  "User-Agent": "codefetch",
145
138
  "Accept": "application/vnd.github+json",
@@ -151,13 +144,13 @@ def get_headers():
151
144
 
152
145
 
153
146
  def get_json(url):
154
- """Fetch and decode JSON from a URL."""
155
- request = urllib.request.Request(url, headers=get_headers())
156
- with urllib.request.urlopen(request, timeout=15) as response:
157
- return json.loads(response.read().decode("utf-8"))
147
+ """Fetch JSON from GitHub API with rate limit handling."""
148
+ req = urllib.request.Request(url, headers=get_headers())
149
+ with urllib.request.urlopen(req, timeout=15) as resp:
150
+ return json.loads(resp.read().decode("utf-8"))
158
151
 
159
152
 
160
- def parse_repo_identifier(repo_str, default_owner=DEFAULT_OWNER):
153
+ def parse_repo_identifier(repo_str, default_owner=None):
161
154
  """Extract (owner, repo) from a repository flag, name, or URL."""
162
155
  if not repo_str:
163
156
  return default_owner, None
@@ -175,51 +168,112 @@ def parse_repo_identifier(repo_str, default_owner=DEFAULT_OWNER):
175
168
  return default_owner, repo_str.removesuffix(".git")
176
169
 
177
170
 
178
- def get_repo_tree(owner, repo, branch="main"):
179
- """Fetch the Git tree for a repository, with fallback to master if main is absent."""
180
- url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1"
171
+ def fetch_raw_direct(owner, repo, branch, clean_filename):
172
+ """
173
+ Direct raw fetch from raw.githubusercontent.com.
174
+ Tries requested branch first. If branch is 'main', also tries 'master',
175
+ and vice versa.
176
+ Returns (branch_used, content_str) or (None, None).
177
+ """
178
+ branches_to_try = [branch]
179
+ if branch == "main":
180
+ branches_to_try.append("master")
181
+ elif branch == "master":
182
+ branches_to_try.append("main")
183
+
184
+ for b in branches_to_try:
185
+ url = f"https://raw.githubusercontent.com/{owner}/{repo}/{b}/{clean_filename}"
186
+ try:
187
+ req = urllib.request.Request(url, headers={"User-Agent": "codefetch"})
188
+ with urllib.request.urlopen(req, timeout=15) as resp:
189
+ return b, resp.read().decode("utf-8")
190
+ except urllib.error.HTTPError as e:
191
+ if e.code == 404:
192
+ continue
193
+ raise
194
+ return None, None
195
+
196
+
197
+ def get_repo_tree_with_branch(owner, repo, branch="main"):
198
+ """
199
+ Fetch the Git tree for a repository, returning (actual_branch, tree_data).
200
+ Tries branch first, then fallback to master/main, then checks repository info for default_branch.
201
+ """
202
+ branches_to_try = [branch]
203
+ if branch == "main":
204
+ branches_to_try.append("master")
205
+ elif branch == "master":
206
+ branches_to_try.append("main")
207
+
208
+ for b in branches_to_try:
209
+ url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{b}?recursive=1"
210
+ try:
211
+ return b, get_json(url)
212
+ except urllib.error.HTTPError as e:
213
+ if e.code == 404:
214
+ continue
215
+ raise
216
+
217
+ # Query repository info to discover actual default branch (e.g. develop, trunk, etc.)
181
218
  try:
182
- return get_json(url)
183
- except urllib.error.HTTPError as e:
184
- if e.code == 404 and branch == "main":
185
- url_master = f"https://api.github.com/repos/{owner}/{repo}/git/trees/master?recursive=1"
186
- try:
187
- return get_json(url_master)
188
- except Exception:
189
- pass
190
- raise
219
+ info_url = f"https://api.github.com/repos/{owner}/{repo}"
220
+ info = get_json(info_url)
221
+ def_branch = info.get("default_branch")
222
+ if def_branch and def_branch not in branches_to_try:
223
+ url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{def_branch}?recursive=1"
224
+ return def_branch, get_json(url)
225
+ except Exception:
226
+ pass
227
+
228
+ raise urllib.error.HTTPError(
229
+ f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}",
230
+ 404,
231
+ "Branch tree not found",
232
+ {},
233
+ None,
234
+ )
235
+
236
+
237
+ def get_repo_tree(owner, repo, branch="main"):
238
+ """Fetch the Git tree for a repository (backwards compatible helper)."""
239
+ _, tree_data = get_repo_tree_with_branch(owner, repo, branch)
240
+ return tree_data
191
241
 
192
242
 
193
243
  def get_user_repos(owner):
194
- """Fetch public repositories for a given GitHub owner."""
244
+ """Fetch public repositories list for a user."""
195
245
  url = f"https://api.github.com/users/{owner}/repos?per_page=100&sort=updated"
196
- try:
197
- return get_json(url)
198
- except Exception:
199
- return []
246
+ return get_json(url)
200
247
 
201
248
 
202
- def list_repos(owner=DEFAULT_OWNER, default_repo=DEFAULT_REPO):
203
- """List all public repositories for the owner."""
249
+ def list_repos(owner=None, default_repo=None):
250
+ """List all public repositories for a GitHub user."""
251
+ if not owner:
252
+ print("Error: No GitHub username specified.")
253
+ print("Usage: codefetch --repos <username>")
254
+ return
255
+
204
256
  try:
205
257
  repos = get_user_repos(owner)
206
258
  if not repos:
207
259
  print(f"No public repositories found for user '{owner}'.")
208
260
  return
209
261
 
210
- print(f"Repositories for '{owner}':\n")
211
- for r in repos:
262
+ print(f"Public repositories for '{owner}':\n")
263
+ for i, r in enumerate(repos, 1):
212
264
  name = r.get("name", "")
213
- desc = r.get("description") or ""
214
- is_def = " (default)" if name.lower() == default_repo.lower() else ""
265
+ desc = r.get("description") or "No description"
266
+ is_def = " (default)" if default_repo and name.lower() == default_repo.lower() else ""
215
267
  marker = "*" if is_def else "-"
216
- desc_text = f" - {desc}" if desc else ""
217
- print(f" {marker} {name}{is_def}{desc_text}")
268
+ print(f" {marker} {name:<22}{is_def}")
269
+ if desc and desc != "No description":
270
+ print(f" {desc}")
218
271
 
272
+ print(f"\nTotal: {len(repos)} repositories")
219
273
  print("\nCommands:")
220
- print(f" codefetch -u {owner} -r <repo> --list List files in a repository")
221
- print(f" codefetch -u {owner} -r <repo> <filename> Download a file from a repository")
222
- print(f" codefetch --set-default <repo> Set default repository")
274
+ print(f" codefetch -l {owner}/<repo> List files in a repository")
275
+ print(f" codefetch {owner}/<repo> <filename> Download a file from a repository")
276
+ print(f" codefetch --set-default {owner}/<repo> Set default repository")
223
277
 
224
278
  except urllib.error.HTTPError as e:
225
279
  print(f"Could not list repositories: HTTP {e.code}")
@@ -231,8 +285,15 @@ def list_repos(owner=DEFAULT_OWNER, default_repo=DEFAULT_REPO):
231
285
 
232
286
  def list_files(owner, repo, branch="main"):
233
287
  """List code files in a repository."""
288
+ if not owner or not repo:
289
+ print("Error: No repository specified to list.\n")
290
+ print("Usage:")
291
+ print(" codefetch -l <owner>/<repo>")
292
+ print(" codefetch -u <owner> -r <repo> -l")
293
+ return
294
+
234
295
  try:
235
- data = get_repo_tree(owner, repo, branch)
296
+ actual_branch, data = get_repo_tree_with_branch(owner, repo, branch)
236
297
  if data.get("truncated"):
237
298
  print("Warning: GitHub returned a truncated file list.")
238
299
 
@@ -253,10 +314,10 @@ def list_files(owner, repo, branch="main"):
253
314
  files.sort(key=str.lower)
254
315
 
255
316
  if not files:
256
- print(f"No supported code files found in {owner}/{repo}.")
317
+ print(f"No supported code files found in {owner}/{repo} ({actual_branch}).")
257
318
  return
258
319
 
259
- print(f"Available files in {owner}/{repo} ({branch}):\n")
320
+ print(f"Available files in {owner}/{repo} ({actual_branch}):\n")
260
321
  for i, path in enumerate(files, 1):
261
322
  print(f"{i:>3}. {path}")
262
323
 
@@ -284,7 +345,7 @@ def resolve_file_in_tree(tree_data, target_filename):
284
345
  Search tree for target_filename:
285
346
  1. Exact match (case-insensitive) on full path
286
347
  2. Exact match (case-insensitive) on basename
287
- 3. Match without extension (e.g. 'exp1' -> 'EXP1.c')
348
+ 3. Match without extension (e.g. 'main' -> 'main.py')
288
349
  """
289
350
  blobs = [item["path"] for item in tree_data.get("tree", []) if item.get("type") == "blob"]
290
351
  target_clean = target_filename.strip("/\\").lower()
@@ -318,11 +379,13 @@ def resolve_file_in_tree(tree_data, target_filename):
318
379
 
319
380
  def find_file_in_other_repos(owner, current_repo, target_filename):
320
381
  """Search other public repositories of the user for the target file."""
382
+ if not owner:
383
+ return None, None
321
384
  try:
322
385
  repos_data = get_user_repos(owner)
323
386
  for r in repos_data:
324
387
  r_name = r.get("name")
325
- if not r_name or r_name.lower() == current_repo.lower():
388
+ if not r_name or (current_repo and r_name.lower() == current_repo.lower()):
326
389
  continue
327
390
  default_branch = r.get("default_branch", "main")
328
391
  try:
@@ -337,9 +400,19 @@ def find_file_in_other_repos(owner, current_repo, target_filename):
337
400
  return None, None
338
401
 
339
402
 
340
- def fetch_file(filename, owner=DEFAULT_OWNER, repo=DEFAULT_REPO, branch=DEFAULT_BRANCH,
403
+ def fetch_file(filename, owner=None, repo=None, branch=DEFAULT_BRANCH,
341
404
  show=False, output=None):
342
405
  """Fetch or download a file from GitHub."""
406
+ if not owner or not repo:
407
+ print("Error: No GitHub repository specified.\n")
408
+ print("Usage:")
409
+ print(" codefetch <owner>/<repo> <filename>")
410
+ print(" codefetch -u <owner> -r <repo> <filename>\n")
411
+ print("To set a default repository:")
412
+ print(" codefetch --set-default <owner>/<repo>\n")
413
+ print("Run 'codefetch --help' for more options.")
414
+ return
415
+
343
416
  clean_filename = filename.strip("/\\")
344
417
 
345
418
  def download_url(url):
@@ -347,50 +420,64 @@ def fetch_file(filename, owner=DEFAULT_OWNER, repo=DEFAULT_REPO, branch=DEFAULT_
347
420
  with urllib.request.urlopen(req, timeout=15) as resp:
348
421
  return resp.read().decode("utf-8")
349
422
 
350
- # 1. Direct raw fetch first (fastest, preserves GitHub API rate limit)
351
- raw_base = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}"
352
- url = f"{raw_base}/{clean_filename}"
353
-
354
423
  content = None
355
424
  resolved_path = clean_filename
425
+ actual_branch = branch
356
426
 
427
+ # 1. Direct raw fetch first (fastest, preserves GitHub API rate limit)
357
428
  try:
358
- content = download_url(url)
429
+ found_branch, raw_content = fetch_raw_direct(owner, repo, branch, clean_filename)
430
+ if raw_content is not None:
431
+ actual_branch = found_branch
432
+ content = raw_content
433
+ except UnicodeDecodeError:
434
+ print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
435
+ return
359
436
  except urllib.error.HTTPError as e:
360
- if e.code == 404:
361
- # 2. Direct fetch 404ed. Try smart resolution with repo tree
362
- try:
363
- tree_data = get_repo_tree(owner, repo, branch)
364
- matched = resolve_file_in_tree(tree_data, clean_filename)
365
- if matched:
366
- resolved_path = matched
367
- content = download_url(f"{raw_base}/{resolved_path}")
368
- if resolved_path != clean_filename:
369
- print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
370
- else:
371
- # Check other public repositories for the same user
372
- other_repo, other_match = find_file_in_other_repos(owner, repo, clean_filename)
373
- if other_repo:
374
- print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
375
- print(f"\n[Tip] Found '{other_match}' in repository '{other_repo}'!")
376
- print(f"To fetch it, run:\n codefetch -u {owner} -r {other_repo} {other_match}")
377
- return
378
- else:
379
- print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
380
- print(f"Run 'codefetch -u {owner} -r {repo} --list' to see available files.")
381
- return
382
- except Exception:
383
- print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
384
- return
385
- else:
386
- print(f"Download failed: HTTP {e.code}")
387
- return
437
+ print(f"Download failed: HTTP {e.code}")
438
+ return
388
439
  except urllib.error.URLError:
389
440
  print("Could not connect to GitHub.")
390
441
  return
391
- except UnicodeDecodeError:
392
- print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
393
- return
442
+
443
+ # 2. If direct fetch didn't find the file (404), fall back to smart repository tree search
444
+ if content is None:
445
+ try:
446
+ actual_branch, tree_data = get_repo_tree_with_branch(owner, repo, branch)
447
+ matched = resolve_file_in_tree(tree_data, clean_filename)
448
+ if matched:
449
+ resolved_path = matched
450
+ raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{actual_branch}/{resolved_path}"
451
+ content = download_url(raw_url)
452
+ if resolved_path != clean_filename:
453
+ print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
454
+ else:
455
+ # Check other public repositories for the same user
456
+ other_repo, other_match = find_file_in_other_repos(owner, repo, clean_filename)
457
+ if other_repo:
458
+ print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
459
+ print(f"\n[Tip] Found '{other_match}' in repository '{other_repo}'!")
460
+ print(f"To fetch it, run:\n codefetch {owner}/{other_repo} {other_match}")
461
+ return
462
+ else:
463
+ print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
464
+ print(f"Run 'codefetch -l {owner}/{repo}' to see available files.")
465
+ return
466
+ except urllib.error.HTTPError as e:
467
+ if e.code == 404:
468
+ print(f"Repository or branch not found: {owner}/{repo} ({branch})")
469
+ else:
470
+ print(f"Could not search repository: HTTP {e.code}")
471
+ return
472
+ except urllib.error.URLError:
473
+ print("Could not connect to GitHub.")
474
+ return
475
+ except UnicodeDecodeError:
476
+ print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
477
+ return
478
+ except Exception:
479
+ print(f"File not found: '{clean_filename}' in {owner}/{repo}.")
480
+ return
394
481
 
395
482
  if content is None:
396
483
  return
@@ -402,7 +489,7 @@ def fetch_file(filename, owner=DEFAULT_OWNER, repo=DEFAULT_REPO, branch=DEFAULT_
402
489
  try:
403
490
  with open(out_name, "w", encoding="utf-8") as f:
404
491
  f.write(content)
405
- print(f"Downloaded: {out_name} (from {owner}/{repo})")
492
+ print(f"Downloaded: {out_name} (from {owner}/{repo}@{actual_branch})")
406
493
  except Exception as e:
407
494
  print(f"Error saving file '{out_name}': {e}")
408
495
 
@@ -419,81 +506,32 @@ class CodefetchParser(argparse.ArgumentParser):
419
506
 
420
507
 
421
508
  def build_parser():
422
- """Build argument parser with full CLI options."""
423
509
  parser = CodefetchParser(
424
510
  prog="codefetch",
425
- add_help=True,
426
- )
427
- parser.add_argument(
428
- "args",
429
- nargs="*",
430
- help="Target specification: [user/repo] [filename] or [filename]",
431
- )
432
- parser.add_argument(
433
- "-r", "--repo",
434
- default=None,
435
- help="Repository name or URL (e.g. DS, Codefetch, or user/repo)",
436
- )
437
- parser.add_argument(
438
- "-u", "--user",
439
- default=None,
440
- help=f"GitHub user/org (default: {DEFAULT_OWNER})",
441
- )
442
- parser.add_argument(
443
- "-b", "--branch",
444
- default=None,
445
- help=f"Branch name (default: {DEFAULT_BRANCH})",
446
- )
447
- parser.add_argument(
448
- "-l", "--list",
449
- action="store_true",
450
- help="List available code files in the repository",
451
- )
452
- parser.add_argument(
453
- "-R", "--repos",
454
- action="store_true",
455
- help="List all public repositories for the user",
456
- )
457
- parser.add_argument(
458
- "-s", "--show",
459
- action="store_true",
460
- help="Display file content in terminal without saving to disk",
461
- )
462
- parser.add_argument(
463
- "-o", "--output",
464
- default=None,
465
- help="Custom destination filename or output path",
466
- )
467
- parser.add_argument(
468
- "--set-default",
469
- metavar="REPO",
470
- help="Set persistent default repository in local config",
471
- )
472
- parser.add_argument(
473
- "--set-default-user",
474
- metavar="USER",
475
- help="Set persistent default GitHub user in local config",
476
- )
477
- parser.add_argument(
478
- "--config",
479
- action="store_true",
480
- help="Display current CodeFetch configuration",
481
- )
482
- parser.add_argument(
483
- "--reset-config",
484
- action="store_true",
485
- help="Reset configuration back to defaults",
486
- )
487
- parser.add_argument(
488
- "-v", "--version",
489
- action="version",
490
- version="codefetch 1.2.1",
511
+ description="Fetch code files from GitHub repositories.",
512
+ add_help=False,
491
513
  )
514
+
515
+ parser.add_argument("args", nargs="*", help="Repository and filename targets")
516
+ parser.add_argument("-s", "--show", action="store_true", help="Print file content to terminal")
517
+ parser.add_argument("-l", "--list", action="store_true", help="List code files in repository")
518
+ parser.add_argument("-R", "--repos", action="store_true", help="List public repositories for a user")
519
+ parser.add_argument("-r", "--repo", type=str, help="Repository name or URL")
520
+ parser.add_argument("-u", "--user", type=str, help="GitHub username")
521
+ parser.add_argument("-b", "--branch", type=str, help="Branch name (default: main)")
522
+ parser.add_argument("-o", "--output", type=str, help="Custom output filename or path")
523
+ parser.add_argument("--set-default", type=str, help="Save default repository (e.g. owner/repo)")
524
+ parser.add_argument("--set-default-user", type=str, help="Save default GitHub user")
525
+ parser.add_argument("--config", action="store_true", help="Display active configuration")
526
+ parser.add_argument("--reset-config", action="store_true", help="Reset configuration back to defaults")
527
+ parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
528
+ parser.add_argument("-h", "--help", action="help", help="Show help message")
529
+
492
530
  return parser
493
531
 
494
532
 
495
533
  def resolve_invocation(positional_args, repo_flag, user_flag, branch_flag,
496
- default_owner, default_repo, default_branch, known_repos=None):
534
+ default_owner, default_repo, default_branch):
497
535
  """
498
536
  Resolve arguments into (owner, repo, branch, filename).
499
537
  """
@@ -530,7 +568,7 @@ def resolve_invocation(positional_args, repo_flag, user_flag, branch_flag,
530
568
  if m:
531
569
  return m.group(1), m.group(2), m.group(3), m.group(4)
532
570
 
533
- # 3. Colon syntax: user/repo:filename or repo:filename
571
+ # 3. Colon syntax: user/repo:filename
534
572
  if ":" in target:
535
573
  repo_part, file_part = target.split(":", 1)
536
574
  p_owner, p_repo = parse_repo_identifier(repo_part, default_owner=owner)
@@ -542,9 +580,12 @@ def resolve_invocation(positional_args, repo_flag, user_flag, branch_flag,
542
580
  return parts[0], parts[1], branch, "/".join(parts[2:])
543
581
 
544
582
  if len(parts) == 2:
545
- return owner, parts[0], branch, parts[1]
583
+ if owner:
584
+ return owner, parts[0], branch, parts[1]
585
+ else:
586
+ return parts[0], parts[1], branch, None
546
587
 
547
- # Single filename, e.g. 8.1.py
588
+ # Single filename, e.g. main.py (requires default_owner and default_repo)
548
589
  return owner, repo, branch, target
549
590
 
550
591
 
@@ -565,7 +606,7 @@ def main():
565
606
  except Exception as e:
566
607
  print(f"Error removing config file: {e}")
567
608
  return
568
- print(f"Configuration reset to defaults (User: {DEFAULT_OWNER}, Repo: {DEFAULT_REPO}).")
609
+ print("Configuration reset to defaults (no default repository or user).")
569
610
  return
570
611
 
571
612
  # Handle --set-default-user
@@ -577,9 +618,10 @@ def main():
577
618
 
578
619
  # Handle --set-default
579
620
  if args.set_default:
580
- owner_to_save = args.user if args.user else None
581
- save_config(owner=owner_to_save, repo=args.set_default)
582
- print(f"Default repository updated to: '{args.set_default}'")
621
+ owner_part, repo_part = parse_repo_identifier(args.set_default, default_owner=args.user)
622
+ owner_to_save = owner_part or (args.user if args.user else None)
623
+ save_config(owner=owner_to_save, repo=repo_part)
624
+ print(f"Default repository updated to: '{repo_part}'")
583
625
  if owner_to_save:
584
626
  print(f"Default user updated to: '{owner_to_save}'")
585
627
  print(f"Saved to: {CONFIG_FILE}")
@@ -588,8 +630,8 @@ def main():
588
630
  # Handle --config
589
631
  if args.config:
590
632
  print("CodeFetch Configuration:")
591
- print(f" Default User: {default_owner}")
592
- print(f" Default Repository: {default_repo}")
633
+ print(f" Default User: {default_owner or 'None (not set)'}")
634
+ print(f" Default Repository: {default_repo or 'None (not set)'}")
593
635
  print(f" Default Branch: {default_branch}")
594
636
  print(f" Config File: {CONFIG_FILE}")
595
637
  return
@@ -602,6 +644,12 @@ def main():
602
644
  target_owner = owner
603
645
  if args.args:
604
646
  target_owner = args.args[0]
647
+ if not target_owner:
648
+ print("Error: No GitHub user specified.\n")
649
+ print("Usage:")
650
+ print(" codefetch --repos <username>")
651
+ print(" codefetch -R -u <username>")
652
+ return
605
653
  list_repos(owner=target_owner, default_repo=default_repo)
606
654
  return
607
655
 
@@ -613,6 +661,12 @@ def main():
613
661
  target_owner, target_repo = parse_repo_identifier(args.repo, default_owner=owner)
614
662
  elif args.args:
615
663
  target_owner, target_repo = parse_repo_identifier(args.args[0], default_owner=owner)
664
+ if not target_owner or not target_repo:
665
+ print("Error: No repository specified to list.\n")
666
+ print("Usage:")
667
+ print(" codefetch -l <owner>/<repo>")
668
+ print(" codefetch -u <owner> -r <repo> -l")
669
+ return
616
670
  list_files(owner=target_owner, repo=target_repo, branch=branch)
617
671
  return
618
672
 
@@ -621,8 +675,6 @@ def main():
621
675
  parser.print_help()
622
676
  return
623
677
 
624
- known_repos = [default_repo, "DS", "MPL", "Discord-Chatbot", "Codefetch"]
625
-
626
678
  resolved_owner, resolved_repo, resolved_branch, resolved_filename = resolve_invocation(
627
679
  args.args,
628
680
  args.repo,
@@ -631,14 +683,12 @@ def main():
631
683
  default_owner,
632
684
  default_repo,
633
685
  default_branch,
634
- known_repos=known_repos,
635
686
  )
636
687
 
637
688
  if not resolved_filename:
638
689
  print("Error: No filename specified.\n")
639
690
  print("Usage:")
640
- print(" codefetch <filename> Download a file")
641
- print(" codefetch <repo> <filename> Download from a repository")
691
+ print(" codefetch <owner>/<repo> <filename>")
642
692
  print(" codefetch --help View full guide and examples")
643
693
  return
644
694
 
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: codefetch-cli
3
+ Version: 1.2.3
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 and Python library 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 <owner>/<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 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
+ - **Python Library Support**: Import and use programmatically with `import codefetch as cf`.
39
+ - **Optional Persistent Defaults**: Save a default repository or user with `--set-default` if you frequently work with the same repository.
40
+ - **Zero Runtime Dependencies**: Built using only the Python standard library with zero third-party dependencies.
41
+
42
+ ---
43
+
44
+ ## Installation
45
+
46
+ ### From PyPI
47
+
48
+ ```bash
49
+ pip install codefetch-cli
50
+ ```
51
+
52
+ ### From GitHub
53
+
54
+ ```bash
55
+ pip install git+https://github.com/InzamamulQureshi/Codefetch.git
56
+ ```
57
+
58
+ ### From Local Source
59
+
60
+ Clone the repository and install locally:
61
+
62
+ ```bash
63
+ git clone https://github.com/InzamamulQureshi/Codefetch.git
64
+ cd Codefetch
65
+ pip install .
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Quick Start & Usage
71
+
72
+ ### 1. Download Files
73
+
74
+ Fetch a single file from any GitHub repository:
75
+
76
+ ```bash
77
+ # Fetch a file from any user and repository
78
+ codefetch owner/repo main.py
79
+
80
+ # Download a configuration or dataset
81
+ codefetch owner/repo data.csv
82
+ codefetch owner/repo config.yaml
83
+
84
+ # Download using explicit flags
85
+ codefetch -u owner -r repo main.py
86
+
87
+ # Save with a custom filename or destination path
88
+ codefetch owner/repo main.py -o custom_name.py
89
+ ```
90
+
91
+ ### 2. Preview in Terminal (Without Downloading)
92
+
93
+ Display the contents of a file directly in stdout with `-s` / `--show`:
94
+
95
+ ```bash
96
+ # Preview file content in terminal
97
+ codefetch -s owner/repo main.py
98
+ codefetch -s torvalds/linux Makefile
99
+ ```
100
+
101
+ ### 3. Explore Repositories and Files
102
+
103
+ ```bash
104
+ # List all code files in a repository
105
+ codefetch -l owner/repo
106
+ codefetch --list owner/repo
107
+
108
+ # List all public repositories for any GitHub user
109
+ codefetch -R -u username
110
+ codefetch --repos username
111
+ ```
112
+
113
+ ### 4. Configuration and Defaults (Optional)
114
+
115
+ By default, CodeFetch has no preset repository or user. If you frequently fetch files from the same repository, you can optionally configure defaults:
116
+
117
+ ```bash
118
+ # Set a default repository (saves both user and repo)
119
+ codefetch --set-default owner/repo
120
+
121
+ # Now you can fetch files directly without typing the repo name!
122
+ codefetch main.py
123
+ codefetch -l
124
+
125
+ # View current saved configuration
126
+ codefetch --config
127
+
128
+ # Reset all configuration back to defaults (no default repository)
129
+ codefetch --reset-config
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Python API Usage
135
+
136
+ You can also use CodeFetch programmatically within your Python scripts:
137
+
138
+ ```python
139
+ import codefetch as cf
140
+
141
+ # 1. Fetch file content as a string directly in memory
142
+ makefile_text = cf.get("torvalds/linux", "Makefile")
143
+ # Or shorthand single argument syntax:
144
+ readme_text = cf.get("psf/requests/README.md")
145
+
146
+ # 2. Download a file to disk
147
+ cf.download("torvalds/linux", "Makefile", output="linux_Makefile")
148
+
149
+ # 3. List all code files in a repository
150
+ files = cf.list("torvalds/linux")
151
+ print(f"Found {len(files)} files")
152
+ ```
153
+
154
+ ---
155
+
156
+ ## Command Reference
157
+
158
+ | Flag | Description | Example |
159
+ |---|---|---|
160
+ | `-s`, `--show` | Print file contents to terminal | `codefetch -s owner/repo main.py` |
161
+ | `-o`, `--output` | Specify destination output filename | `codefetch owner/repo main.py -o script.py` |
162
+ | `-l`, `--list` | List available code files in a repository | `codefetch -l owner/repo` |
163
+ | `-R`, `--repos` | List public repositories for a user | `codefetch -R -u username` |
164
+ | `-r`, `--repo` | Specify repository name or URL | `codefetch -r repo main.py` |
165
+ | `-u`, `--user` | Specify GitHub username or organization | `codefetch -u username -r repo main.py` |
166
+ | `-b`, `--branch` | Branch name (default: `main`) | `codefetch -b master owner/repo Makefile` |
167
+ | `--set-default` | Set persistent default repository | `codefetch --set-default owner/repo` |
168
+ | `--set-default-user` | Set persistent default user | `codefetch --set-default-user username` |
169
+ | `--config` | Display active configuration | `codefetch --config` |
170
+ | `--reset-config` | Reset configuration back to defaults | `codefetch --reset-config` |
171
+ | `-v`, `--version` | Display version information | `codefetch --version` |
172
+ | `-h`, `--help` | Display help and usage message | `codefetch --help` |
173
+
174
+ ---
175
+
176
+ ## Supported File Extensions
177
+
178
+ CodeFetch recognizes and lists common source and document files:
179
+
180
+ - **Data & Tables**: `.csv`, `.tsv`, `.json`, `.jsonl`, `.yaml`, `.yml`, `.toml`, `.xml`, `.ini`, `.cfg`, `.conf`, `.env`
181
+ - **Data Science**: `.ipynb`, `.py`, `.r`, `.rmd`, `.m`
182
+ - **Languages**: `.c`, `.cpp`, `.h`, `.hpp`, `.rs`, `.go`, `.java`, `.kt`, `.cs`, `.swift`, `.dart`, `.php`
183
+ - **Web**: `.html`, `.css`, `.scss`, `.js`, `.jsx`, `.ts`, `.tsx`, `.vue`, `.svelte`
184
+ - **Scripts & Systems**: `.sh`, `.bash`, `.zsh`, `.fish`, `.bat`, `.cmd`, `.ps1`, `.lua`, `.rb`, `.asm`, `.s`, `.hex`
185
+ - **Database & Schemas**: `.sql`, `.graphql`, `.gql`, `.proto`
186
+ - **Documentation**: `.md`, `.markdown`, `.rst`, `.txt`, `.tex`, `.log`
187
+ - **Build Files**: `Dockerfile`, `Makefile`, `CMakeLists.txt`, `Gemfile`, `Procfile`
188
+
189
+ ---
190
+
191
+ ## License
192
+
193
+ This project is licensed under the [MIT License](LICENSE).
@@ -0,0 +1,8 @@
1
+ codefetch/__init__.py,sha256=4eDsjK1Bul-KXSSDOJqK2OsrS_2ckHtW_n8Z_jQiC5U,4542
2
+ codefetch/cli.py,sha256=sftww8iPXrpcOfTnbULLXPN0AXoSzKA_S-TvjSiOrQc,25531
3
+ codefetch_cli-1.2.3.dist-info/licenses/LICENSE,sha256=hqEPPm8jxPava5BfKIj3wTIXNyWNwsIvPv2daPCOijY,1094
4
+ codefetch_cli-1.2.3.dist-info/METADATA,sha256=pIx0QNujNrvHrf9YgdB0w1NwxFrPrBF2362xcO_NQsQ,6675
5
+ codefetch_cli-1.2.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
+ codefetch_cli-1.2.3.dist-info/entry_points.txt,sha256=jFLRsBB_USeSdqjewcXZfnOzY56s0PZat7mnYkYdrXg,49
7
+ codefetch_cli-1.2.3.dist-info/top_level.txt,sha256=kHrftHbyqugb8LQXjW1dR9uI-JFjZQJ0sUReIa45yXQ,10
8
+ codefetch_cli-1.2.3.dist-info/RECORD,,
@@ -1,21 +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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 InzamamulQureshi
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.
@@ -1,187 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: codefetch-cli
3
- Version: 1.2.1
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
- - **Data & Tables**: `.csv`, `.tsv`, `.json`, `.jsonl`, `.yaml`, `.yml`, `.toml`, `.xml`, `.ini`, `.cfg`, `.conf`, `.env`
175
- - **Data Science**: `.ipynb`, `.py`, `.r`, `.rmd`, `.m`
176
- - **Languages**: `.c`, `.cpp`, `.h`, `.hpp`, `.rs`, `.go`, `.java`, `.kt`, `.cs`, `.swift`, `.dart`, `.php`
177
- - **Web**: `.html`, `.css`, `.scss`, `.js`, `.jsx`, `.ts`, `.tsx`, `.vue`, `.svelte`
178
- - **Scripts & Systems**: `.sh`, `.bash`, `.zsh`, `.fish`, `.bat`, `.cmd`, `.ps1`, `.lua`, `.rb`, `.asm`, `.s`, `.hex`
179
- - **Database & Schemas**: `.sql`, `.graphql`, `.gql`, `.proto`
180
- - **Documentation**: `.md`, `.markdown`, `.rst`, `.txt`, `.tex`, `.log`
181
- - **Build Files**: `Dockerfile`, `Makefile`, `CMakeLists.txt`, `Gemfile`, `Procfile`
182
-
183
- ---
184
-
185
- ## License
186
-
187
- This project is licensed under the [MIT License](LICENSE).
@@ -1,8 +0,0 @@
1
- codefetch/__init__.py,sha256=hbpjJJOuxVjNlJ4ZVpsQEyhLAepknLr6ugsa01sVVCU,98
2
- codefetch/cli.py,sha256=-Eg3K81jid2uXQ-sGA943HWHL92-WYNi3-Q6putdlEQ,22588
3
- codefetch_cli-1.2.1.dist-info/licenses/LICENSE,sha256=eztogQOJbUv4rWzTBEaVeMVQYzoaP1Ehs_vg-CqlpsM,1066
4
- codefetch_cli-1.2.1.dist-info/METADATA,sha256=LMZvjcTMWpMb6B3Qciy4hEoZXAFhAHJJ-Shxev11nrs,6117
5
- codefetch_cli-1.2.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
6
- codefetch_cli-1.2.1.dist-info/entry_points.txt,sha256=jFLRsBB_USeSdqjewcXZfnOzY56s0PZat7mnYkYdrXg,49
7
- codefetch_cli-1.2.1.dist-info/top_level.txt,sha256=kHrftHbyqugb8LQXjW1dR9uI-JFjZQJ0sUReIa45yXQ,10
8
- codefetch_cli-1.2.1.dist-info/RECORD,,