Skip to content

Commit 23e7971

Browse files
authored
Merge pull request #30 from 15r10nk/new_cli
improved cli output
2 parents ff14230 + 3b05c4a commit 23e7971

File tree

3 files changed

+192
-30
lines changed

3 files changed

+192
-30
lines changed

.github/workflows/ci.yml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
architecture: x64
2020
allow-prereleases: true
2121
- run: pip install hatch
22-
- run: hatch run +py=${{matrix.python-version}} mypy:test
22+
- run: hatch run +py=${{matrix.python-version}} types:check
2323

2424
test:
2525
runs-on: ubuntu-latest
@@ -35,3 +35,52 @@ jobs:
3535
allow-prereleases: true
3636
- run: pip install hatch
3737
- run: hatch test -py ${{matrix.python-version}}
38+
39+
40+
publish:
41+
name: Publish new release
42+
runs-on: ubuntu-latest
43+
needs: [test, mypy]
44+
environment: pypi
45+
permissions:
46+
# IMPORTANT: this permission is mandatory for Trusted Publishing
47+
id-token: write
48+
# this permission is mandatory to create github releases
49+
contents: write
50+
51+
steps:
52+
- name: Checkout main
53+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
54+
with:
55+
fetch-depth: 0
56+
57+
- name: Install uv
58+
uses: astral-sh/setup-uv@v5
59+
with:
60+
python-version: '3.12'
61+
62+
- name: Check if the commit has a vx.y.z tag
63+
id: check-version
64+
run: |
65+
if git tag --list --points-at ${{ github.sha }} | grep -q -E '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
66+
echo "is new version"
67+
echo "should_continue=true" >> "$GITHUB_OUTPUT"
68+
else
69+
echo "is not a new version"
70+
echo "should_continue=false" >> "$GITHUB_OUTPUT"
71+
fi
72+
73+
- run: uv pip install hatch scriv
74+
75+
- name: build package
76+
run: hatch build
77+
78+
- name: Publish package distributions to PyPI
79+
if: ${{ steps.check-version.outputs.should_continue == 'true' }}
80+
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
81+
82+
- name: create github release
83+
if: ${{ steps.check-version.outputs.should_continue == 'true' }}
84+
env:
85+
GITHUB_TOKEN: ${{ github.token }}
86+
run: scriv github-release

pyproject.toml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ classifiers = [
1919
"Programming Language :: Python :: Implementation :: PyPy"
2020
]
2121
dependencies = [
22-
"rich >=12.6.0",
23-
"astunparse >=1.6.3",
24-
"click >=8.1.7",
22+
"astunparse >=1.6.3; python_version < '3.9'",
2523
]
2624
description = 'minimize python source code'
2725
keywords = []
@@ -31,6 +29,13 @@ readme = "README.md"
3129
requires-python = ">=3.8"
3230
version="0.7.0"
3331

32+
[project.optional-dependencies]
33+
cli = [
34+
"rich >=12.6.0",
35+
"click >=8.1.7",
36+
"black>=23.3.0",
37+
]
38+
3439

3540
[project.scripts]
3641
pysource-minimize = "pysource_minimize.__main__:main"
@@ -64,12 +69,12 @@ extra-dependencies = [
6469
python=['3.8',"3.9","3.10","3.11","3.12","3.13"]
6570

6671

67-
[[tool.hatch.envs.mypy.matrix]]
72+
[[tool.hatch.envs.types.matrix]]
6873
python=['3.8',"3.10","3.11","3.12","3.13"]
6974

70-
[tool.hatch.envs.mypy]
71-
extra-dependencies=["mypy"]
72-
scripts.test = ["mypy src"]
75+
[tool.hatch.envs.types]
76+
extra-dependencies=["mypy","pysource-minimize[cli]"]
77+
scripts.check = ["mypy src"]
7378

7479
[tool.hatch.envs.docs]
7580
dependencies = [

src/pysource_minimize/__main__.py

Lines changed: 130 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,40 @@
22
import subprocess as sp
33
import sys
44

5-
import click
6-
from rich.console import Console
7-
from rich.progress import Progress
8-
from rich.syntax import Syntax
5+
try:
6+
import click
7+
from black import format_str, FileMode
8+
from rich.console import Console
9+
from rich.live import Live
10+
from rich.progress import Progress
11+
from rich.syntax import Syntax
12+
from rich.prompt import Confirm
13+
from rich.layout import Layout
14+
except ModuleNotFoundError:
15+
print("pysource-minimize can only be used if you installed pysource-minimize[cli]")
16+
exit(1)
17+
918

1019
from ._minimize import minimize
1120

1221

22+
def num_equal_lines(a: str, b: str):
23+
lines_a = a.splitlines()
24+
lines_b = b.splitlines()
25+
start = 0
26+
for line_a, line_b in zip(lines_a, lines_b):
27+
if line_a != line_b:
28+
break
29+
start += 1
30+
31+
end = 0
32+
for line_a, line_b in zip(reversed(lines_a), reversed(lines_b)):
33+
if line_a != line_b:
34+
break
35+
end += 1
36+
return start, end
37+
38+
1339
@click.command()
1440
@click.option(
1541
"--file", required=True, type=click.Path(exists=True), help="file to minimize"
@@ -22,8 +48,15 @@
2248
@click.option(
2349
"write_back", "-w", "--write", is_flag=True, help="write minimized output to file"
2450
)
51+
@click.option(
52+
"format",
53+
"-f",
54+
"--format",
55+
is_flag=True,
56+
help="format the file with black to provide better output for complex files",
57+
)
2558
@click.argument("cmd", nargs=-1)
26-
def main(cmd, file, track, write_back):
59+
def main(cmd, file, track, write_back, format):
2760
file = pathlib.Path(file)
2861

2962
first_result = sp.run(cmd, capture_output=True)
@@ -35,42 +68,117 @@ def main(cmd, file, track, write_back):
3568
)
3669
sys.exit(1)
3770

71+
original_source = file.read_text()
72+
console = Console()
73+
syntax = Syntax(original_source, "python", line_numbers=True)
74+
75+
check_count = 0
76+
77+
last_minimized_code = ""
78+
79+
def refresh():
80+
live.refresh()
81+
3882
def checker(source):
83+
nonlocal last_minimized_code
84+
nonlocal check_count
85+
86+
info = ""
87+
formatted = False
88+
if format:
89+
try:
90+
source = format_str(
91+
source, mode=FileMode(line_length=console.size.width - 5)
92+
)
93+
formatted = True
94+
except:
95+
info = "(formatting failed)"
96+
3997
file.write_text(source)
4098

4199
result = sp.run(cmd, capture_output=True)
100+
check_count += 1
101+
layout["info"].update(f"test {check_count} {info}")
102+
103+
equal_lines_start, equal_lines_end = num_equal_lines(
104+
last_minimized_code, source
105+
)
106+
num_lines = len(last_minimized_code.splitlines())
107+
start_line = equal_lines_start - 2 if num_lines > console.size.height - 2 else 0
42108

43109
if track not in (result.stdout.decode() + result.stderr.decode()):
110+
111+
num_lines = len(last_minimized_code.splitlines())
112+
113+
syntax.highlight_lines = {
114+
n for n in range(equal_lines_start + 1, num_lines - equal_lines_end + 1)
115+
}
116+
117+
syntax.line_range = (start_line, None)
118+
119+
live.refresh()
120+
44121
return False
45122

46-
return True
123+
if source == last_minimized_code:
124+
return True
47125

48-
original_source = file.read_text()
126+
syntax.word_wrap = formatted
49127

50-
with Progress() as progress:
51-
task = progress.add_task("minimize")
128+
progress.update(
129+
task,
130+
completed=len(original_source) - len(source),
131+
total=len(original_source),
132+
)
52133

53-
def update(current, total):
54-
progress.update(task, completed=total - current, total=total)
134+
syntax.highlight_lines = {
135+
n for n in range(equal_lines_start + 1, num_lines - equal_lines_end + 1)
136+
}
55137

56-
new_source = minimize(original_source, checker, progress_callback=update)
138+
if syntax.line_range is None or start_line != syntax.line_range[0]:
139+
syntax.line_range = (start_line, None)
140+
refresh()
57141

58-
if write_back:
59-
file.write_text(new_source)
60-
else:
61-
file.write_text(original_source)
142+
syntax.code = source
62143

63-
console = Console()
144+
num_lines = len(source.splitlines())
145+
146+
syntax.highlight_lines = {
147+
n for n in range(equal_lines_start + 1, num_lines - equal_lines_end + 1)
148+
}
149+
150+
refresh()
151+
152+
last_minimized_code = source
153+
return True
154+
155+
progress = Progress()
156+
layout = Layout()
157+
layout.split_column(
158+
Layout(name="progress", size=1),
159+
Layout(name="info", size=1),
160+
Layout(name="code"),
161+
)
162+
layout["progress"].update(progress)
163+
layout["code"].update(syntax)
164+
layout["info"].update("start testing ...")
165+
166+
with Live(layout, auto_refresh=False, screen=True) as live:
167+
task = progress.add_task("minimize")
168+
169+
new_source = minimize(original_source, checker, retries=2)
64170

65171
console.print()
66172
console.print("The minimized code is:")
67-
console.print(Syntax(new_source, "python", line_numbers=True))
173+
console.print(Syntax(new_source, "python", line_numbers=True, word_wrap=True))
68174
console.print()
69175

70-
if not write_back:
71-
console.print(
72-
"The file is not changed. Use -w to write the minimized version back to the file."
73-
)
176+
if write_back or Confirm.ask(
177+
f"do you want to write the minimized code to {file}?", default=False
178+
):
179+
file.write_text(new_source)
180+
else:
181+
file.write_text(original_source)
74182

75183

76184
if __name__ == "__main__":

0 commit comments

Comments
 (0)