compact-code 0.1.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.
- compact_code/__init__.py +7 -0
- compact_code/cli.py +153 -0
- compact_code/compactor.py +118 -0
- compact_code/expand.py +38 -0
- compact_code/tokens.py +29 -0
- compact_code-0.1.0.dist-info/METADATA +76 -0
- compact_code-0.1.0.dist-info/RECORD +10 -0
- compact_code-0.1.0.dist-info/WHEEL +4 -0
- compact_code-0.1.0.dist-info/entry_points.txt +2 -0
- compact_code-0.1.0.dist-info/licenses/LICENSE +21 -0
compact_code/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""compact-code: dense Python for AI agents, readable view for humans."""
|
|
2
|
+
|
|
3
|
+
from .compactor import compact_source, verify_equivalence
|
|
4
|
+
from .expand import expand_source
|
|
5
|
+
|
|
6
|
+
__version__ = "0.1.0"
|
|
7
|
+
__all__ = ["compact_source", "verify_equivalence", "expand_source", "__version__"]
|
compact_code/cli.py
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""compact-code CLI: compact / expand / stats."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import glob
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from . import compactor, expand, tokens
|
|
10
|
+
|
|
11
|
+
MANIFEST = ".compact-code.json"
|
|
12
|
+
EXCLUDED_DIRS = {
|
|
13
|
+
".git", ".venv", "venv", "__pycache__", "site-packages",
|
|
14
|
+
".tox", ".mypy_cache", ".pytest_cache", "node_modules", ".eggs",
|
|
15
|
+
}
|
|
16
|
+
DEFAULT_PRICE_PER_MTOK = 3.0 # USD, input tokens
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _iter_files(patterns):
|
|
20
|
+
if not patterns:
|
|
21
|
+
patterns = ["**/*.py"]
|
|
22
|
+
seen = set()
|
|
23
|
+
for pattern in patterns:
|
|
24
|
+
if os.path.isdir(pattern):
|
|
25
|
+
pattern = os.path.join(pattern, "**", "*.py")
|
|
26
|
+
for path in glob.glob(pattern, recursive=True):
|
|
27
|
+
path = os.path.normpath(path)
|
|
28
|
+
parts = set(path.replace("\\", "/").split("/"))
|
|
29
|
+
if parts & EXCLUDED_DIRS or path in seen or not path.endswith(".py"):
|
|
30
|
+
continue
|
|
31
|
+
seen.add(path)
|
|
32
|
+
yield path
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _load_manifest():
|
|
36
|
+
if os.path.exists(MANIFEST):
|
|
37
|
+
with open(MANIFEST, encoding="utf-8") as f:
|
|
38
|
+
return json.load(f)
|
|
39
|
+
return {"files": {}}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _save_manifest(manifest):
|
|
43
|
+
with open(MANIFEST, "w", encoding="utf-8", newline="\n") as f:
|
|
44
|
+
json.dump(manifest, f, indent=1)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def cmd_compact(args):
|
|
48
|
+
manifest = _load_manifest()
|
|
49
|
+
changed = skipped = before_total = after_total = 0
|
|
50
|
+
for path in _iter_files(args.paths):
|
|
51
|
+
source = open(path, encoding="utf-8").read()
|
|
52
|
+
keep_module_doc = os.path.basename(path) == "__init__.py"
|
|
53
|
+
try:
|
|
54
|
+
out = compactor.compact_source(
|
|
55
|
+
source,
|
|
56
|
+
keep_docstrings=args.keep_docstrings,
|
|
57
|
+
keep_module_docstring=keep_module_doc,
|
|
58
|
+
)
|
|
59
|
+
except SyntaxError as e:
|
|
60
|
+
print(f"SKIP {path}: {e}")
|
|
61
|
+
skipped += 1
|
|
62
|
+
continue
|
|
63
|
+
if not compactor.verify_equivalence(source, out):
|
|
64
|
+
print(f"SKIP {path}: equivalence check failed (please report this bug)")
|
|
65
|
+
skipped += 1
|
|
66
|
+
continue
|
|
67
|
+
before, after = tokens.count(source), tokens.count(out)
|
|
68
|
+
before_total += before
|
|
69
|
+
after_total += after
|
|
70
|
+
if out != source:
|
|
71
|
+
if not args.check:
|
|
72
|
+
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
|
73
|
+
f.write(out)
|
|
74
|
+
manifest["files"][path.replace("\\", "/")] = {
|
|
75
|
+
"before": before, "after": after,
|
|
76
|
+
}
|
|
77
|
+
changed += 1
|
|
78
|
+
if not args.check and changed:
|
|
79
|
+
_save_manifest(manifest)
|
|
80
|
+
saved = before_total - after_total
|
|
81
|
+
pct = saved / before_total * 100 if before_total else 0
|
|
82
|
+
verb = "would be compacted" if args.check else "compacted"
|
|
83
|
+
approx = "" if tokens.is_exact() else " (approx., install tiktoken for exact counts)"
|
|
84
|
+
print(f"{changed} files {verb}, {skipped} skipped")
|
|
85
|
+
print(f"{before_total:,} -> {after_total:,} tokens ({pct:.1f}% smaller){approx}")
|
|
86
|
+
if not args.check and changed:
|
|
87
|
+
print("Now run your test suite. If anything fails, restore (git) and retry "
|
|
88
|
+
"with --keep-docstrings (some docstrings are functional: CLI help, doctests).")
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def cmd_expand(args):
|
|
93
|
+
source = open(args.file, encoding="utf-8").read()
|
|
94
|
+
out = expand.expand_source(source)
|
|
95
|
+
if args.write:
|
|
96
|
+
with open(args.file, "w", encoding="utf-8", newline="\n") as f:
|
|
97
|
+
f.write(out)
|
|
98
|
+
print(f"{args.file} expanded in place")
|
|
99
|
+
else:
|
|
100
|
+
sys.stdout.write(out)
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def cmd_stats(args):
|
|
105
|
+
manifest = _load_manifest()
|
|
106
|
+
files = manifest["files"]
|
|
107
|
+
if not files:
|
|
108
|
+
print("No compaction recorded here yet. Run `compact-code compact` first.")
|
|
109
|
+
return 1
|
|
110
|
+
before = sum(f["before"] for f in files.values())
|
|
111
|
+
after = sum(f["after"] for f in files.values())
|
|
112
|
+
saved = before - after
|
|
113
|
+
pct = saved / before * 100 if before else 0
|
|
114
|
+
dollars = saved / 1_000_000 * args.price
|
|
115
|
+
print(f"Codebase: {before:,} -> {after:,} tokens ({pct:.1f}% smaller, {len(files)} files)")
|
|
116
|
+
print(f"Every full read of this codebase by your agent now saves "
|
|
117
|
+
f"{saved:,} tokens (~${dollars:.2f} at ${args.price}/Mtok input).")
|
|
118
|
+
print("Sessions that read a lot of code (exploration, onboarding, refactors) "
|
|
119
|
+
"benefit the most; surgical one-file fixes benefit little.")
|
|
120
|
+
return 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main(argv=None):
|
|
124
|
+
parser = argparse.ArgumentParser(
|
|
125
|
+
prog="compact-code",
|
|
126
|
+
description="Dense Python for AI agents, readable view for humans. "
|
|
127
|
+
"Same logic, fewer tokens.",
|
|
128
|
+
)
|
|
129
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
130
|
+
|
|
131
|
+
p = sub.add_parser("compact", help="compact Python files in place (AST-safe)")
|
|
132
|
+
p.add_argument("paths", nargs="*", help="files, dirs or globs (default: **/*.py)")
|
|
133
|
+
p.add_argument("--keep-docstrings", action="store_true",
|
|
134
|
+
help="keep docstrings (use when they are functional: CLI help, doctests)")
|
|
135
|
+
p.add_argument("--check", action="store_true", help="dry run, report savings only")
|
|
136
|
+
p.set_defaults(func=cmd_compact)
|
|
137
|
+
|
|
138
|
+
p = sub.add_parser("expand", help="print a readable view of a compacted file")
|
|
139
|
+
p.add_argument("file")
|
|
140
|
+
p.add_argument("--write", action="store_true", help="rewrite the file expanded")
|
|
141
|
+
p.set_defaults(func=cmd_expand)
|
|
142
|
+
|
|
143
|
+
p = sub.add_parser("stats", help="show tokens (and $) saved in this project")
|
|
144
|
+
p.add_argument("--price", type=float, default=DEFAULT_PRICE_PER_MTOK,
|
|
145
|
+
help="USD per million input tokens (default: 3.0)")
|
|
146
|
+
p.set_defaults(func=cmd_stats)
|
|
147
|
+
|
|
148
|
+
args = parser.parse_args(argv)
|
|
149
|
+
return args.func(args)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Deterministic AST-based Python compactor.
|
|
2
|
+
|
|
3
|
+
Removes comments, docstrings and type annotations (except functional
|
|
4
|
+
class-field annotations), strips blank lines, and re-indents to 1 space.
|
|
5
|
+
The program's AST is preserved: same logic, fewer tokens.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import io
|
|
10
|
+
import tokenize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Transformer(ast.NodeTransformer):
|
|
14
|
+
def __init__(self, keep_docstrings=False):
|
|
15
|
+
self.keep_docstrings = keep_docstrings
|
|
16
|
+
|
|
17
|
+
def _strip_doc(self, node):
|
|
18
|
+
body = node.body
|
|
19
|
+
if (
|
|
20
|
+
not self.keep_docstrings
|
|
21
|
+
and not getattr(node, "_keep_doc", False)
|
|
22
|
+
and body
|
|
23
|
+
and isinstance(body[0], ast.Expr)
|
|
24
|
+
and isinstance(body[0].value, ast.Constant)
|
|
25
|
+
and isinstance(body[0].value.value, str)
|
|
26
|
+
):
|
|
27
|
+
body = body[1:] or [ast.Pass()]
|
|
28
|
+
node.body = [self.visit(stmt) for stmt in body]
|
|
29
|
+
return node
|
|
30
|
+
|
|
31
|
+
def visit_Module(self, node):
|
|
32
|
+
return self._strip_doc(node)
|
|
33
|
+
|
|
34
|
+
def visit_ClassDef(self, node):
|
|
35
|
+
return self._strip_doc(node)
|
|
36
|
+
|
|
37
|
+
def visit_FunctionDef(self, node):
|
|
38
|
+
return self._function(node)
|
|
39
|
+
|
|
40
|
+
def visit_AsyncFunctionDef(self, node):
|
|
41
|
+
return self._function(node)
|
|
42
|
+
|
|
43
|
+
def _function(self, node):
|
|
44
|
+
for arg in node.args.args + node.args.posonlyargs + node.args.kwonlyargs:
|
|
45
|
+
arg.annotation = None
|
|
46
|
+
if node.args.vararg:
|
|
47
|
+
node.args.vararg.annotation = None
|
|
48
|
+
if node.args.kwarg:
|
|
49
|
+
node.args.kwarg.annotation = None
|
|
50
|
+
node.returns = None
|
|
51
|
+
return self._strip_doc(node)
|
|
52
|
+
|
|
53
|
+
def visit_AnnAssign(self, node):
|
|
54
|
+
self.generic_visit(node)
|
|
55
|
+
# Bare class-field annotations are functional (dataclasses, NamedTuple,
|
|
56
|
+
# TypedDict, pydantic): keep them.
|
|
57
|
+
if node.value is None:
|
|
58
|
+
return node
|
|
59
|
+
if getattr(node, "_in_class", False):
|
|
60
|
+
return node
|
|
61
|
+
return ast.Assign(targets=[node.target], value=node.value)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _mark_class_fields(tree):
|
|
65
|
+
for node in ast.walk(tree):
|
|
66
|
+
if isinstance(node, ast.ClassDef):
|
|
67
|
+
for stmt in node.body:
|
|
68
|
+
if isinstance(stmt, ast.AnnAssign):
|
|
69
|
+
stmt._in_class = True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _protected_lines(source):
|
|
73
|
+
"""Line numbers interior to multi-line string tokens: their content must
|
|
74
|
+
not be re-indented or blank-stripped."""
|
|
75
|
+
protected = set()
|
|
76
|
+
for tok in tokenize.generate_tokens(io.StringIO(source).readline):
|
|
77
|
+
if tok.type == tokenize.STRING and tok.end[0] > tok.start[0]:
|
|
78
|
+
protected.update(range(tok.start[0] + 1, tok.end[0] + 1))
|
|
79
|
+
return protected
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def compact_source(source, keep_docstrings=False, keep_module_docstring=False):
|
|
83
|
+
"""Return the compacted version of *source*.
|
|
84
|
+
|
|
85
|
+
Raises SyntaxError if *source* is not valid Python.
|
|
86
|
+
"""
|
|
87
|
+
tree = ast.parse(source)
|
|
88
|
+
_mark_class_fields(tree)
|
|
89
|
+
if keep_module_docstring:
|
|
90
|
+
tree._keep_doc = True
|
|
91
|
+
tree = _Transformer(keep_docstrings).visit(tree)
|
|
92
|
+
ast.fix_missing_locations(tree)
|
|
93
|
+
out = ast.unparse(tree)
|
|
94
|
+
protected = _protected_lines(out)
|
|
95
|
+
lines = []
|
|
96
|
+
for lineno, line in enumerate(out.split("\n"), 1):
|
|
97
|
+
if lineno in protected:
|
|
98
|
+
lines.append(line)
|
|
99
|
+
continue
|
|
100
|
+
if not line.strip():
|
|
101
|
+
continue
|
|
102
|
+
indent = len(line) - len(line.lstrip())
|
|
103
|
+
lines.append(" " * (indent // 4) + line.lstrip())
|
|
104
|
+
return "\n".join(lines) + "\n"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _normalized_dump(source):
|
|
108
|
+
"""AST dump with docstrings/annotations stripped — the equivalence
|
|
109
|
+
invariant shared by original and compacted code."""
|
|
110
|
+
tree = ast.parse(source)
|
|
111
|
+
_mark_class_fields(tree)
|
|
112
|
+
tree = _Transformer(keep_docstrings=False).visit(tree)
|
|
113
|
+
return ast.dump(tree)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def verify_equivalence(original, compacted):
|
|
117
|
+
"""True if *compacted* preserves the (normalized) AST of *original*."""
|
|
118
|
+
return _normalized_dump(original) == _normalized_dump(compacted)
|
compact_code/expand.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Deterministic readable view of compacted code: 4-space indentation and
|
|
2
|
+
blank lines between definitions. Instant, no LLM."""
|
|
3
|
+
|
|
4
|
+
import io
|
|
5
|
+
import tokenize
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _protected_lines(source):
|
|
9
|
+
protected = set()
|
|
10
|
+
for tok in tokenize.generate_tokens(io.StringIO(source).readline):
|
|
11
|
+
if tok.type == tokenize.STRING and tok.end[0] > tok.start[0]:
|
|
12
|
+
protected.update(range(tok.start[0] + 1, tok.end[0] + 1))
|
|
13
|
+
return protected
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def expand_source(source):
|
|
17
|
+
"""Return a human-readable view of *source* (does not modify logic)."""
|
|
18
|
+
protected = _protected_lines(source)
|
|
19
|
+
out = []
|
|
20
|
+
previous_indent = 0
|
|
21
|
+
for lineno, line in enumerate(source.split("\n"), 1):
|
|
22
|
+
if lineno in protected:
|
|
23
|
+
out.append(line)
|
|
24
|
+
continue
|
|
25
|
+
stripped = line.lstrip()
|
|
26
|
+
indent = len(line) - len(stripped)
|
|
27
|
+
if stripped.startswith(("def ", "async def ", "class ", "@")) and out:
|
|
28
|
+
# blank line before a definition, two before a top-level one
|
|
29
|
+
blanks = 2 if indent == 0 else 1
|
|
30
|
+
if not stripped.startswith("@") or previous_indent != indent:
|
|
31
|
+
out.extend([""] * blanks)
|
|
32
|
+
out.append(" " * indent + stripped)
|
|
33
|
+
if stripped:
|
|
34
|
+
previous_indent = indent
|
|
35
|
+
text = "\n".join(out)
|
|
36
|
+
while "\n\n\n\n" in text:
|
|
37
|
+
text = text.replace("\n\n\n\n", "\n\n\n")
|
|
38
|
+
return text if text.endswith("\n") else text + "\n"
|
compact_code/tokens.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Token counting: tiktoken (o200k_base) if available, chars/4 heuristic otherwise."""
|
|
2
|
+
|
|
3
|
+
_encoder = None
|
|
4
|
+
_exact = None
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _setup():
|
|
8
|
+
global _encoder, _exact
|
|
9
|
+
if _exact is not None:
|
|
10
|
+
return
|
|
11
|
+
try:
|
|
12
|
+
import tiktoken
|
|
13
|
+
|
|
14
|
+
_encoder = tiktoken.get_encoding("o200k_base")
|
|
15
|
+
_exact = True
|
|
16
|
+
except Exception:
|
|
17
|
+
_exact = False
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def count(text):
|
|
21
|
+
_setup()
|
|
22
|
+
if _exact:
|
|
23
|
+
return len(_encoder.encode(text))
|
|
24
|
+
return len(text) // 4
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_exact():
|
|
28
|
+
_setup()
|
|
29
|
+
return _exact
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: compact-code
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dense Python for AI agents, readable view for humans. Same logic, ~30-55% fewer tokens.
|
|
5
|
+
Author-email: Emmanuel Freund <freund.emmanuel@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: agents,ast,claude,llm,minify,tokens
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Provides-Extra: exact
|
|
16
|
+
Requires-Dist: tiktoken>=0.5; extra == 'exact'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# compact-code
|
|
20
|
+
|
|
21
|
+
**Dense Python for AI agents, readable view for humans. Same logic, fewer tokens.**
|
|
22
|
+
|
|
23
|
+
AI coding agents (Claude Code, Cursor, Codex...) read your code far more often than they write it. Every `Read`, every context refill re-pays the token weight of your code. But your code is written for humans: comments, docstrings, type annotations, blank lines, 4-space indentation — none of which the agent needs to understand the logic.
|
|
24
|
+
|
|
25
|
+
`compact-code` removes all of it, **provably without changing the program** (the AST is preserved), and gives you back a readable view on demand. Think *minified JS + source maps, applied to the AI workflow*.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
pip install compact-code
|
|
29
|
+
cd your-project
|
|
30
|
+
compact-code compact # 3 seconds, AST-safe, reversible via git
|
|
31
|
+
pytest # your tests are the safety net
|
|
32
|
+
compact-code stats # see what you now save on every read
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What it does
|
|
36
|
+
|
|
37
|
+
- removes comments, docstrings and type annotations (keeps functional class-field annotations: dataclasses, NamedTuple, pydantic)
|
|
38
|
+
- strips blank lines, re-indents to 1 space
|
|
39
|
+
- never touches string contents, public names, parameters or logic
|
|
40
|
+
- verifies AST equivalence on every file before writing — if the check fails, the file is skipped
|
|
41
|
+
|
|
42
|
+
## Measured results
|
|
43
|
+
|
|
44
|
+
Benchmarked with twin-codebase experiments (identical prompts, blind agents, real test suites as judges — full methodology in the repo):
|
|
45
|
+
|
|
46
|
+
| Scenario | Session-token savings |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Surgical one-file fix | ~0% (agent barely reads code) |
|
|
49
|
+
| Maintenance on a fully compacted codebase | **-31%** |
|
|
50
|
+
| Codebase exploration / audit (replicated n=3) | **-56%** |
|
|
51
|
+
|
|
52
|
+
Quality: across 16 paired sessions, agents on compacted code passed **exactly the same test suites and evals** as agents on normal code. Zero measured loss.
|
|
53
|
+
|
|
54
|
+
Codebase density gain: -19% to -41% tokens depending on the project (less if docstrings must be kept).
|
|
55
|
+
|
|
56
|
+
## Commands
|
|
57
|
+
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `compact-code compact [paths]` | compact `**/*.py` in place (skips `.venv`, `.git`, etc.) |
|
|
61
|
+
| `compact-code compact --check` | dry run: report savings without writing |
|
|
62
|
+
| `compact-code compact --keep-docstrings` | keep docstrings (use when they are functional: CLI help text, doctests, flit) |
|
|
63
|
+
| `compact-code expand <file>` | print a readable 4-space-indented view (`--write` to rewrite in place) |
|
|
64
|
+
| `compact-code stats` | tokens and $ saved per full codebase read |
|
|
65
|
+
|
|
66
|
+
Install `compact-code[exact]` for exact token counts (tiktoken).
|
|
67
|
+
|
|
68
|
+
## The honest fine print
|
|
69
|
+
|
|
70
|
+
- The gain is proportional to how much code your agent actually **reads**. Exploration-heavy and read-heavy sessions save the most; one-file quick fixes save nothing.
|
|
71
|
+
- Some docstrings are functional (click help text, doctests, flit metadata). Always run your test suite after compacting; on failure, restore via git and re-run with `--keep-docstrings`.
|
|
72
|
+
- Token counts use tiktoken `o200k_base` as a proxy for your model's tokenizer; ratios are indicative.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
compact_code/__init__.py,sha256=CEkh3QhL_zEzkF3auA9n9wWlvLmb7WXJqTsrUnZLacs,273
|
|
2
|
+
compact_code/cli.py,sha256=cxRQL-d4O9ezmthQXFmpYQYISHflGebC6tPPS_fxf34,5664
|
|
3
|
+
compact_code/compactor.py,sha256=3XHnTWXICa0ch-MlVlJP8ixD6ctQC56N6mvrmjCbTFo,3900
|
|
4
|
+
compact_code/expand.py,sha256=bfID5ZtWvHqnXMg9ux37lhAizXQsHdGnZdmvVvzvO3Q,1419
|
|
5
|
+
compact_code/tokens.py,sha256=4bg0d5rpv4bquP-15zKNuAVIiS9gORxQtEwmSt7OpZw,524
|
|
6
|
+
compact_code-0.1.0.dist-info/METADATA,sha256=xygBAUYFE3ecqbPhESZagAXanhGAlBpp8doRcWIgdrE,3666
|
|
7
|
+
compact_code-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
8
|
+
compact_code-0.1.0.dist-info/entry_points.txt,sha256=IZNSoYbRk5atYzZCkbqFenI290C24EOz30U7e19o-wA,55
|
|
9
|
+
compact_code-0.1.0.dist-info/licenses/LICENSE,sha256=N-Rrt7sqXVm_MP6RC2EhrLhUtTrGtluOr7zclo-0who,1072
|
|
10
|
+
compact_code-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emmanuel Freund
|
|
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.
|