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 +143 -2
- codefetch/cli.py +278 -228
- codefetch_cli-1.2.3.dist-info/METADATA +193 -0
- codefetch_cli-1.2.3.dist-info/RECORD +8 -0
- {codefetch_cli-1.2.1.dist-info → codefetch_cli-1.2.3.dist-info}/licenses/LICENSE +21 -21
- codefetch_cli-1.2.1.dist-info/METADATA +0 -187
- codefetch_cli-1.2.1.dist-info/RECORD +0 -8
- {codefetch_cli-1.2.1.dist-info → codefetch_cli-1.2.3.dist-info}/WHEEL +0 -0
- {codefetch_cli-1.2.1.dist-info → codefetch_cli-1.2.3.dist-info}/entry_points.txt +0 -0
- {codefetch_cli-1.2.1.dist-info → codefetch_cli-1.2.3.dist-info}/top_level.txt +0 -0
codefetch/__init__.py
CHANGED
|
@@ -1,5 +1,146 @@
|
|
|
1
1
|
"""
|
|
2
|
-
CodeFetch -
|
|
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.
|
|
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 =
|
|
16
|
-
DEFAULT_REPO =
|
|
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>
|
|
59
|
-
codefetch <
|
|
60
|
-
codefetch <
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
codefetch --set-default
|
|
83
|
-
codefetch
|
|
84
|
-
codefetch --config
|
|
85
|
-
codefetch --reset-config
|
|
86
|
-
|
|
87
|
-
OPTIONS:
|
|
88
|
-
-s, --show
|
|
89
|
-
-o, --output <file>
|
|
90
|
-
-l, --list
|
|
91
|
-
-R, --repos
|
|
92
|
-
-r, --repo <
|
|
93
|
-
-u, --user <
|
|
94
|
-
-b, --branch <
|
|
95
|
-
-
|
|
96
|
-
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
120
|
-
return
|
|
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
|
-
|
|
126
|
-
if owner:
|
|
127
|
-
|
|
128
|
-
if repo:
|
|
129
|
-
|
|
130
|
-
if branch:
|
|
131
|
-
|
|
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(
|
|
135
|
-
return True
|
|
129
|
+
json.dump(cfg, f, indent=2)
|
|
136
130
|
except Exception as e:
|
|
137
|
-
print(f"
|
|
138
|
-
return False
|
|
131
|
+
print(f"Warning: Could not save configuration: {e}")
|
|
139
132
|
|
|
140
133
|
|
|
141
134
|
def get_headers():
|
|
142
|
-
"""
|
|
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
|
|
155
|
-
|
|
156
|
-
with urllib.request.urlopen(
|
|
157
|
-
return json.loads(
|
|
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=
|
|
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
|
|
179
|
-
"""
|
|
180
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
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
|
-
|
|
197
|
-
return get_json(url)
|
|
198
|
-
except Exception:
|
|
199
|
-
return []
|
|
246
|
+
return get_json(url)
|
|
200
247
|
|
|
201
248
|
|
|
202
|
-
def list_repos(owner=
|
|
203
|
-
"""List all public repositories for
|
|
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"
|
|
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
|
-
|
|
217
|
-
|
|
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 -
|
|
221
|
-
print(f" codefetch
|
|
222
|
-
print(f" codefetch --set-default
|
|
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 =
|
|
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} ({
|
|
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. '
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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>
|
|
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
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|