1- import signal
21from argparse import ArgumentParser
32from shlex import join
43from sys import stderr , stdout
5- from os import getpgid , killpg
64from shutil import copyfileobj
75from subprocess import Popen , PIPE , run
6+ import sys
87from typing import Dict , Any
9- from .utils import check_pid
8+ from .utils import check_pid , filesizepu , kill_pid , tail_bytes , tail_file
109from .main import Main , flag , arg
11-
1210from .procdb import ProcessDB as Manager
1311
1412# from .spawn import Spawn as Manager
1715class FormatDict (dict ):
1816 def __missing__ (self , key : str ) -> str :
1917 if key == "pid?" :
20- return f'{ self ["pid" ]} { "" if check_pid (self ["pid" ]) else "?👻 " } '
18+ return f'{ self ["pid" ]} { "" if check_pid (self ["pid" ]) else "?" } '
2119 elif key == "elapsed" :
2220 import time
2321
@@ -27,7 +25,7 @@ def __missing__(self, key: str) -> str:
2725 return self ["cmd" ]
2826 return join (self ["cmd" ])
2927 elif key == "pid_status" :
30- return "✅ Live " if check_pid (self ["pid" ]) else "❌ Gone "
28+ return "Running " if check_pid (self ["pid" ]) else "Stopped "
3129 raise KeyError (f"No { key !r} " )
3230
3331
@@ -40,11 +38,19 @@ def fn(x: Dict[str, Any]) -> str:
4038
4139
4240def no_record (name ):
43- print (f"🤷 No record of { name !r} " )
41+ try :
42+ print ("🤷 " , end = "" )
43+ except UnicodeEncodeError :
44+ pass
45+ print (f"No record of { name !r} " )
4446
4547
4648def ambiguous (name ):
47- print (f"⁉️ { name !r} is ambiguous" )
49+ try :
50+ print ("⁉️ " , end = "" )
51+ except UnicodeEncodeError :
52+ pass
53+ print (f"{ name !r} is ambiguous" )
4854
4955
5056class Clean (Main ):
@@ -61,7 +67,11 @@ def start(self) -> None:
6167 for d in sp .find_names (self .ids , ambiguous , no_record ):
6268 if check_pid (d ["pid" ]):
6369 continue
64- print (f"🧹 Cleaning { d ['pid' ]} { d ['name' ]} " )
70+ try :
71+ print ("🧹 " , end = "" )
72+ except UnicodeEncodeError :
73+ pass
74+ print (f"Cleaning { d ['pid' ]} { d ['name' ]} " )
6575 sp .drop (d )
6676
6777
@@ -81,6 +91,7 @@ def init_argparse(self, argp: ArgumentParser) -> None:
8191
8292 def start (self ) -> None :
8393 f = format_prep (self .format )
94+ e = ["✅" , "❌" ]
8495 for d in Manager ().find_names (self .ids , ambiguous , no_record ):
8596 print (f (d ))
8697
@@ -91,70 +102,92 @@ class Kill(Main):
91102 ids : list [str ] = arg ("ID" , "run ids" , nargs = "+" )
92103 dry_run : bool = flag ("dry-run" , "dry run (don't actually kill)" , default = False )
93104 remove : bool = flag ("remove" , "remove entry after killing" , default = False )
105+ group : bool = flag ("group" , "kill process group" , default = False )
106+ signal : str = flag ("signal" , "send signal" , default = None )
94107
95108 def init_argparse (self , argp : ArgumentParser ) -> None :
96109 argp .description = "Kill the process of a run id"
97110 return super ().init_argparse (argp )
98111
99112 def start (self ) -> None :
113+ _errdef = ["❌" , "Error" ]
114+ _noproc = ["👻" , "No process" ]
115+ _killed = ["💀" , "Killed" ]
116+ signal = int (self .signal ) if self .signal else None
100117 sp = Manager ()
101118 if self .ids :
102119 for x in sp .find_names (self .ids , ambiguous , no_record ):
103- pref = "❌ Error"
120+ s = _errdef
121+ if self .dry_run :
122+ s = _killed
123+ else :
124+ if check_pid (x ["pid" ]):
125+ if kill_pid (x ["pid" ], process_group = self .group ):
126+ s = _killed
127+ else :
128+ s = _noproc
104129 try :
105- pgid = getpgid (x ["pid" ])
106- if not self .dry_run :
107- killpg (pgid , signal .SIGTERM )
108- pref = "💀 Killed"
109- except ProcessLookupError :
110- pref = "👻 No process"
111- finally :
112- print (f'{ pref } PID={ x ["pid" ]} { x ["name" ]!r} ' )
113- if not self .dry_run and self .remove :
114- sp .drop (x )
130+ print (f"{ s [0 ]} " , end = "" )
131+ except UnicodeEncodeError :
132+ pass
133+ print (f'{ s [1 ]} PID={ x ["pid" ]} { x ["name" ]!r} ' )
134+ if not self .dry_run and self .remove :
135+ sp .drop (x )
136+
137+
138+ def _tail (n : float , u = "" , out = "" , tab = None ):
139+ if u :
140+ stdout .buffer .write (tail_bytes (out , int (n )))
141+ elif n > 0 :
142+ if sys .platform .startswith ("win" ):
143+ cmd = [
144+ "powershell" ,
145+ "-c" ,
146+ f"Get-Content -Tail { int (n )} '{ out } '" ,
147+ ]
148+ else :
149+ cmd = ["tail" , "-n" , str (int (n )), out ]
150+ if tab :
151+ with Popen (cmd , stdout = PIPE ).stdout as o :
152+ for line in o :
153+ stdout .buffer .write (b"\t " + line )
154+ else :
155+ run (cmd )
115156
116157
117158class Tail (Main ):
118159 """Tail process output."""
119160
120161 ids : list [str ] = arg ("ID" , "run ids" , nargs = "*" )
121162 format : str = flag ("header" , "header format" )
122- lines : int = flag ("n" , "lines" , "how many lines" )
163+ lines : str = flag ("n" , "lines" , "how many lines or bytes " )
123164 existing : bool = flag (
124165 "x" , "only-existing" , "only show existing processes" , default = False
125166 )
126167 tab : bool = flag ("t" , "tab" , "prefix tab space" , default = False )
127168 err : bool = flag ("e" , "err" , "output stderr" , default = False )
128- p_open : str = "📜 "
129- p_close : str = ""
169+ p_open : str = "=== "
170+ p_close : str = " === "
130171
131172 def start (self ) -> None :
173+ import sys
174+
132175 if self .format == "no" :
133176 hf = None
134177 else :
135178 hf = format_prep (self .format or r"{pid?}: {name}" )
136- lines = self .lines or 10
179+ n , u = filesizepu ( self .lines or "10" )
137180 j = 0
138181 out = "err" if self .err else "out"
139182
140183 for x in Manager ().find_names (self .ids , ambiguous , no_record ):
141184 if self .existing and not check_pid (x ["pid" ]):
142185 continue
143186
144- j > 1 and lines > 0 and print ()
187+ j > 1 and n > 0 and print ()
145188 if hf :
146189 print (f"{ self .p_open } { hf (x )} { self .p_close } " , flush = True )
147-
148- if lines > 0 :
149- # TODO: pythonify
150- cmd = ["tail" , "-n" , str (lines ), x [out ]]
151- if self .tab :
152- with Popen (cmd , stdout = PIPE ).stdout as o :
153- for line in o :
154- stdout .buffer .write (b"\t " + line )
155- else :
156- run (cmd )
157- stdout .flush ()
190+ _tail (n , u , x [out ], self .tab )
158191 j += 1
159192
160193
@@ -165,11 +198,11 @@ class Run(Main):
165198 tail : int = flag ("t" , "tail" , "tail the output with n lines" , default = 0 )
166199 run_id : str = flag ("id" , "Unique run identifier" , default = "" )
167200 cwd : str = flag ("Working directory for the command" )
168- tail : int = flag (
201+ tail : str = flag (
169202 "t" ,
170203 "tail" ,
171204 "Tail the output (n lines). Use `-t -1` to print the entire output" ,
172- default = 0 ,
205+ # default=0,
173206 )
174207 overwrite : bool = flag ("overwrite" , "Overwrite existing entry" , default = False )
175208 cmd_after : str = flag ("run-after" , "Run command after" , metavar = "command" )
@@ -183,24 +216,25 @@ def start(self) -> None:
183216 # Check for existing process first
184217 e = sp .find_name (name ) if name else None
185218 if e :
186- hf = format_prep (r"🚨 Found: PID={pid} ({pid_status}) {name}" )
187- print (hf (e ), file = stderr )
219+ s = ["🚨" , r"Found: PID={pid} ({pid_status}) {name}" ]
188220 else :
189221 # Start new process
190222 e = sp .spawn (
191223 args , name , overwrite = self .overwrite , cwd = self .cwd , split = self .split
192224 )
193- hf = format_prep ("🚀 Started: PID={pid} ({pid_status}) {name}" )
194- print (hf (e ), file = stderr )
225+ s = ["🚀" , r"Started: PID={pid} ({pid_status}) {name}" ]
195226 assert e
227+ try :
228+ print (f"{ s [0 ]} " , end = "" , file = stderr )
229+ except UnicodeEncodeError :
230+ pass
231+ hf = format_prep (s [1 ])
232+ print (hf (e ), file = stderr , flush = True )
196233
197234 # Handle tail output
198235 if self .tail :
199- if self .tail < 0 :
200- with open (e ["out" ], "rb" ) as f :
201- copyfileobj (f , stdout .buffer )
202- elif self .tail > 0 :
203- run (["tail" , "-n" , str (self .tail ), e ["out" ]])
236+ n , u = filesizepu (self .tail )
237+ _tail (n , u , e ["out" ])
204238
205239 # Run post-command if specified
206240 if self .cmd_after :
@@ -228,7 +262,7 @@ def start(self) -> None:
228262 else :
229263 f = "{pid_status} {elapsed} {pid}\t {name}, {command}"
230264 print ("Status Elapsed PID\t Name, Command" )
231- print ("─────── ──────── ────── ──────────── " )
265+ print ("------- -------- ------ ------------ " )
232266 fp = format_prep (f )
233267 for d in Manager ().all ():
234268 print (fp (d ))
0 commit comments