codefetch-cli 1.2.3__py3-none-any.whl → 1.2.5__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 +30 -16
- codefetch/cli.py +333 -141
- {codefetch_cli-1.2.3.dist-info → codefetch_cli-1.2.5.dist-info}/METADATA +11 -2
- codefetch_cli-1.2.5.dist-info/RECORD +8 -0
- codefetch_cli-1.2.3.dist-info/RECORD +0 -8
- {codefetch_cli-1.2.3.dist-info → codefetch_cli-1.2.5.dist-info}/WHEEL +0 -0
- {codefetch_cli-1.2.3.dist-info → codefetch_cli-1.2.5.dist-info}/entry_points.txt +0 -0
- {codefetch_cli-1.2.3.dist-info → codefetch_cli-1.2.5.dist-info}/licenses/LICENSE +0 -0
- {codefetch_cli-1.2.3.dist-info → codefetch_cli-1.2.5.dist-info}/top_level.txt +0 -0
codefetch/__init__.py
CHANGED
|
@@ -16,17 +16,17 @@ Usage in Python:
|
|
|
16
16
|
files = cf.list("torvalds/linux")
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
__version__ = "1.2.
|
|
19
|
+
__version__ = "1.2.5"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def get(target, filename=None, branch=
|
|
22
|
+
def get(target, filename=None, branch=None):
|
|
23
23
|
"""
|
|
24
24
|
Fetch and return the text content of a file from GitHub.
|
|
25
25
|
|
|
26
26
|
Args:
|
|
27
27
|
target: 'owner/repo' or full path 'owner/repo/path/to/file' or full GitHub URL.
|
|
28
28
|
filename: Optional path to file in repo if target is 'owner/repo'.
|
|
29
|
-
branch: Branch name (default:
|
|
29
|
+
branch: Branch name (default: None, automatically detects repo default branch).
|
|
30
30
|
|
|
31
31
|
Returns:
|
|
32
32
|
str: File content.
|
|
@@ -37,8 +37,10 @@ def get(target, filename=None, branch="main"):
|
|
|
37
37
|
"""
|
|
38
38
|
import urllib.request
|
|
39
39
|
from .cli import (
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
get_default_branch,
|
|
41
|
+
check_branch_exists,
|
|
42
|
+
fetch_raw_single,
|
|
43
|
+
get_repo_tree,
|
|
42
44
|
resolve_file_in_tree,
|
|
43
45
|
parse_repo_identifier,
|
|
44
46
|
resolve_invocation,
|
|
@@ -49,7 +51,7 @@ def get(target, filename=None, branch="main"):
|
|
|
49
51
|
file_path = filename.strip("/\\")
|
|
50
52
|
else:
|
|
51
53
|
owner, repo, resolved_branch, file_path = resolve_invocation(
|
|
52
|
-
[target], None, None, branch, None, None
|
|
54
|
+
[target], None, None, branch, None, None
|
|
53
55
|
)
|
|
54
56
|
if resolved_branch:
|
|
55
57
|
branch = resolved_branch
|
|
@@ -57,24 +59,30 @@ def get(target, filename=None, branch="main"):
|
|
|
57
59
|
if not owner or not repo or not file_path:
|
|
58
60
|
raise ValueError(f"Could not resolve owner, repo, and filename from target='{target}', filename='{filename}'")
|
|
59
61
|
|
|
62
|
+
target_branch = branch
|
|
63
|
+
if not target_branch:
|
|
64
|
+
target_branch = get_default_branch(owner, repo)
|
|
65
|
+
elif not check_branch_exists(owner, repo, target_branch):
|
|
66
|
+
target_branch = get_default_branch(owner, repo)
|
|
67
|
+
|
|
60
68
|
# 1. Try fast direct raw fetch
|
|
61
|
-
|
|
69
|
+
content = fetch_raw_single(owner, repo, target_branch, file_path)
|
|
62
70
|
if content is not None:
|
|
63
71
|
return content
|
|
64
72
|
|
|
65
73
|
# 2. Try repository tree search
|
|
66
|
-
|
|
74
|
+
tree_data = get_repo_tree(owner, repo, target_branch)
|
|
67
75
|
matched = resolve_file_in_tree(tree_data, file_path)
|
|
68
76
|
if matched:
|
|
69
|
-
url = f"https://raw.githubusercontent.com/{owner}/{repo}/{
|
|
77
|
+
url = f"https://raw.githubusercontent.com/{owner}/{repo}/{target_branch}/{matched}"
|
|
70
78
|
req = urllib.request.Request(url, headers={"User-Agent": "codefetch"})
|
|
71
79
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
72
80
|
return resp.read().decode("utf-8")
|
|
73
81
|
|
|
74
|
-
raise FileNotFoundError(f"File '{file_path}' not found in {owner}/{repo} ({
|
|
82
|
+
raise FileNotFoundError(f"File '{file_path}' not found in {owner}/{repo} ({target_branch})")
|
|
75
83
|
|
|
76
84
|
|
|
77
|
-
def download(target, filename=None, output=None, branch=
|
|
85
|
+
def download(target, filename=None, output=None, branch=None):
|
|
78
86
|
"""
|
|
79
87
|
Download a file from GitHub and save it locally.
|
|
80
88
|
|
|
@@ -82,7 +90,7 @@ def download(target, filename=None, output=None, branch="main"):
|
|
|
82
90
|
target: 'owner/repo' or full path 'owner/repo/path/to/file'.
|
|
83
91
|
filename: Optional path to file in repo if target is 'owner/repo'.
|
|
84
92
|
output: Destination filename/path on local disk (default: original filename).
|
|
85
|
-
branch: Branch name (default:
|
|
93
|
+
branch: Branch name (default: None, automatically detects repo default branch).
|
|
86
94
|
|
|
87
95
|
Returns:
|
|
88
96
|
str: Path to the downloaded file.
|
|
@@ -99,20 +107,22 @@ def download(target, filename=None, output=None, branch="main"):
|
|
|
99
107
|
return dest_name
|
|
100
108
|
|
|
101
109
|
|
|
102
|
-
def list(target, branch=
|
|
110
|
+
def list(target, branch=None):
|
|
103
111
|
"""
|
|
104
112
|
List supported code files in a repository.
|
|
105
113
|
|
|
106
114
|
Args:
|
|
107
115
|
target: 'owner/repo' or repository URL.
|
|
108
|
-
branch: Branch name (default:
|
|
116
|
+
branch: Branch name (default: None, automatically detects repo default branch).
|
|
109
117
|
|
|
110
118
|
Returns:
|
|
111
119
|
list[str]: Relative paths of code files in the repository.
|
|
112
120
|
"""
|
|
113
121
|
from .cli import (
|
|
114
122
|
parse_repo_identifier,
|
|
115
|
-
|
|
123
|
+
get_default_branch,
|
|
124
|
+
check_branch_exists,
|
|
125
|
+
get_repo_tree,
|
|
116
126
|
CODE_EXTENSIONS,
|
|
117
127
|
NAMED_FILES,
|
|
118
128
|
)
|
|
@@ -121,7 +131,11 @@ def list(target, branch="main"):
|
|
|
121
131
|
if not owner or not repo:
|
|
122
132
|
raise ValueError(f"Invalid repository identifier: '{target}'")
|
|
123
133
|
|
|
124
|
-
|
|
134
|
+
target_branch = branch
|
|
135
|
+
if not target_branch or not check_branch_exists(owner, repo, target_branch):
|
|
136
|
+
target_branch = get_default_branch(owner, repo)
|
|
137
|
+
|
|
138
|
+
tree_data = get_repo_tree(owner, repo, target_branch)
|
|
125
139
|
files = []
|
|
126
140
|
for item in tree_data.get("tree", []):
|
|
127
141
|
if item.get("type") != "blob":
|
codefetch/cli.py
CHANGED
|
@@ -7,6 +7,7 @@ import argparse
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
|
+
import subprocess
|
|
10
11
|
import sys
|
|
11
12
|
import urllib.error
|
|
12
13
|
import urllib.request
|
|
@@ -14,11 +15,10 @@ from pathlib import Path
|
|
|
14
15
|
|
|
15
16
|
DEFAULT_OWNER = None
|
|
16
17
|
DEFAULT_REPO = None
|
|
17
|
-
DEFAULT_BRANCH = "main"
|
|
18
18
|
|
|
19
19
|
CONFIG_FILE = Path.home() / ".codefetch.json"
|
|
20
20
|
|
|
21
|
-
__version__ = "1.2.
|
|
21
|
+
__version__ = "1.2.5"
|
|
22
22
|
|
|
23
23
|
# Supported code, data, and config extensions
|
|
24
24
|
CODE_EXTENSIONS = {
|
|
@@ -62,6 +62,7 @@ USAGE:
|
|
|
62
62
|
codefetch -s <owner>/<repo> <filename> Preview file in terminal without saving
|
|
63
63
|
codefetch -l <owner>/<repo> List all code files in a repository
|
|
64
64
|
codefetch -R <username> List public repositories for a user
|
|
65
|
+
codefetch --update Update CodeFetch to the latest version
|
|
65
66
|
|
|
66
67
|
POPULAR EXAMPLES:
|
|
67
68
|
# Download from any repository:
|
|
@@ -93,36 +94,42 @@ FLAGS & OPTIONS:
|
|
|
93
94
|
-R, --repos List public repositories for a GitHub user
|
|
94
95
|
-r, --repo <repo> Specify repository name or URL
|
|
95
96
|
-u, --user <user> Specify GitHub username / organization
|
|
96
|
-
-b, --branch <branch> Specify branch name (default:
|
|
97
|
+
-b, --branch <branch> Specify branch name (default: repository default branch)
|
|
97
98
|
--set-default <repo> Save default repository (e.g. owner/repo)
|
|
98
99
|
--set-default-user <user> Save default user/organization
|
|
99
100
|
--config Show current saved configuration
|
|
100
101
|
--reset-config Reset configuration back to defaults
|
|
102
|
+
-U, --update Update CodeFetch to the latest version from PyPI
|
|
101
103
|
-v, --version Show version number
|
|
102
104
|
-h, --help Show this help message
|
|
103
105
|
"""
|
|
104
106
|
|
|
107
|
+
_REPO_DEFAULT_BRANCH_CACHE = {}
|
|
108
|
+
_REPO_BRANCHES_CACHE = {}
|
|
109
|
+
|
|
105
110
|
|
|
106
111
|
def load_config():
|
|
107
112
|
"""Load user configuration from ~/.codefetch.json."""
|
|
108
113
|
if CONFIG_FILE.exists():
|
|
109
114
|
try:
|
|
110
115
|
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
111
|
-
|
|
116
|
+
data = json.load(f)
|
|
117
|
+
return {
|
|
118
|
+
"owner": data.get("owner"),
|
|
119
|
+
"repo": data.get("repo"),
|
|
120
|
+
}
|
|
112
121
|
except Exception:
|
|
113
122
|
return {}
|
|
114
123
|
return {}
|
|
115
124
|
|
|
116
125
|
|
|
117
|
-
def save_config(owner=None, repo=None
|
|
126
|
+
def save_config(owner=None, repo=None):
|
|
118
127
|
"""Save user configuration to ~/.codefetch.json."""
|
|
119
128
|
cfg = load_config()
|
|
120
129
|
if owner is not None:
|
|
121
130
|
cfg["owner"] = owner
|
|
122
131
|
if repo is not None:
|
|
123
132
|
cfg["repo"] = repo
|
|
124
|
-
if branch is not None:
|
|
125
|
-
cfg["branch"] = branch
|
|
126
133
|
|
|
127
134
|
try:
|
|
128
135
|
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
@@ -168,76 +175,99 @@ def parse_repo_identifier(repo_str, default_owner=None):
|
|
|
168
175
|
return default_owner, repo_str.removesuffix(".git")
|
|
169
176
|
|
|
170
177
|
|
|
171
|
-
def
|
|
178
|
+
def get_default_branch(owner, repo):
|
|
172
179
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
and vice versa.
|
|
176
|
-
Returns (branch_used, content_str) or (None, None).
|
|
180
|
+
Get the default branch of a repository (e.g. 'main', 'master', 'unstable').
|
|
181
|
+
Queries GitHub API and caches the result. Falls back to probing if rate limited.
|
|
177
182
|
"""
|
|
178
|
-
|
|
179
|
-
if
|
|
180
|
-
|
|
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
|
|
183
|
+
key = f"{owner.lower()}/{repo.lower()}"
|
|
184
|
+
if key in _REPO_DEFAULT_BRANCH_CACHE:
|
|
185
|
+
return _REPO_DEFAULT_BRANCH_CACHE[key]
|
|
195
186
|
|
|
187
|
+
try:
|
|
188
|
+
url = f"https://api.github.com/repos/{owner}/{repo}"
|
|
189
|
+
data = get_json(url)
|
|
190
|
+
def_b = data.get("default_branch")
|
|
191
|
+
if def_b:
|
|
192
|
+
_REPO_DEFAULT_BRANCH_CACHE[key] = def_b
|
|
193
|
+
return def_b
|
|
194
|
+
except Exception:
|
|
195
|
+
pass
|
|
196
196
|
|
|
197
|
-
|
|
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"
|
|
197
|
+
# Fast fallback probe via CDN if API is unavailable or rate-limited
|
|
198
|
+
for b in ("main", "master"):
|
|
210
199
|
try:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
200
|
+
req = urllib.request.Request(
|
|
201
|
+
f"https://raw.githubusercontent.com/{owner}/{repo}/{b}/README.md",
|
|
202
|
+
headers=get_headers(),
|
|
203
|
+
)
|
|
204
|
+
with urllib.request.urlopen(req, timeout=5):
|
|
205
|
+
_REPO_DEFAULT_BRANCH_CACHE[key] = b
|
|
206
|
+
return b
|
|
207
|
+
except Exception:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
return "main"
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def get_repo_branches(owner, repo):
|
|
214
|
+
"""Fetch the list of all branch names in a repository."""
|
|
215
|
+
key = f"{owner.lower()}/{repo.lower()}"
|
|
216
|
+
if key in _REPO_BRANCHES_CACHE:
|
|
217
|
+
return _REPO_BRANCHES_CACHE[key]
|
|
216
218
|
|
|
217
|
-
# Query repository info to discover actual default branch (e.g. develop, trunk, etc.)
|
|
218
219
|
try:
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if
|
|
223
|
-
|
|
224
|
-
return
|
|
220
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/branches?per_page=100"
|
|
221
|
+
data = get_json(url)
|
|
222
|
+
branches = [b["name"] for b in data if isinstance(b, dict) and b.get("name")]
|
|
223
|
+
if branches:
|
|
224
|
+
_REPO_BRANCHES_CACHE[key] = branches
|
|
225
|
+
return branches
|
|
225
226
|
except Exception:
|
|
226
227
|
pass
|
|
228
|
+
return []
|
|
227
229
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
|
|
231
|
+
def check_branch_exists(owner, repo, branch):
|
|
232
|
+
"""Check if a specific branch exists in a repository."""
|
|
233
|
+
if not branch:
|
|
234
|
+
return False
|
|
235
|
+
branches = get_repo_branches(owner, repo)
|
|
236
|
+
if branches:
|
|
237
|
+
return branch in branches
|
|
238
|
+
try:
|
|
239
|
+
get_json(f"https://api.github.com/repos/{owner}/{repo}/branches/{branch}")
|
|
240
|
+
return True
|
|
241
|
+
except urllib.error.HTTPError as e:
|
|
242
|
+
if e.code == 404:
|
|
243
|
+
return False
|
|
244
|
+
return True
|
|
245
|
+
except Exception:
|
|
246
|
+
return True
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def fetch_raw_single(owner, repo, branch, clean_filename):
|
|
250
|
+
"""
|
|
251
|
+
Direct raw fetch from raw.githubusercontent.com for a specific branch.
|
|
252
|
+
Returns content string or None if 404.
|
|
253
|
+
"""
|
|
254
|
+
url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{clean_filename}"
|
|
255
|
+
try:
|
|
256
|
+
req = urllib.request.Request(url, headers={"User-Agent": "codefetch"})
|
|
257
|
+
with urllib.request.urlopen(req, timeout=15) as resp:
|
|
258
|
+
return resp.read().decode("utf-8")
|
|
259
|
+
except urllib.error.HTTPError as e:
|
|
260
|
+
if e.code == 404:
|
|
261
|
+
return None
|
|
262
|
+
raise
|
|
263
|
+
except UnicodeDecodeError:
|
|
264
|
+
raise
|
|
235
265
|
|
|
236
266
|
|
|
237
|
-
def get_repo_tree(owner, repo, branch
|
|
238
|
-
"""Fetch the Git tree for a repository
|
|
239
|
-
|
|
240
|
-
return
|
|
267
|
+
def get_repo_tree(owner, repo, branch):
|
|
268
|
+
"""Fetch the Git tree for a repository on a given branch."""
|
|
269
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1"
|
|
270
|
+
return get_json(url)
|
|
241
271
|
|
|
242
272
|
|
|
243
273
|
def get_user_repos(owner):
|
|
@@ -247,7 +277,7 @@ def get_user_repos(owner):
|
|
|
247
277
|
|
|
248
278
|
|
|
249
279
|
def list_repos(owner=None, default_repo=None):
|
|
250
|
-
"""List all public repositories for a GitHub user."""
|
|
280
|
+
"""List all public repositories for a GitHub user with clean spacing."""
|
|
251
281
|
if not owner:
|
|
252
282
|
print("Error: No GitHub username specified.")
|
|
253
283
|
print("Usage: codefetch --repos <username>")
|
|
@@ -259,21 +289,21 @@ def list_repos(owner=None, default_repo=None):
|
|
|
259
289
|
print(f"No public repositories found for user '{owner}'.")
|
|
260
290
|
return
|
|
261
291
|
|
|
262
|
-
print(f"Public repositories for '{owner}':\n")
|
|
292
|
+
print(f"Public repositories for '{owner}' ({len(repos)} total):\n")
|
|
263
293
|
for i, r in enumerate(repos, 1):
|
|
264
294
|
name = r.get("name", "")
|
|
265
|
-
desc = r.get("description") or "
|
|
295
|
+
desc = (r.get("description") or "").strip()
|
|
266
296
|
is_def = " (default)" if default_repo and name.lower() == default_repo.lower() else ""
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if desc and desc != "No description":
|
|
297
|
+
print(f" {i:>2}. {name}{is_def}")
|
|
298
|
+
if desc:
|
|
270
299
|
print(f" {desc}")
|
|
300
|
+
print()
|
|
271
301
|
|
|
272
|
-
print(
|
|
273
|
-
print("
|
|
274
|
-
print(f" codefetch -
|
|
275
|
-
print(f" codefetch {owner}/<repo>
|
|
276
|
-
print(f" codefetch --set-default {owner}/<repo> Set default repository")
|
|
302
|
+
print("Commands:")
|
|
303
|
+
print(f" codefetch {owner}/<repo> <filename> Download a file")
|
|
304
|
+
print(f" codefetch -s {owner}/<repo> <filename> Preview file in terminal")
|
|
305
|
+
print(f" codefetch -l {owner}/<repo> List files in repository")
|
|
306
|
+
print(f" codefetch --set-default {owner}/<repo> Set as default repository")
|
|
277
307
|
|
|
278
308
|
except urllib.error.HTTPError as e:
|
|
279
309
|
print(f"Could not list repositories: HTTP {e.code}")
|
|
@@ -283,8 +313,8 @@ def list_repos(owner=None, default_repo=None):
|
|
|
283
313
|
print(f"Could not list repositories: {e}")
|
|
284
314
|
|
|
285
315
|
|
|
286
|
-
def list_files(owner, repo, branch=
|
|
287
|
-
"""List code files in a repository."""
|
|
316
|
+
def list_files(owner, repo, branch=None):
|
|
317
|
+
"""List code files in a repository, with automatic branch detection and fallback."""
|
|
288
318
|
if not owner or not repo:
|
|
289
319
|
print("Error: No repository specified to list.\n")
|
|
290
320
|
print("Usage:")
|
|
@@ -292,8 +322,25 @@ def list_files(owner, repo, branch="main"):
|
|
|
292
322
|
print(" codefetch -u <owner> -r <repo> -l")
|
|
293
323
|
return
|
|
294
324
|
|
|
325
|
+
target_branch = branch
|
|
326
|
+
default_branch = get_default_branch(owner, repo)
|
|
327
|
+
|
|
328
|
+
# If branch was specified by user, verify it first
|
|
329
|
+
if target_branch:
|
|
330
|
+
if not check_branch_exists(owner, repo, target_branch):
|
|
331
|
+
print(f"Notice: Branch '{target_branch}' was not found in {owner}/{repo}.")
|
|
332
|
+
available_branches = get_repo_branches(owner, repo)
|
|
333
|
+
if available_branches:
|
|
334
|
+
shown = available_branches[:15]
|
|
335
|
+
extra = f" (+{len(available_branches) - 15} more)" if len(available_branches) > 15 else ""
|
|
336
|
+
print(f"Available branches in {owner}/{repo}: {', '.join(shown)}{extra}")
|
|
337
|
+
print(f"Listing files from default branch '{default_branch}' instead:\n")
|
|
338
|
+
target_branch = default_branch
|
|
339
|
+
else:
|
|
340
|
+
target_branch = default_branch
|
|
341
|
+
|
|
295
342
|
try:
|
|
296
|
-
|
|
343
|
+
data = get_repo_tree(owner, repo, target_branch)
|
|
297
344
|
if data.get("truncated"):
|
|
298
345
|
print("Warning: GitHub returned a truncated file list.")
|
|
299
346
|
|
|
@@ -314,10 +361,10 @@ def list_files(owner, repo, branch="main"):
|
|
|
314
361
|
files.sort(key=str.lower)
|
|
315
362
|
|
|
316
363
|
if not files:
|
|
317
|
-
print(f"No supported code files found in {owner}/{repo} ({
|
|
364
|
+
print(f"No supported code files found in {owner}/{repo} ({target_branch}).")
|
|
318
365
|
return
|
|
319
366
|
|
|
320
|
-
print(f"Available files in {owner}/{repo} ({
|
|
367
|
+
print(f"Available files in {owner}/{repo} ({target_branch}):\n")
|
|
321
368
|
for i, path in enumerate(files, 1):
|
|
322
369
|
print(f"{i:>3}. {path}")
|
|
323
370
|
|
|
@@ -387,9 +434,9 @@ def find_file_in_other_repos(owner, current_repo, target_filename):
|
|
|
387
434
|
r_name = r.get("name")
|
|
388
435
|
if not r_name or (current_repo and r_name.lower() == current_repo.lower()):
|
|
389
436
|
continue
|
|
390
|
-
|
|
437
|
+
default_b = r.get("default_branch") or "main"
|
|
391
438
|
try:
|
|
392
|
-
tree_data = get_repo_tree(owner, r_name,
|
|
439
|
+
tree_data = get_repo_tree(owner, r_name, default_b)
|
|
393
440
|
match = resolve_file_in_tree(tree_data, target_filename)
|
|
394
441
|
if match:
|
|
395
442
|
return r_name, match
|
|
@@ -400,9 +447,14 @@ def find_file_in_other_repos(owner, current_repo, target_filename):
|
|
|
400
447
|
return None, None
|
|
401
448
|
|
|
402
449
|
|
|
403
|
-
def fetch_file(filename, owner=None, repo=None, branch=
|
|
450
|
+
def fetch_file(filename, owner=None, repo=None, branch=None,
|
|
404
451
|
show=False, output=None):
|
|
405
|
-
"""
|
|
452
|
+
"""
|
|
453
|
+
Fetch or download a file from GitHub.
|
|
454
|
+
- If branch is None: fetches the default branch of the repository.
|
|
455
|
+
- If branch is specified: tries that branch first; if not found, shows message,
|
|
456
|
+
displays all available branches, and falls back to default branch.
|
|
457
|
+
"""
|
|
406
458
|
if not owner or not repo:
|
|
407
459
|
print("Error: No GitHub repository specified.\n")
|
|
408
460
|
print("Usage:")
|
|
@@ -422,63 +474,150 @@ def fetch_file(filename, owner=None, repo=None, branch=DEFAULT_BRANCH,
|
|
|
422
474
|
|
|
423
475
|
content = None
|
|
424
476
|
resolved_path = clean_filename
|
|
425
|
-
actual_branch =
|
|
477
|
+
actual_branch = None
|
|
426
478
|
|
|
427
|
-
|
|
428
|
-
try:
|
|
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
|
|
436
|
-
except urllib.error.HTTPError as e:
|
|
437
|
-
print(f"Download failed: HTTP {e.code}")
|
|
438
|
-
return
|
|
439
|
-
except urllib.error.URLError:
|
|
440
|
-
print("Could not connect to GitHub.")
|
|
441
|
-
return
|
|
479
|
+
default_branch = get_default_branch(owner, repo)
|
|
442
480
|
|
|
443
|
-
#
|
|
444
|
-
if
|
|
481
|
+
# Case A: Branch parameter was explicitly entered by the user
|
|
482
|
+
if branch:
|
|
483
|
+
branch_exists = check_branch_exists(owner, repo, branch)
|
|
484
|
+
|
|
485
|
+
if not branch_exists:
|
|
486
|
+
print(f"Notice: Branch '{branch}' was not found in {owner}/{repo}.")
|
|
487
|
+
available_branches = get_repo_branches(owner, repo)
|
|
488
|
+
if available_branches:
|
|
489
|
+
shown = available_branches[:15]
|
|
490
|
+
extra = f" (+{len(available_branches) - 15} more)" if len(available_branches) > 15 else ""
|
|
491
|
+
print(f"Available branches in {owner}/{repo}: {', '.join(shown)}{extra}")
|
|
492
|
+
print(f"Trying default branch '{default_branch}'...")
|
|
493
|
+
|
|
494
|
+
actual_branch = default_branch
|
|
495
|
+
try:
|
|
496
|
+
content = fetch_raw_single(owner, repo, default_branch, clean_filename)
|
|
497
|
+
except UnicodeDecodeError:
|
|
498
|
+
print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
|
|
499
|
+
return
|
|
500
|
+
except Exception:
|
|
501
|
+
pass
|
|
502
|
+
|
|
503
|
+
if content is None:
|
|
504
|
+
try:
|
|
505
|
+
tree_data = get_repo_tree(owner, repo, default_branch)
|
|
506
|
+
matched = resolve_file_in_tree(tree_data, clean_filename)
|
|
507
|
+
if matched:
|
|
508
|
+
resolved_path = matched
|
|
509
|
+
content = download_url(f"https://raw.githubusercontent.com/{owner}/{repo}/{default_branch}/{resolved_path}")
|
|
510
|
+
if resolved_path != clean_filename:
|
|
511
|
+
print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
|
|
512
|
+
except Exception:
|
|
513
|
+
pass
|
|
514
|
+
|
|
515
|
+
if content is None:
|
|
516
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo} (checked '{branch}' and default '{default_branch}').")
|
|
517
|
+
print(f"To fetch from a specific branch: codefetch -b <branch> {owner}/{repo} {clean_filename}")
|
|
518
|
+
return
|
|
519
|
+
|
|
520
|
+
else:
|
|
521
|
+
actual_branch = branch
|
|
522
|
+
try:
|
|
523
|
+
content = fetch_raw_single(owner, repo, branch, clean_filename)
|
|
524
|
+
except UnicodeDecodeError:
|
|
525
|
+
print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
|
|
526
|
+
return
|
|
527
|
+
except Exception:
|
|
528
|
+
pass
|
|
529
|
+
|
|
530
|
+
if content is None:
|
|
531
|
+
try:
|
|
532
|
+
tree_data = get_repo_tree(owner, repo, branch)
|
|
533
|
+
matched = resolve_file_in_tree(tree_data, clean_filename)
|
|
534
|
+
if matched:
|
|
535
|
+
resolved_path = matched
|
|
536
|
+
content = download_url(f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{resolved_path}")
|
|
537
|
+
if resolved_path != clean_filename:
|
|
538
|
+
print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
|
|
539
|
+
except Exception:
|
|
540
|
+
pass
|
|
541
|
+
|
|
542
|
+
if content is None and branch.lower() != default_branch.lower():
|
|
543
|
+
print(f"Notice: File '{clean_filename}' not found on branch '{branch}'.")
|
|
544
|
+
available_branches = get_repo_branches(owner, repo)
|
|
545
|
+
if available_branches:
|
|
546
|
+
shown = available_branches[:15]
|
|
547
|
+
extra = f" (+{len(available_branches) - 15} more)" if len(available_branches) > 15 else ""
|
|
548
|
+
print(f"Available branches in {owner}/{repo}: {', '.join(shown)}{extra}")
|
|
549
|
+
print(f"Trying default branch '{default_branch}'...")
|
|
550
|
+
|
|
551
|
+
try:
|
|
552
|
+
content = fetch_raw_single(owner, repo, default_branch, clean_filename)
|
|
553
|
+
if content is not None:
|
|
554
|
+
actual_branch = default_branch
|
|
555
|
+
except Exception:
|
|
556
|
+
pass
|
|
557
|
+
|
|
558
|
+
if content is None:
|
|
559
|
+
try:
|
|
560
|
+
tree_data = get_repo_tree(owner, repo, default_branch)
|
|
561
|
+
matched = resolve_file_in_tree(tree_data, clean_filename)
|
|
562
|
+
if matched:
|
|
563
|
+
resolved_path = matched
|
|
564
|
+
actual_branch = default_branch
|
|
565
|
+
content = download_url(f"https://raw.githubusercontent.com/{owner}/{repo}/{default_branch}/{resolved_path}")
|
|
566
|
+
if resolved_path != clean_filename:
|
|
567
|
+
print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
|
|
568
|
+
except Exception:
|
|
569
|
+
pass
|
|
570
|
+
|
|
571
|
+
if content is None:
|
|
572
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo} ({branch}).")
|
|
573
|
+
return
|
|
574
|
+
|
|
575
|
+
# Case B: No branch was specified (branch is None) -> fetch repo default branch
|
|
576
|
+
else:
|
|
577
|
+
actual_branch = default_branch
|
|
445
578
|
try:
|
|
446
|
-
|
|
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
|
|
579
|
+
content = fetch_raw_single(owner, repo, default_branch, clean_filename)
|
|
475
580
|
except UnicodeDecodeError:
|
|
476
581
|
print("The file is not UTF-8 text, so it cannot be displayed or saved as text.")
|
|
477
582
|
return
|
|
478
|
-
except
|
|
479
|
-
|
|
583
|
+
except urllib.error.HTTPError as e:
|
|
584
|
+
if e.code != 404:
|
|
585
|
+
print(f"Download failed: HTTP {e.code}")
|
|
586
|
+
return
|
|
587
|
+
except urllib.error.URLError:
|
|
588
|
+
print("Could not connect to GitHub.")
|
|
480
589
|
return
|
|
481
590
|
|
|
591
|
+
if content is None:
|
|
592
|
+
try:
|
|
593
|
+
tree_data = get_repo_tree(owner, repo, default_branch)
|
|
594
|
+
matched = resolve_file_in_tree(tree_data, clean_filename)
|
|
595
|
+
if matched:
|
|
596
|
+
resolved_path = matched
|
|
597
|
+
content = download_url(f"https://raw.githubusercontent.com/{owner}/{repo}/{default_branch}/{resolved_path}")
|
|
598
|
+
if resolved_path != clean_filename:
|
|
599
|
+
print(f"(Resolved '{clean_filename}' -> '{resolved_path}')")
|
|
600
|
+
else:
|
|
601
|
+
other_repo, other_match = find_file_in_other_repos(owner, repo, clean_filename)
|
|
602
|
+
if other_repo:
|
|
603
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo} ({default_branch}).")
|
|
604
|
+
print(f"\n[Tip] Found '{other_match}' in repository '{other_repo}'!")
|
|
605
|
+
print(f"To fetch it, run:\n codefetch {owner}/{other_repo} {other_match}")
|
|
606
|
+
return
|
|
607
|
+
else:
|
|
608
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo} ({default_branch}).")
|
|
609
|
+
print(f"Run 'codefetch -l {owner}/{repo}' to see available files.")
|
|
610
|
+
return
|
|
611
|
+
except urllib.error.HTTPError as e:
|
|
612
|
+
print(f"Could not search repository: HTTP {e.code}")
|
|
613
|
+
return
|
|
614
|
+
except urllib.error.URLError:
|
|
615
|
+
print("Could not connect to GitHub.")
|
|
616
|
+
return
|
|
617
|
+
except Exception:
|
|
618
|
+
print(f"File not found: '{clean_filename}' in {owner}/{repo} ({default_branch}).")
|
|
619
|
+
return
|
|
620
|
+
|
|
482
621
|
if content is None:
|
|
483
622
|
return
|
|
484
623
|
|
|
@@ -494,6 +633,56 @@ def fetch_file(filename, owner=None, repo=None, branch=DEFAULT_BRANCH,
|
|
|
494
633
|
print(f"Error saving file '{out_name}': {e}")
|
|
495
634
|
|
|
496
635
|
|
|
636
|
+
def parse_version_tuple(v_str):
|
|
637
|
+
"""Parse version string like '1.2.5' into a comparable tuple of integers."""
|
|
638
|
+
try:
|
|
639
|
+
return tuple(int(x) for x in re.findall(r"\d+", v_str))
|
|
640
|
+
except Exception:
|
|
641
|
+
return (0,)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def update_codefetch():
|
|
645
|
+
"""Update codefetch-cli to the latest version from PyPI."""
|
|
646
|
+
print("Checking for updates from PyPI...")
|
|
647
|
+
package_name = "codefetch-cli"
|
|
648
|
+
latest_version = None
|
|
649
|
+
|
|
650
|
+
try:
|
|
651
|
+
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
652
|
+
req = urllib.request.Request(url, headers={"User-Agent": "codefetch"})
|
|
653
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
654
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
655
|
+
latest_version = data.get("info", {}).get("version")
|
|
656
|
+
except Exception as e:
|
|
657
|
+
print(f"Note: Could not check PyPI metadata ({e}).")
|
|
658
|
+
|
|
659
|
+
if latest_version:
|
|
660
|
+
curr_tuple = parse_version_tuple(__version__)
|
|
661
|
+
latest_tuple = parse_version_tuple(latest_version)
|
|
662
|
+
|
|
663
|
+
if latest_tuple <= curr_tuple:
|
|
664
|
+
print(f"CodeFetch is already up to date (version {__version__}).")
|
|
665
|
+
return
|
|
666
|
+
|
|
667
|
+
print(f"Update available: v{latest_version} (current: v{__version__})")
|
|
668
|
+
print(f"Updating {package_name}...")
|
|
669
|
+
else:
|
|
670
|
+
print(f"Updating {package_name} to latest version...")
|
|
671
|
+
|
|
672
|
+
cmd = [sys.executable, "-m", "pip", "install", "--upgrade", package_name]
|
|
673
|
+
try:
|
|
674
|
+
result = subprocess.run(cmd)
|
|
675
|
+
if result.returncode == 0:
|
|
676
|
+
target_v = f"v{latest_version}" if latest_version else "the latest version"
|
|
677
|
+
print(f"\nSuccessfully updated CodeFetch to {target_v}!")
|
|
678
|
+
else:
|
|
679
|
+
print(f"\nUpdate failed (exit code {result.returncode}).")
|
|
680
|
+
print(f"You can manually update with: pip install --upgrade {package_name}")
|
|
681
|
+
except Exception as e:
|
|
682
|
+
print(f"\nError running update: {e}")
|
|
683
|
+
print(f"You can manually update with: pip install --upgrade {package_name}")
|
|
684
|
+
|
|
685
|
+
|
|
497
686
|
class CodefetchParser(argparse.ArgumentParser):
|
|
498
687
|
"""Custom parser providing clean, intuitive help formatting."""
|
|
499
688
|
|
|
@@ -518,12 +707,13 @@ def build_parser():
|
|
|
518
707
|
parser.add_argument("-R", "--repos", action="store_true", help="List public repositories for a user")
|
|
519
708
|
parser.add_argument("-r", "--repo", type=str, help="Repository name or URL")
|
|
520
709
|
parser.add_argument("-u", "--user", type=str, help="GitHub username")
|
|
521
|
-
parser.add_argument("-b", "--branch", type=str, help="Branch name (default:
|
|
710
|
+
parser.add_argument("-b", "--branch", type=str, default=None, help="Branch name (default: repository default branch)")
|
|
522
711
|
parser.add_argument("-o", "--output", type=str, help="Custom output filename or path")
|
|
523
712
|
parser.add_argument("--set-default", type=str, help="Save default repository (e.g. owner/repo)")
|
|
524
713
|
parser.add_argument("--set-default-user", type=str, help="Save default GitHub user")
|
|
525
714
|
parser.add_argument("--config", action="store_true", help="Display active configuration")
|
|
526
715
|
parser.add_argument("--reset-config", action="store_true", help="Reset configuration back to defaults")
|
|
716
|
+
parser.add_argument("-U", "--update", action="store_true", help="Update CodeFetch to the latest version")
|
|
527
717
|
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
528
718
|
parser.add_argument("-h", "--help", action="help", help="Show help message")
|
|
529
719
|
|
|
@@ -531,13 +721,13 @@ def build_parser():
|
|
|
531
721
|
|
|
532
722
|
|
|
533
723
|
def resolve_invocation(positional_args, repo_flag, user_flag, branch_flag,
|
|
534
|
-
default_owner, default_repo
|
|
724
|
+
default_owner, default_repo):
|
|
535
725
|
"""
|
|
536
726
|
Resolve arguments into (owner, repo, branch, filename).
|
|
537
727
|
"""
|
|
538
728
|
owner = user_flag or default_owner
|
|
539
729
|
repo = default_repo
|
|
540
|
-
branch = branch_flag
|
|
730
|
+
branch = branch_flag # None if not provided
|
|
541
731
|
filename = None
|
|
542
732
|
|
|
543
733
|
if repo_flag:
|
|
@@ -593,10 +783,14 @@ def main():
|
|
|
593
783
|
parser = build_parser()
|
|
594
784
|
args = parser.parse_args()
|
|
595
785
|
|
|
786
|
+
# Handle --update
|
|
787
|
+
if args.update:
|
|
788
|
+
update_codefetch()
|
|
789
|
+
return
|
|
790
|
+
|
|
596
791
|
config = load_config()
|
|
597
792
|
default_owner = config.get("owner", DEFAULT_OWNER)
|
|
598
793
|
default_repo = config.get("repo", DEFAULT_REPO)
|
|
599
|
-
default_branch = config.get("branch", DEFAULT_BRANCH)
|
|
600
794
|
|
|
601
795
|
# Handle --reset-config
|
|
602
796
|
if args.reset_config:
|
|
@@ -632,12 +826,11 @@ def main():
|
|
|
632
826
|
print("CodeFetch Configuration:")
|
|
633
827
|
print(f" Default User: {default_owner or 'None (not set)'}")
|
|
634
828
|
print(f" Default Repository: {default_repo or 'None (not set)'}")
|
|
635
|
-
print(f" Default Branch: {default_branch}")
|
|
636
829
|
print(f" Config File: {CONFIG_FILE}")
|
|
637
830
|
return
|
|
638
831
|
|
|
639
832
|
owner = args.user or default_owner
|
|
640
|
-
branch = args.branch
|
|
833
|
+
branch = args.branch # None if not provided
|
|
641
834
|
|
|
642
835
|
# Handle --repos (list repositories)
|
|
643
836
|
if args.repos:
|
|
@@ -682,7 +875,6 @@ def main():
|
|
|
682
875
|
args.branch,
|
|
683
876
|
default_owner,
|
|
684
877
|
default_repo,
|
|
685
|
-
default_branch,
|
|
686
878
|
)
|
|
687
879
|
|
|
688
880
|
if not resolved_filename:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codefetch-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.5
|
|
4
4
|
Summary: Quickly fetch code files from GitHub repositories with simple commands
|
|
5
5
|
Home-page: https://github.com/InzamamulQureshi/Codefetch
|
|
6
6
|
Author: Inzamamul Qureshi
|
|
@@ -35,6 +35,7 @@ CodeFetch is a lightweight command-line utility and Python library designed to q
|
|
|
35
35
|
- **Automatic Path & Case Resolution**: Automatically resolves case mismatches and finds files nested inside subdirectories.
|
|
36
36
|
- **Repository Discovery**: List all public repositories for any GitHub user with `--repos`.
|
|
37
37
|
- **Tree Exploration**: List all supported code files within a repository with `--list`.
|
|
38
|
+
- **Self-Updating**: Keep CodeFetch updated to the latest PyPI release with `codefetch --update`.
|
|
38
39
|
- **Python Library Support**: Import and use programmatically with `import codefetch as cf`.
|
|
39
40
|
- **Optional Persistent Defaults**: Save a default repository or user with `--set-default` if you frequently work with the same repository.
|
|
40
41
|
- **Zero Runtime Dependencies**: Built using only the Python standard library with zero third-party dependencies.
|
|
@@ -129,6 +130,13 @@ codefetch --config
|
|
|
129
130
|
codefetch --reset-config
|
|
130
131
|
```
|
|
131
132
|
|
|
133
|
+
### 5. Self-Updating
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Update CodeFetch to the latest version published on PyPI
|
|
137
|
+
codefetch --update
|
|
138
|
+
```
|
|
139
|
+
|
|
132
140
|
---
|
|
133
141
|
|
|
134
142
|
## Python API Usage
|
|
@@ -163,11 +171,12 @@ print(f"Found {len(files)} files")
|
|
|
163
171
|
| `-R`, `--repos` | List public repositories for a user | `codefetch -R -u username` |
|
|
164
172
|
| `-r`, `--repo` | Specify repository name or URL | `codefetch -r repo main.py` |
|
|
165
173
|
| `-u`, `--user` | Specify GitHub username or organization | `codefetch -u username -r repo main.py` |
|
|
166
|
-
| `-b`, `--branch` | Branch name (default:
|
|
174
|
+
| `-b`, `--branch` | Branch name (default: repo default branch) | `codefetch -b master owner/repo Makefile` |
|
|
167
175
|
| `--set-default` | Set persistent default repository | `codefetch --set-default owner/repo` |
|
|
168
176
|
| `--set-default-user` | Set persistent default user | `codefetch --set-default-user username` |
|
|
169
177
|
| `--config` | Display active configuration | `codefetch --config` |
|
|
170
178
|
| `--reset-config` | Reset configuration back to defaults | `codefetch --reset-config` |
|
|
179
|
+
| `-U`, `--update` | Update CodeFetch to latest PyPI version | `codefetch --update` |
|
|
171
180
|
| `-v`, `--version` | Display version information | `codefetch --version` |
|
|
172
181
|
| `-h`, `--help` | Display help and usage message | `codefetch --help` |
|
|
173
182
|
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
codefetch/__init__.py,sha256=TnExw5rEX9F0Y72dvp-rNut71JfVHBT6jco4-x8nfmE,5049
|
|
2
|
+
codefetch/cli.py,sha256=UC-ZpV3kYJ2zuAmOCD64YgT3unnhPAMDhYQ4a5TNTUo,33929
|
|
3
|
+
codefetch_cli-1.2.5.dist-info/licenses/LICENSE,sha256=hqEPPm8jxPava5BfKIj3wTIXNyWNwsIvPv2daPCOijY,1094
|
|
4
|
+
codefetch_cli-1.2.5.dist-info/METADATA,sha256=gjQ8dwKPqb3v-AyGvurQV0oD6XX00SLulqvkFcbiYNU,6994
|
|
5
|
+
codefetch_cli-1.2.5.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
codefetch_cli-1.2.5.dist-info/entry_points.txt,sha256=jFLRsBB_USeSdqjewcXZfnOzY56s0PZat7mnYkYdrXg,49
|
|
7
|
+
codefetch_cli-1.2.5.dist-info/top_level.txt,sha256=kHrftHbyqugb8LQXjW1dR9uI-JFjZQJ0sUReIa45yXQ,10
|
|
8
|
+
codefetch_cli-1.2.5.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|