22import subprocess as sp
33import 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
1019from ._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"
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
76184if __name__ == "__main__" :
0 commit comments