code-to-txt 0.1.0__py3-none-any.whl → 0.2.0__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.
- code_to_txt/cli.py +158 -25
- code_to_txt/code_to_txt.py +121 -42
- code_to_txt/config.py +161 -0
- code_to_txt-0.2.0.dist-info/METADATA +440 -0
- code_to_txt-0.2.0.dist-info/RECORD +9 -0
- {code_to_txt-0.1.0.dist-info → code_to_txt-0.2.0.dist-info}/WHEEL +1 -1
- code_to_txt-0.2.0.dist-info/licenses/LICENSE +21 -0
- code_to_txt-0.1.0.dist-info/METADATA +0 -24
- code_to_txt-0.1.0.dist-info/RECORD +0 -7
- {code_to_txt-0.1.0.dist-info → code_to_txt-0.2.0.dist-info}/entry_points.txt +0 -0
code_to_txt/cli.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
from datetime import datetime
|
|
2
2
|
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import pyperclip
|
|
6
|
+
|
|
3
7
|
from .code_to_txt import CodeToText
|
|
8
|
+
from .config import create_default_config, load_config
|
|
4
9
|
|
|
5
10
|
|
|
6
11
|
@click.command()
|
|
@@ -8,15 +13,15 @@ from .code_to_txt import CodeToText
|
|
|
8
13
|
@click.option(
|
|
9
14
|
"-o",
|
|
10
15
|
"--output",
|
|
11
|
-
default=
|
|
12
|
-
help="Output file path",
|
|
16
|
+
default=None,
|
|
17
|
+
help="Output file path (default: codetotxt_YYYYMMDD_HHMMSS.txt)",
|
|
13
18
|
type=click.Path(),
|
|
14
19
|
)
|
|
15
20
|
@click.option(
|
|
16
21
|
"-e",
|
|
17
22
|
"--extensions",
|
|
18
|
-
|
|
19
|
-
help="File extensions to include (e.g., .py .js)
|
|
23
|
+
default=None,
|
|
24
|
+
help="File extensions to include. Space-separated list (e.g., '.py .js .ts') or comma-separated (e.g., '.py,.js,.ts')",
|
|
20
25
|
)
|
|
21
26
|
@click.option(
|
|
22
27
|
"-x",
|
|
@@ -24,6 +29,12 @@ from .code_to_txt import CodeToText
|
|
|
24
29
|
multiple=True,
|
|
25
30
|
help="Patterns to exclude (gitignore style). Can be specified multiple times.",
|
|
26
31
|
)
|
|
32
|
+
@click.option(
|
|
33
|
+
"-g",
|
|
34
|
+
"--glob",
|
|
35
|
+
multiple=True,
|
|
36
|
+
help="Glob patterns to include (e.g., '*.py' 'src/**/*.js'). Can be specified multiple times.",
|
|
37
|
+
)
|
|
27
38
|
@click.option(
|
|
28
39
|
"--no-gitignore",
|
|
29
40
|
is_flag=True,
|
|
@@ -39,7 +50,48 @@ from .code_to_txt import CodeToText
|
|
|
39
50
|
default="=" * 80,
|
|
40
51
|
help="Separator between files",
|
|
41
52
|
)
|
|
42
|
-
|
|
53
|
+
@click.option(
|
|
54
|
+
"--clipboard",
|
|
55
|
+
"-c",
|
|
56
|
+
is_flag=True,
|
|
57
|
+
help="Copy output to clipboard in addition to saving to file",
|
|
58
|
+
)
|
|
59
|
+
@click.option(
|
|
60
|
+
"--clipboard-only",
|
|
61
|
+
is_flag=True,
|
|
62
|
+
help="Copy output to clipboard only (don't save to file)",
|
|
63
|
+
)
|
|
64
|
+
@click.option(
|
|
65
|
+
"--config",
|
|
66
|
+
type=click.Path(exists=True),
|
|
67
|
+
help="Path to config file (.yml or .yaml)",
|
|
68
|
+
)
|
|
69
|
+
@click.option(
|
|
70
|
+
"--init-config",
|
|
71
|
+
is_flag=True,
|
|
72
|
+
help="Create default configuration file (.code-to-txt.yml)",
|
|
73
|
+
)
|
|
74
|
+
@click.option(
|
|
75
|
+
"--timestamp",
|
|
76
|
+
"-t",
|
|
77
|
+
is_flag=True,
|
|
78
|
+
help="Add timestamp to output filename",
|
|
79
|
+
)
|
|
80
|
+
def main(
|
|
81
|
+
path: str,
|
|
82
|
+
output: str | None,
|
|
83
|
+
extensions: str | None,
|
|
84
|
+
exclude: tuple[str, ...],
|
|
85
|
+
glob: tuple[str, ...],
|
|
86
|
+
no_gitignore: bool,
|
|
87
|
+
no_tree: bool,
|
|
88
|
+
separator: str,
|
|
89
|
+
clipboard: bool,
|
|
90
|
+
clipboard_only: bool,
|
|
91
|
+
config: str | None,
|
|
92
|
+
init_config: bool,
|
|
93
|
+
timestamp: bool,
|
|
94
|
+
) -> None:
|
|
43
95
|
"""
|
|
44
96
|
Convert code files to a single text file for easy LLM consumption.
|
|
45
97
|
|
|
@@ -47,45 +99,126 @@ def main(path, output, extensions, exclude, no_gitignore, no_tree, separator):
|
|
|
47
99
|
|
|
48
100
|
Examples:
|
|
49
101
|
|
|
50
|
-
# Convert all code files in current directory
|
|
51
|
-
code-to-txt
|
|
102
|
+
# Convert all code files in current directory with timestamp
|
|
103
|
+
code-to-txt -t
|
|
52
104
|
|
|
53
105
|
# Convert specific directory to custom output
|
|
54
106
|
code-to-txt ./my-project -o project.txt
|
|
55
107
|
|
|
56
|
-
#
|
|
57
|
-
code-to-txt -e .py
|
|
108
|
+
# Include Python, C, and header files (space or comma separated)
|
|
109
|
+
code-to-txt -e ".py .c .h"
|
|
110
|
+
code-to-txt -e ".py,.c,.h"
|
|
111
|
+
|
|
112
|
+
# Use glob patterns
|
|
113
|
+
code-to-txt -g "*.py" -g "src/**/*.js"
|
|
58
114
|
|
|
59
115
|
# Exclude test files
|
|
60
116
|
code-to-txt -x "tests/*" -x "*.test.js"
|
|
61
117
|
|
|
62
|
-
#
|
|
63
|
-
code-to-txt --
|
|
118
|
+
# Copy to clipboard
|
|
119
|
+
code-to-txt --clipboard
|
|
120
|
+
|
|
121
|
+
# Copy to clipboard only (no file)
|
|
122
|
+
code-to-txt --clipboard-only
|
|
123
|
+
|
|
124
|
+
# Create default config file
|
|
125
|
+
code-to-txt --init-config
|
|
126
|
+
|
|
127
|
+
# Use config file
|
|
128
|
+
code-to-txt --config .code-to-txt.yml
|
|
64
129
|
"""
|
|
130
|
+
if init_config:
|
|
131
|
+
config_path = Path(".code-to-txt.yml")
|
|
132
|
+
if config_path.exists():
|
|
133
|
+
click.confirm(
|
|
134
|
+
f"Config file {config_path} already exists. Overwrite?",
|
|
135
|
+
abort=True,
|
|
136
|
+
)
|
|
137
|
+
create_default_config(config_path)
|
|
138
|
+
click.echo(f"Created default config file: {config_path}")
|
|
139
|
+
click.echo("You can now edit this file and use it with --config flag")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
config_data = {}
|
|
143
|
+
if config:
|
|
144
|
+
config_data = load_config(config)
|
|
145
|
+
click.echo(f"Using config file: {config}")
|
|
146
|
+
|
|
147
|
+
output = output or config_data.get("output")
|
|
148
|
+
extensions = extensions or config_data.get("extensions")
|
|
149
|
+
exclude = exclude or config_data.get("exclude", [])
|
|
150
|
+
glob_patterns = glob or config_data.get("glob", [])
|
|
151
|
+
no_gitignore = no_gitignore or config_data.get("no_gitignore", False)
|
|
152
|
+
no_tree = no_tree or config_data.get("no_tree", False)
|
|
153
|
+
separator = separator if separator == "=" * 80 else separator
|
|
154
|
+
separator = config_data.get("separator", separator)
|
|
155
|
+
clipboard = clipboard or config_data.get("clipboard", False)
|
|
156
|
+
clipboard_only = clipboard_only or config_data.get("clipboard_only", False)
|
|
157
|
+
timestamp = timestamp or config_data.get("timestamp", False)
|
|
158
|
+
|
|
159
|
+
if not output or timestamp:
|
|
160
|
+
timestamp_str = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
161
|
+
if output:
|
|
162
|
+
output_path = Path(output)
|
|
163
|
+
stem = output_path.stem
|
|
164
|
+
suffix = output_path.suffix or ".txt"
|
|
165
|
+
output = str(output_path.parent / f"{stem}_{timestamp_str}{suffix}")
|
|
166
|
+
else:
|
|
167
|
+
output = f"codetotxt_{timestamp_str}.txt"
|
|
168
|
+
|
|
65
169
|
click.echo(f"Scanning: {path}")
|
|
66
170
|
|
|
67
|
-
include_extensions =
|
|
171
|
+
include_extensions = None
|
|
172
|
+
if extensions:
|
|
173
|
+
if "," in extensions:
|
|
174
|
+
ext_list = [e.strip() for e in extensions.split(",")]
|
|
175
|
+
else:
|
|
176
|
+
ext_list = extensions.split()
|
|
177
|
+
|
|
178
|
+
include_extensions = set()
|
|
179
|
+
for ext in ext_list:
|
|
180
|
+
ext = ext.strip()
|
|
181
|
+
if ext and not ext.startswith("."):
|
|
182
|
+
ext = "." + ext
|
|
183
|
+
if ext:
|
|
184
|
+
include_extensions.add(ext)
|
|
68
185
|
|
|
69
186
|
codetotxt = CodeToText(
|
|
70
187
|
root_path=path,
|
|
71
|
-
output_file=output,
|
|
188
|
+
output_file=output if not clipboard_only else None,
|
|
72
189
|
include_extensions=include_extensions,
|
|
73
190
|
exclude_patterns=list(exclude),
|
|
191
|
+
glob_patterns=list(glob_patterns),
|
|
74
192
|
gitignore=not no_gitignore,
|
|
75
193
|
)
|
|
76
194
|
|
|
77
195
|
try:
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
196
|
+
if clipboard_only:
|
|
197
|
+
content = codetotxt.generate_content(
|
|
198
|
+
add_tree=not no_tree,
|
|
199
|
+
separator=separator,
|
|
200
|
+
)
|
|
201
|
+
pyperclip.copy(content)
|
|
202
|
+
click.echo("Content copied to clipboard")
|
|
203
|
+
click.echo(f"Processed {codetotxt.file_count} files")
|
|
204
|
+
click.echo(f"Content size: {len(content) / 1024:.2f} KB")
|
|
205
|
+
else:
|
|
206
|
+
num_files = codetotxt.convert(
|
|
207
|
+
add_tree=not no_tree,
|
|
208
|
+
separator=separator,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
output_path = Path(output).resolve()
|
|
212
|
+
click.echo(f"Successfully processed {num_files} files")
|
|
213
|
+
click.echo(f"Output saved to: {output_path}")
|
|
214
|
+
|
|
215
|
+
size_kb = output_path.stat().st_size / 1024
|
|
216
|
+
click.echo(f"File size: {size_kb:.2f} KB")
|
|
217
|
+
|
|
218
|
+
if clipboard:
|
|
219
|
+
content = output_path.read_text(encoding="utf-8")
|
|
220
|
+
pyperclip.copy(content)
|
|
221
|
+
click.echo("Content also copied to clipboard")
|
|
89
222
|
|
|
90
223
|
except Exception as e:
|
|
91
224
|
click.echo(f"Error: {e}", err=True)
|
code_to_txt/code_to_txt.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from fnmatch import fnmatch
|
|
2
3
|
from pathlib import Path
|
|
3
|
-
from typing import
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
4
6
|
import pathspec
|
|
7
|
+
from pathspec import PathSpec
|
|
5
8
|
|
|
6
9
|
|
|
7
10
|
class CodeToText:
|
|
@@ -9,19 +12,43 @@ class CodeToText:
|
|
|
9
12
|
"__pycache__",
|
|
10
13
|
"*.pyc",
|
|
11
14
|
"*.pyo",
|
|
12
|
-
"*.pyd"
|
|
15
|
+
"*.pyd",
|
|
16
|
+
".git",
|
|
17
|
+
".svn",
|
|
18
|
+
".hg",
|
|
19
|
+
"node_modules",
|
|
20
|
+
".venv",
|
|
21
|
+
"venv",
|
|
22
|
+
".env",
|
|
23
|
+
"*.egg-info",
|
|
24
|
+
"dist",
|
|
25
|
+
"build",
|
|
26
|
+
".pytest_cache",
|
|
27
|
+
".mypy_cache",
|
|
28
|
+
".ruff_cache",
|
|
29
|
+
"*.so",
|
|
30
|
+
"*.dylib",
|
|
31
|
+
"*.dll",
|
|
13
32
|
}
|
|
14
33
|
|
|
15
34
|
DEFAULT_EXTENSIONS = {
|
|
16
|
-
".py", ".js", ".ts", ".jsx", ".tsx",
|
|
35
|
+
".py", ".js", ".ts", ".jsx", ".tsx",
|
|
36
|
+
".java", ".c", ".cpp", ".h", ".hpp",
|
|
37
|
+
".cs", ".go", ".rs", ".rb", ".php",
|
|
38
|
+
".swift", ".kt", ".scala", ".r",
|
|
39
|
+
".sql", ".sh", ".bash", ".zsh",
|
|
40
|
+
".yaml", ".yml", ".json", ".toml",
|
|
41
|
+
".xml", ".html", ".css", ".scss",
|
|
42
|
+
".md", ".txt", ".rst",
|
|
17
43
|
}
|
|
18
44
|
|
|
19
45
|
def __init__(
|
|
20
46
|
self,
|
|
21
47
|
root_path: str,
|
|
22
|
-
output_file: str = "output.txt",
|
|
23
|
-
include_extensions:
|
|
24
|
-
exclude_patterns:
|
|
48
|
+
output_file: str | None = "output.txt",
|
|
49
|
+
include_extensions: set[str] | None = None,
|
|
50
|
+
exclude_patterns: list[str] | None = None,
|
|
51
|
+
glob_patterns: list[str] | None = None,
|
|
25
52
|
gitignore: bool = True,
|
|
26
53
|
):
|
|
27
54
|
"""
|
|
@@ -29,28 +56,31 @@ class CodeToText:
|
|
|
29
56
|
|
|
30
57
|
Args:
|
|
31
58
|
root_path: Root directory to scan
|
|
32
|
-
output_file: Output file path
|
|
59
|
+
output_file: Output file path (None for clipboard-only mode)
|
|
33
60
|
include_extensions: Set of file extensions to include (with dots)
|
|
34
61
|
exclude_patterns: List of patterns to exclude (gitignore style)
|
|
62
|
+
glob_patterns: List of glob patterns to include (e.g., '*.py', 'src/**/*.js')
|
|
35
63
|
gitignore: Whether to respect .gitignore files
|
|
36
64
|
"""
|
|
37
65
|
self.root_path = Path(root_path).resolve()
|
|
38
66
|
self.output_file = output_file
|
|
39
67
|
self.include_extensions = include_extensions or self.DEFAULT_EXTENSIONS
|
|
40
68
|
self.exclude_patterns = exclude_patterns or []
|
|
69
|
+
self.glob_patterns = glob_patterns or []
|
|
41
70
|
self.gitignore = gitignore
|
|
42
|
-
self.spec = None
|
|
71
|
+
self.spec: PathSpec | None = None
|
|
72
|
+
self.file_count = 0
|
|
43
73
|
|
|
44
74
|
if self.gitignore:
|
|
45
75
|
self._load_gitignore()
|
|
46
76
|
|
|
47
|
-
def _load_gitignore(self):
|
|
77
|
+
def _load_gitignore(self) -> None:
|
|
48
78
|
"""Load .gitignore patterns if present."""
|
|
49
79
|
gitignore_path = self.root_path / ".gitignore"
|
|
50
80
|
patterns = list(self.DEFAULT_IGNORE)
|
|
51
81
|
|
|
52
82
|
if gitignore_path.exists():
|
|
53
|
-
with open(gitignore_path, "
|
|
83
|
+
with open(gitignore_path, encoding="utf-8") as f:
|
|
54
84
|
for line in f:
|
|
55
85
|
line = line.strip()
|
|
56
86
|
if line and not line.startswith("#"):
|
|
@@ -59,11 +89,30 @@ class CodeToText:
|
|
|
59
89
|
patterns.extend(self.exclude_patterns)
|
|
60
90
|
self.spec = pathspec.PathSpec.from_lines("gitignore", patterns)
|
|
61
91
|
|
|
92
|
+
def _matches_glob_pattern(self, file_path: Path) -> bool:
|
|
93
|
+
"""Check if file matches any glob pattern."""
|
|
94
|
+
if not self.glob_patterns:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
relative_path = file_path.relative_to(self.root_path)
|
|
98
|
+
relative_str = str(relative_path)
|
|
99
|
+
|
|
100
|
+
for pattern in self.glob_patterns:
|
|
101
|
+
if fnmatch(relative_str, pattern):
|
|
102
|
+
return True
|
|
103
|
+
if fnmatch(file_path.name, pattern):
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
return False
|
|
107
|
+
|
|
62
108
|
def _should_include_file(self, file_path: Path) -> bool:
|
|
63
109
|
"""Check if a file should be included."""
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
110
|
+
if self.glob_patterns:
|
|
111
|
+
if not self._matches_glob_pattern(file_path):
|
|
112
|
+
return False
|
|
113
|
+
else:
|
|
114
|
+
if file_path.suffix not in self.include_extensions:
|
|
115
|
+
return False
|
|
67
116
|
|
|
68
117
|
if self.spec:
|
|
69
118
|
relative_path = file_path.relative_to(self.root_path)
|
|
@@ -72,7 +121,7 @@ class CodeToText:
|
|
|
72
121
|
|
|
73
122
|
return True
|
|
74
123
|
|
|
75
|
-
def _get_files(self) ->
|
|
124
|
+
def _get_files(self) -> list[Path]:
|
|
76
125
|
"""Get all files to process."""
|
|
77
126
|
files = []
|
|
78
127
|
for root, dirs, filenames in os.walk(self.root_path):
|
|
@@ -92,55 +141,85 @@ class CodeToText:
|
|
|
92
141
|
|
|
93
142
|
return sorted(files)
|
|
94
143
|
|
|
95
|
-
def
|
|
144
|
+
def generate_content(self, add_tree: bool = True, separator: str = "=" * 80) -> str:
|
|
96
145
|
"""
|
|
97
|
-
|
|
146
|
+
Generate content as string (for clipboard).
|
|
98
147
|
|
|
99
148
|
Args:
|
|
100
149
|
add_tree: Whether to add directory tree at the beginning
|
|
101
150
|
separator: Separator between files
|
|
102
151
|
|
|
103
152
|
Returns:
|
|
104
|
-
|
|
153
|
+
Generated content as string
|
|
105
154
|
"""
|
|
106
155
|
files = self._get_files()
|
|
156
|
+
self.file_count = len(files)
|
|
157
|
+
|
|
158
|
+
lines = []
|
|
159
|
+
lines.append(f"Code Export from: {self.root_path}")
|
|
160
|
+
lines.append(f"Total files: {len(files)}")
|
|
161
|
+
lines.append(separator)
|
|
162
|
+
lines.append("")
|
|
163
|
+
|
|
164
|
+
if add_tree:
|
|
165
|
+
lines.append("DIRECTORY TREE:")
|
|
166
|
+
lines.append(separator)
|
|
167
|
+
lines.append(self._generate_tree())
|
|
168
|
+
lines.append("")
|
|
169
|
+
lines.append(separator)
|
|
170
|
+
lines.append("")
|
|
171
|
+
|
|
172
|
+
for i, file_path in enumerate(files, 1):
|
|
173
|
+
relative_path = file_path.relative_to(self.root_path)
|
|
107
174
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
out.write(f"Total files: {len(files)}\n")
|
|
111
|
-
out.write(f"{separator}\n\n")
|
|
175
|
+
lines.append(f"FILE {i}/{len(files)}: {relative_path}")
|
|
176
|
+
lines.append(separator)
|
|
112
177
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
178
|
+
try:
|
|
179
|
+
with open(file_path, encoding="utf-8") as f:
|
|
180
|
+
content = f.read()
|
|
181
|
+
lines.append(content)
|
|
182
|
+
except UnicodeDecodeError:
|
|
183
|
+
lines.append("[Binary file - skipped]")
|
|
184
|
+
except Exception as e:
|
|
185
|
+
lines.append(f"[Error reading file: {e}]")
|
|
118
186
|
|
|
119
|
-
|
|
120
|
-
|
|
187
|
+
lines.append("")
|
|
188
|
+
lines.append(separator)
|
|
189
|
+
lines.append("")
|
|
121
190
|
|
|
122
|
-
|
|
123
|
-
out.write(separator + "\n")
|
|
191
|
+
return "\n".join(lines)
|
|
124
192
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
out.write(f"[Error reading file: {e}]\n")
|
|
193
|
+
def convert(self, add_tree: bool = True, separator: str = "=" * 80) -> int:
|
|
194
|
+
"""
|
|
195
|
+
Convert files to single text file.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
add_tree: Whether to add directory tree at the beginning
|
|
199
|
+
separator: Separator between files
|
|
133
200
|
|
|
134
|
-
|
|
201
|
+
Returns:
|
|
202
|
+
Number of files processed
|
|
203
|
+
"""
|
|
204
|
+
if not self.output_file:
|
|
205
|
+
raise ValueError("output_file must be specified for convert()")
|
|
135
206
|
|
|
136
|
-
|
|
207
|
+
content = self.generate_content(add_tree=add_tree, separator=separator)
|
|
208
|
+
|
|
209
|
+
with open(self.output_file, "w", encoding="utf-8") as out:
|
|
210
|
+
out.write(content)
|
|
211
|
+
|
|
212
|
+
return self.file_count
|
|
137
213
|
|
|
138
214
|
def _generate_tree(self) -> str:
|
|
139
215
|
"""Generate a directory tree representation."""
|
|
140
216
|
tree_lines = []
|
|
141
217
|
files = self._get_files()
|
|
142
218
|
|
|
143
|
-
|
|
219
|
+
if not files:
|
|
220
|
+
return "(no files to display)"
|
|
221
|
+
|
|
222
|
+
dir_structure: dict[str, Any] = {}
|
|
144
223
|
for file_path in files:
|
|
145
224
|
relative_path = file_path.relative_to(self.root_path)
|
|
146
225
|
parts = relative_path.parts
|
|
@@ -155,7 +234,7 @@ class CodeToText:
|
|
|
155
234
|
current["__files__"] = []
|
|
156
235
|
current["__files__"].append(parts[-1])
|
|
157
236
|
|
|
158
|
-
def print_tree(structure, prefix="", is_last=True):
|
|
237
|
+
def print_tree(structure: dict[str, Any], prefix: str = "", is_last: bool = True) -> None:
|
|
159
238
|
items = []
|
|
160
239
|
for key in sorted(structure.keys()):
|
|
161
240
|
if key != "__files__":
|
code_to_txt/config.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
DEFAULT_CONFIG = {
|
|
7
|
+
"output": "code_output.txt",
|
|
8
|
+
"extensions": None, # None means use defaults
|
|
9
|
+
"exclude": [
|
|
10
|
+
"tests/*",
|
|
11
|
+
"*.test.js",
|
|
12
|
+
"*.test.ts",
|
|
13
|
+
"*.spec.js",
|
|
14
|
+
"*.spec.ts",
|
|
15
|
+
],
|
|
16
|
+
"glob": [], # e.g., ["*.py", "src/**/*.js"]
|
|
17
|
+
"no_gitignore": False,
|
|
18
|
+
"no_tree": False,
|
|
19
|
+
"separator": "=" * 80,
|
|
20
|
+
"clipboard": False,
|
|
21
|
+
"clipboard_only": False,
|
|
22
|
+
"timestamp": False,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def load_config(config_path: str) -> dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Load configuration from YAML file.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config_path: Path to the configuration file
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dictionary with configuration values
|
|
35
|
+
"""
|
|
36
|
+
path = Path(config_path)
|
|
37
|
+
|
|
38
|
+
if not path.exists():
|
|
39
|
+
raise FileNotFoundError(f"Config file not found: {config_path}")
|
|
40
|
+
|
|
41
|
+
with open(path, encoding="utf-8") as f:
|
|
42
|
+
config = yaml.safe_load(f)
|
|
43
|
+
|
|
44
|
+
if config is None:
|
|
45
|
+
config = {}
|
|
46
|
+
|
|
47
|
+
validated_config: dict[str, Any] = {}
|
|
48
|
+
|
|
49
|
+
if "output" in config:
|
|
50
|
+
validated_config["output"] = str(config["output"])
|
|
51
|
+
|
|
52
|
+
if "separator" in config:
|
|
53
|
+
validated_config["separator"] = str(config["separator"])
|
|
54
|
+
|
|
55
|
+
if "extensions" in config:
|
|
56
|
+
ext = config["extensions"]
|
|
57
|
+
if isinstance(ext, str):
|
|
58
|
+
validated_config["extensions"] = ext
|
|
59
|
+
elif isinstance(ext, list):
|
|
60
|
+
validated_config["extensions"] = " ".join(str(e) for e in ext)
|
|
61
|
+
elif ext is not None:
|
|
62
|
+
validated_config["extensions"] = str(ext)
|
|
63
|
+
|
|
64
|
+
for field in ["exclude", "glob"]:
|
|
65
|
+
if field in config:
|
|
66
|
+
value = config[field]
|
|
67
|
+
if isinstance(value, list):
|
|
68
|
+
validated_config[field] = value
|
|
69
|
+
elif isinstance(value, str):
|
|
70
|
+
validated_config[field] = [value]
|
|
71
|
+
elif value is not None:
|
|
72
|
+
validated_config[field] = [str(value)]
|
|
73
|
+
|
|
74
|
+
for field in ["no_gitignore", "no_tree", "clipboard", "clipboard_only", "timestamp"]:
|
|
75
|
+
if field in config:
|
|
76
|
+
validated_config[field] = bool(config[field])
|
|
77
|
+
|
|
78
|
+
return validated_config
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def create_default_config(config_path: Path) -> None:
|
|
82
|
+
"""
|
|
83
|
+
Create a default configuration file.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
config_path: Path where to create the config file
|
|
87
|
+
"""
|
|
88
|
+
config_content = """# Code-to-Txt Configuration File
|
|
89
|
+
# This file defines default settings for code-to-txt
|
|
90
|
+
# CLI arguments will override these settings
|
|
91
|
+
|
|
92
|
+
# Output file name (supports strftime formatting)
|
|
93
|
+
# Use timestamp: true to automatically add timestamp
|
|
94
|
+
output: code_output.txt
|
|
95
|
+
|
|
96
|
+
# File extensions to include
|
|
97
|
+
# Can be a list or space/comma-separated string
|
|
98
|
+
# Leave as null to use default extensions
|
|
99
|
+
# extensions: [.py, .js, .ts]
|
|
100
|
+
# extensions: ".py .js .ts"
|
|
101
|
+
extensions: null
|
|
102
|
+
|
|
103
|
+
# Patterns to exclude (gitignore-style)
|
|
104
|
+
# These are in addition to .gitignore patterns
|
|
105
|
+
exclude:
|
|
106
|
+
- "tests/*"
|
|
107
|
+
- "*.test.js"
|
|
108
|
+
- "*.test.ts"
|
|
109
|
+
- "*.spec.js"
|
|
110
|
+
- "*.spec.ts"
|
|
111
|
+
- "node_modules/*"
|
|
112
|
+
- "__pycache__/*"
|
|
113
|
+
- "*.pyc"
|
|
114
|
+
|
|
115
|
+
# Glob patterns to include (alternative to extensions)
|
|
116
|
+
# If specified, only files matching these patterns will be included
|
|
117
|
+
# glob:
|
|
118
|
+
# - "*.py"
|
|
119
|
+
# - "src/**/*.js"
|
|
120
|
+
# - "**/*.tsx"
|
|
121
|
+
glob: []
|
|
122
|
+
|
|
123
|
+
# Ignore .gitignore files
|
|
124
|
+
no_gitignore: false
|
|
125
|
+
|
|
126
|
+
# Don't include directory tree in output
|
|
127
|
+
no_tree: false
|
|
128
|
+
|
|
129
|
+
# Separator between files
|
|
130
|
+
separator: "================================================================================"
|
|
131
|
+
|
|
132
|
+
# Copy output to clipboard
|
|
133
|
+
clipboard: false
|
|
134
|
+
|
|
135
|
+
# Copy to clipboard only (don't save file)
|
|
136
|
+
clipboard_only: false
|
|
137
|
+
|
|
138
|
+
# Add timestamp to output filename
|
|
139
|
+
timestamp: false
|
|
140
|
+
|
|
141
|
+
# Example configurations:
|
|
142
|
+
#
|
|
143
|
+
# For Python projects:
|
|
144
|
+
# extensions: [.py]
|
|
145
|
+
# exclude: ["tests/*", "*.pyc", "__pycache__/*", "venv/*"]
|
|
146
|
+
#
|
|
147
|
+
# For JavaScript/TypeScript projects:
|
|
148
|
+
# extensions: [.js, .ts, .jsx, .tsx]
|
|
149
|
+
# exclude: ["node_modules/*", "dist/*", "build/*", "*.test.js"]
|
|
150
|
+
#
|
|
151
|
+
# For C/C++ projects:
|
|
152
|
+
# extensions: [.c, .cpp, .h, .hpp]
|
|
153
|
+
# exclude: ["build/*", "*.o", "*.a"]
|
|
154
|
+
#
|
|
155
|
+
# Using glob patterns:
|
|
156
|
+
# glob: ["src/**/*.py", "lib/**/*.py", "*.md"]
|
|
157
|
+
# extensions: null
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
161
|
+
f.write(config_content)
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: code-to-txt
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Convert code files to a single text file for LLM consumption
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Andrii Sonsiadlo
|
|
8
|
+
Author-email: andrii.sonsiadlo@gmail.com
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: click (>=8.3.1,<9.0.0)
|
|
18
|
+
Requires-Dist: gitpython (>=3.1.46,<4.0.0)
|
|
19
|
+
Requires-Dist: pathspec (>=1.0.4,<2.0.0)
|
|
20
|
+
Requires-Dist: pyperclip (>=1.8.2,<2.0.0)
|
|
21
|
+
Requires-Dist: pyyaml (>=6.0.0,<7.0.0)
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# CodeToTxt
|
|
25
|
+
|
|
26
|
+
A powerful Python package to convert code files into a single text file, perfect for feeding into Large Language
|
|
27
|
+
Models (LLMs) or for easy code review and documentation.
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
✨ **New in v0.2.0:**
|
|
32
|
+
|
|
33
|
+
- 🕐 **Automatic timestamps** in output filenames
|
|
34
|
+
- 📋 **Clipboard support** - copy output directly to clipboard
|
|
35
|
+
- 🎯 **Better extension handling** - specify multiple extensions without repeating `-e` flag
|
|
36
|
+
- 🔍 **Glob pattern support** - use patterns like `*.py` or `src/**/*.js`
|
|
37
|
+
- ⚙️ **Configuration file support** - save your preferences in `.code-to-txt.yml`
|
|
38
|
+
- 🚀 **Enhanced defaults** - more file types and ignore patterns out of the box
|
|
39
|
+
|
|
40
|
+
**Core Features:**
|
|
41
|
+
|
|
42
|
+
- 📁 Convert entire directories of code into a single text file
|
|
43
|
+
- 🌳 Optional directory tree visualization
|
|
44
|
+
- 🚫 Respects `.gitignore` patterns automatically
|
|
45
|
+
- 🎨 Customizable file separators and output format
|
|
46
|
+
- 🔧 Flexible file filtering by extension or glob patterns
|
|
47
|
+
- 📦 Easy to use CLI and Python API
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install code-to-txt
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Or with Poetry:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
poetry add code-to-txt
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
### Basic Usage
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Convert all code files in current directory with timestamp
|
|
67
|
+
code-to-txt -t
|
|
68
|
+
|
|
69
|
+
# Convert specific directory
|
|
70
|
+
code-to-txt ./my-project -o project.txt
|
|
71
|
+
|
|
72
|
+
# Copy to clipboard instead of saving
|
|
73
|
+
code-to-txt --clipboard-only
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Specify File Types
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Multiple extensions (space or comma separated)
|
|
80
|
+
code-to-txt -e ".py .js .ts"
|
|
81
|
+
code-to-txt -e ".py,.js,.ts"
|
|
82
|
+
|
|
83
|
+
# Using glob patterns
|
|
84
|
+
code-to-txt -g "*.py" -g "src/**/*.js"
|
|
85
|
+
code-to-txt -g "*.py" -g "*.md"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Advanced Usage
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Exclude patterns
|
|
92
|
+
code-to-txt -x "tests/*" -x "*.test.js"
|
|
93
|
+
|
|
94
|
+
# Don't use .gitignore
|
|
95
|
+
code-to-txt --no-gitignore
|
|
96
|
+
|
|
97
|
+
# Don't show directory tree
|
|
98
|
+
code-to-txt --no-tree
|
|
99
|
+
|
|
100
|
+
# Custom separator
|
|
101
|
+
code-to-txt --separator "---"
|
|
102
|
+
|
|
103
|
+
# Combine options
|
|
104
|
+
code-to-txt -t -c -e ".py .js" -x "tests/*"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Configuration File
|
|
108
|
+
|
|
109
|
+
Create a default configuration file:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
code-to-txt --init-config
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This creates `.code-to-txt.yml` with default settings:
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
# Output file name
|
|
119
|
+
output: codetotxt.txt
|
|
120
|
+
|
|
121
|
+
# File extensions to include (null = use defaults)
|
|
122
|
+
extensions: null
|
|
123
|
+
|
|
124
|
+
# Patterns to exclude
|
|
125
|
+
exclude:
|
|
126
|
+
- "tests/*"
|
|
127
|
+
- "*.test.js"
|
|
128
|
+
- "node_modules/*"
|
|
129
|
+
|
|
130
|
+
# Glob patterns (alternative to extensions)
|
|
131
|
+
glob: [ ]
|
|
132
|
+
|
|
133
|
+
# Options
|
|
134
|
+
no_gitignore: false
|
|
135
|
+
no_tree: false
|
|
136
|
+
separator: "================"
|
|
137
|
+
clipboard: false
|
|
138
|
+
clipboard_only: false
|
|
139
|
+
timestamp: false
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use the config file:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
code-to-txt --config .code-to-txt.yml
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Note:** CLI arguments override config file settings.
|
|
149
|
+
|
|
150
|
+
### Example Configurations
|
|
151
|
+
|
|
152
|
+
**Python Project:**
|
|
153
|
+
|
|
154
|
+
```yaml
|
|
155
|
+
extensions: [ .py ]
|
|
156
|
+
exclude: [ "tests/*", "*.pyc", "__pycache__/*", "venv/*", ".venv/*" ]
|
|
157
|
+
timestamp: true
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**JavaScript/TypeScript Project:**
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
extensions: [ .js, .ts, .jsx, .tsx ]
|
|
164
|
+
exclude: [ "node_modules/*", "dist/*", "build/*", "*.test.js", "*.spec.ts" ]
|
|
165
|
+
no_tree: false
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**C/C++ Project:**
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
extensions: [ .c, .cpp, .h, .hpp ]
|
|
172
|
+
exclude: [ "build/*", "*.o", "*.a", "cmake-build-*" ]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Using Glob Patterns:**
|
|
176
|
+
|
|
177
|
+
```yaml
|
|
178
|
+
glob: [ "src/**/*.py", "lib/**/*.py", "*.md" ]
|
|
179
|
+
extensions: null # Ignore extensions when using glob
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Command Line Options
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
Usage: code-to-txt [OPTIONS] [PATH]
|
|
186
|
+
|
|
187
|
+
Arguments:
|
|
188
|
+
PATH Directory to scan (default: current directory)
|
|
189
|
+
|
|
190
|
+
Options:
|
|
191
|
+
-o, --output PATH Output file path (default: codetotxt_YYYYMMDD_HHMMSS.txt)
|
|
192
|
+
-e, --extensions TEXT File extensions to include (space or comma separated)
|
|
193
|
+
-x, --exclude TEXT Patterns to exclude (can be used multiple times)
|
|
194
|
+
-g, --glob TEXT Glob patterns to include (can be used multiple times)
|
|
195
|
+
--no-gitignore Don't respect .gitignore files
|
|
196
|
+
--no-tree Don't include directory tree in output
|
|
197
|
+
--separator TEXT Separator between files (default: ====...)
|
|
198
|
+
-c, --clipboard Copy output to clipboard in addition to file
|
|
199
|
+
--clipboard-only Copy to clipboard only (don't save file)
|
|
200
|
+
--config PATH Path to config file (.yml or .yaml)
|
|
201
|
+
--init-config Create default configuration file
|
|
202
|
+
-t, --timestamp Add timestamp to output filename
|
|
203
|
+
--help Show this message and exit
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Python API
|
|
207
|
+
|
|
208
|
+
### Basic Usage
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from code_to_txt import CodeToText
|
|
212
|
+
|
|
213
|
+
# Create instance
|
|
214
|
+
code_to_text = CodeToText(
|
|
215
|
+
root_path="./my-project",
|
|
216
|
+
output_file="output.txt",
|
|
217
|
+
include_extensions={".py", ".js"},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Convert to file
|
|
221
|
+
num_files = code_to_text.convert(add_tree=True)
|
|
222
|
+
print(f"Processed {num_files} files")
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Generate Content for Clipboard
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from code_to_txt import CodeToText
|
|
229
|
+
|
|
230
|
+
# Generate content without writing to file
|
|
231
|
+
code_to_text = CodeToText(
|
|
232
|
+
root_path="./my-project",
|
|
233
|
+
output_file=None, # No file needed
|
|
234
|
+
include_extensions={".py"},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
content = code_to_text.generate_content(add_tree=True)
|
|
238
|
+
print(f"Generated {len(content)} characters")
|
|
239
|
+
|
|
240
|
+
# Copy to clipboard using pyperclip
|
|
241
|
+
import pyperclip
|
|
242
|
+
|
|
243
|
+
pyperclip.copy(content)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Using Glob Patterns
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from code_to_txt import CodeToText
|
|
250
|
+
|
|
251
|
+
code_to_text = CodeToText(
|
|
252
|
+
root_path="./my-project",
|
|
253
|
+
output_file="output.txt",
|
|
254
|
+
glob_patterns=["*.py", "src/**/*.js", "**/*.md"],
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
num_files = code_to_text.convert()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Advanced Configuration
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
from code_to_txt import CodeToText
|
|
264
|
+
|
|
265
|
+
code_to_text = CodeToText(
|
|
266
|
+
root_path="./my-project",
|
|
267
|
+
output_file="detailed_output.txt",
|
|
268
|
+
include_extensions={".py", ".js", ".ts"},
|
|
269
|
+
exclude_patterns=["tests/*", "*.test.js", "node_modules/*"],
|
|
270
|
+
gitignore=True, # Respect .gitignore (default)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
num_files = code_to_text.convert(
|
|
274
|
+
add_tree=True,
|
|
275
|
+
separator="=" * 100,
|
|
276
|
+
)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Default File Extensions
|
|
280
|
+
|
|
281
|
+
When no extensions are specified, CodeToTxt includes these file types by default:
|
|
282
|
+
|
|
283
|
+
- **Python:** `.py`
|
|
284
|
+
- **JavaScript/TypeScript:** `.js`, `.ts`, `.jsx`, `.tsx`
|
|
285
|
+
- **Systems:** `.c`, `.cpp`, `.h`, `.hpp`, `.java`, `.cs`, `.go`, `.rs`
|
|
286
|
+
- **Web:** `.html`, `.css`, `.scss`
|
|
287
|
+
- **Config:** `.yaml`, `.yml`, `.json`, `.toml`, `.xml`
|
|
288
|
+
- **Documentation:** `.md`, `.txt`, `.rst`
|
|
289
|
+
- **Scripts:** `.sh`, `.bash`, `.zsh`
|
|
290
|
+
- **Other:** `.rb`, `.php`, `.swift`, `.kt`, `.scala`, `.r`, `.sql`
|
|
291
|
+
|
|
292
|
+
## Default Ignore Patterns
|
|
293
|
+
|
|
294
|
+
CodeToTxt automatically ignores common build artifacts and dependencies:
|
|
295
|
+
|
|
296
|
+
- `__pycache__`, `*.pyc`, `*.pyo`, `*.pyd`
|
|
297
|
+
- `.git`, `.svn`, `.hg`
|
|
298
|
+
- `node_modules`
|
|
299
|
+
- `.venv`, `venv`, `.env`
|
|
300
|
+
- `*.egg-info`, `dist`, `build`
|
|
301
|
+
- `.pytest_cache`, `.mypy_cache`, `.ruff_cache`
|
|
302
|
+
- `*.so`, `*.dylib`, `*.dll`
|
|
303
|
+
|
|
304
|
+
Plus any patterns in your `.gitignore` file.
|
|
305
|
+
|
|
306
|
+
## Output Format
|
|
307
|
+
|
|
308
|
+
The generated file includes:
|
|
309
|
+
|
|
310
|
+
1. **Header:** Source directory and file count
|
|
311
|
+
2. **Directory Tree:** Visual representation of the file structure (optional)
|
|
312
|
+
3. **File Contents:** Each file with its relative path and content
|
|
313
|
+
|
|
314
|
+
Example output:
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
Code Export from: /path/to/project
|
|
318
|
+
Total files: 4
|
|
319
|
+
================================================================================
|
|
320
|
+
|
|
321
|
+
DIRECTORY TREE:
|
|
322
|
+
================================================================================
|
|
323
|
+
my-project/
|
|
324
|
+
├── src/
|
|
325
|
+
│ ├── main.py
|
|
326
|
+
│ └── utils.py
|
|
327
|
+
├── tests/
|
|
328
|
+
│ └── test_main.py
|
|
329
|
+
└── README.md
|
|
330
|
+
|
|
331
|
+
================================================================================
|
|
332
|
+
|
|
333
|
+
FILE 1/4: src/main.py
|
|
334
|
+
================================================================================
|
|
335
|
+
def main():
|
|
336
|
+
print("Hello, World!")
|
|
337
|
+
|
|
338
|
+
if __name__ == "__main__":
|
|
339
|
+
main()
|
|
340
|
+
|
|
341
|
+
================================================================================
|
|
342
|
+
...
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Use Cases
|
|
346
|
+
|
|
347
|
+
- 📚 **Code Review:** Share entire codebase in a single file
|
|
348
|
+
- 🤖 **LLM Input:** Feed code to ChatGPT, Claude, or other AI assistants
|
|
349
|
+
- 📖 **Documentation:** Create comprehensive code documentation
|
|
350
|
+
- 🔍 **Code Search:** Easy text-based search across entire project
|
|
351
|
+
- 📊 **Analysis:** Input for code analysis tools
|
|
352
|
+
- 💾 **Archival:** Simple code backup format
|
|
353
|
+
|
|
354
|
+
## Tips & Tricks
|
|
355
|
+
|
|
356
|
+
### For Large Projects
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
# Use specific extensions to reduce size
|
|
360
|
+
code-to-txt -e ".py" -t
|
|
361
|
+
|
|
362
|
+
# Exclude heavy directories
|
|
363
|
+
code-to-txt -x "node_modules/*" -x "venv/*" -x "dist/*"
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### For LLM Consumption
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# Copy directly to clipboard for pasting into ChatGPT/Claude
|
|
370
|
+
code-to-txt --clipboard-only -e ".py .md"
|
|
371
|
+
|
|
372
|
+
# Or save and copy
|
|
373
|
+
code-to-txt -t -c -e ".py .js"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### For Specific Features
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
# Only include source files, exclude tests
|
|
380
|
+
code-to-txt -g "src/**/*.py" -g "lib/**/*.py"
|
|
381
|
+
|
|
382
|
+
# Only documentation
|
|
383
|
+
code-to-txt -e ".md .rst .txt"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## Requirements
|
|
387
|
+
|
|
388
|
+
- Python 3.10+
|
|
389
|
+
- Dependencies: `click`, `gitpython`, `pathspec`, `pyperclip`, `pyyaml`
|
|
390
|
+
|
|
391
|
+
## Development
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
# Clone repository
|
|
395
|
+
git clone https://github.com/AndriiSonsiadlo/code-to-txt.git
|
|
396
|
+
cd code-to-txt
|
|
397
|
+
|
|
398
|
+
# Install with Poetry
|
|
399
|
+
poetry install
|
|
400
|
+
|
|
401
|
+
# Run tests
|
|
402
|
+
poetry run pytest
|
|
403
|
+
|
|
404
|
+
# Run linting
|
|
405
|
+
poetry run ruff check .
|
|
406
|
+
poetry run mypy src/
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Contributing
|
|
410
|
+
|
|
411
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
412
|
+
|
|
413
|
+
## License
|
|
414
|
+
|
|
415
|
+
MIT License - see LICENSE file for details.
|
|
416
|
+
|
|
417
|
+
## Changelog
|
|
418
|
+
|
|
419
|
+
### v0.2.0
|
|
420
|
+
|
|
421
|
+
- ✨ Added automatic timestamp generation for output files
|
|
422
|
+
- 📋 Added clipboard support (`--clipboard` and `--clipboard-only`)
|
|
423
|
+
- 🎯 Improved extension handling (space/comma separated)
|
|
424
|
+
- 🔍 Added glob pattern support
|
|
425
|
+
- ⚙️ Added configuration file support (`.code-to-txt.yml`)
|
|
426
|
+
- 🚀 Expanded default file extensions and ignore patterns
|
|
427
|
+
- 🐛 Various bug fixes and improvements
|
|
428
|
+
|
|
429
|
+
### v0.1.0
|
|
430
|
+
|
|
431
|
+
- 🎉 Initial release
|
|
432
|
+
- 📁 Basic directory to text conversion
|
|
433
|
+
- 🌳 Directory tree generation
|
|
434
|
+
- 🚫 .gitignore support
|
|
435
|
+
- 🎨 Customizable separators
|
|
436
|
+
|
|
437
|
+
## Acknowledgments
|
|
438
|
+
|
|
439
|
+
Created by Andrii Sonsiadlo
|
|
440
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
code_to_txt/__init__.py,sha256=0_iks7Uz24B1pc-Na1n8C97vgMms6haaFNqIRkpq_Cg,62
|
|
2
|
+
code_to_txt/cli.py,sha256=5NEXWGts1JBSXpAWsgrAfz9O0YFHzu6uRPf4NUePOj0,6662
|
|
3
|
+
code_to_txt/code_to_txt.py,sha256=ZCqc-Yk-hc5AexDIGaphCe_2Ck3LhfVeQP8-pDVRCec,8417
|
|
4
|
+
code_to_txt/config.py,sha256=KMlpeKO0F8YRbEmlXMnCs_PrR3iYQNTYOgZISZfCzVU,4148
|
|
5
|
+
code_to_txt-0.2.0.dist-info/METADATA,sha256=AS-XxI1i8Au96Y1_y04nhgY2U6A8whMEshnGHUfHNgc,10519
|
|
6
|
+
code_to_txt-0.2.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
|
|
7
|
+
code_to_txt-0.2.0.dist-info/entry_points.txt,sha256=jPT0g_nryiuAd0E496deFZAhdscNLXiUmUdD3KGN3iA,52
|
|
8
|
+
code_to_txt-0.2.0.dist-info/licenses/LICENSE,sha256=-K4fNS51V7AiwILLB_InW4EECFSbFrrOBd66OqVVyh4,1068
|
|
9
|
+
code_to_txt-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Andrii Sonsiadlo
|
|
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,24 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: code-to-txt
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Convert code files to a single text file for LLM consumption
|
|
5
|
-
License: MIT
|
|
6
|
-
Author: Andrii Sonsiadlo
|
|
7
|
-
Author-email: andrii.sonsiadlo@gmail.com
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
-
Requires-Dist: click (>=8.3.1,<9.0.0)
|
|
17
|
-
Requires-Dist: gitpython (>=3.1.46,<4.0.0)
|
|
18
|
-
Requires-Dist: pathspec (>=1.0.4,<2.0.0)
|
|
19
|
-
Description-Content-Type: text/markdown
|
|
20
|
-
|
|
21
|
-
# CodeToTxt
|
|
22
|
-
|
|
23
|
-
A small Python package to convert code to text.
|
|
24
|
-
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
code_to_txt/__init__.py,sha256=0_iks7Uz24B1pc-Na1n8C97vgMms6haaFNqIRkpq_Cg,62
|
|
2
|
-
code_to_txt/cli.py,sha256=EEm87GRTgk1YorSfoLl2IR2NUjG0f_fkgEQ5YnkxpQ8,2409
|
|
3
|
-
code_to_txt/code_to_txt.py,sha256=HKXiTXMUxM612f7SVHcoxsra6uK6SVV6_-UY49jtR2w,6089
|
|
4
|
-
code_to_txt-0.1.0.dist-info/METADATA,sha256=QEbEcYTZonOO00fLkq3EFuIWBgaGWQLsxnGV3UY1O7I,807
|
|
5
|
-
code_to_txt-0.1.0.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
6
|
-
code_to_txt-0.1.0.dist-info/entry_points.txt,sha256=jPT0g_nryiuAd0E496deFZAhdscNLXiUmUdD3KGN3iA,52
|
|
7
|
-
code_to_txt-0.1.0.dist-info/RECORD,,
|
|
File without changes
|