33Classes:
44 IO: IO tool class. It will process the input and output files.
55"""
6+
67from __future__ import absolute_import
78import os
89import re
10+ import signal
911import subprocess
1012import tempfile
11- from typing import Union , overload , Optional
13+ from typing import Union , overload , Optional , List , cast
1214from io import IOBase
1315from . import log
1416from .utils import list_like , make_unicode
@@ -18,34 +20,39 @@ class IO:
1820 """IO tool class. It will process the input and output files."""
1921
2022 @overload
21- def __init__ (self ,
22- input_file : Optional [Union [IOBase , str , int ]] = None ,
23- output_file : Optional [Union [IOBase , str , int ]] = None ,
24- data_id : Optional [int ] = None ,
25- disable_output : bool = False ,
26- make_dirs : bool = False ):
23+ def __init__ (
24+ self ,
25+ input_file : Optional [Union [IOBase , str , int ]] = None ,
26+ output_file : Optional [Union [IOBase , str , int ]] = None ,
27+ data_id : Optional [int ] = None ,
28+ disable_output : bool = False ,
29+ make_dirs : bool = False ,
30+ ):
2731 ...
2832
2933 @overload
30- def __init__ (self ,
31- data_id : Optional [int ] = None ,
32- file_prefix : Optional [str ] = None ,
33- input_suffix : str = '.in' ,
34- output_suffix : str = '.out' ,
35- disable_output : bool = False ,
36- make_dirs : bool = False ):
34+ def __init__ (
35+ self ,
36+ data_id : Optional [int ] = None ,
37+ file_prefix : Optional [str ] = None ,
38+ input_suffix : str = ".in" ,
39+ output_suffix : str = ".out" ,
40+ disable_output : bool = False ,
41+ make_dirs : bool = False ,
42+ ):
3743 ...
3844
3945 def __init__ ( # type: ignore
40- self ,
41- input_file : Optional [Union [IOBase , str , int ]] = None ,
42- output_file : Optional [Union [IOBase , str , int ]] = None ,
43- data_id : Optional [int ] = None ,
44- file_prefix : Optional [str ] = None ,
45- input_suffix : str = '.in' ,
46- output_suffix : str = '.out' ,
47- disable_output : bool = False ,
48- make_dirs : bool = False ):
46+ self ,
47+ input_file : Optional [Union [IOBase , str , int ]] = None ,
48+ output_file : Optional [Union [IOBase , str , int ]] = None ,
49+ data_id : Optional [int ] = None ,
50+ file_prefix : Optional [str ] = None ,
51+ input_suffix : str = ".in" ,
52+ output_suffix : str = ".out" ,
53+ disable_output : bool = False ,
54+ make_dirs : bool = False ,
55+ ):
4956 """
5057 Args:
5158 input_file (optional): input file object or filename or file descriptor.
@@ -84,12 +91,13 @@ def __init__( # type: ignore
8491 # if the dir "./io" not found it will be created
8592 """
8693 self .__closed = False
87- self .input_file , self .output_file = None , None
94+ self .input_file = cast (IOBase , None )
95+ self .output_file = None
8896 if file_prefix is not None :
8997 # legacy mode
90- input_file = ' {}{{}}{}' .format (self .__escape_format (file_prefix ),
98+ input_file = " {}{{}}{}" .format (self .__escape_format (file_prefix ),
9199 self .__escape_format (input_suffix ))
92- output_file = ' {}{{}}{}' .format (
100+ output_file = " {}{{}}{}" .format (
93101 self .__escape_format (file_prefix ),
94102 self .__escape_format (output_suffix ))
95103 self .input_filename , self .output_filename = None , None
@@ -101,9 +109,13 @@ def __init__( # type: ignore
101109 self .output_file = None
102110 self .is_first_char = {}
103111
104- def __init_file (self , f : Union [IOBase , str , int ,
105- None ], data_id : Union [int , None ],
106- file_type : str , make_dirs : bool ):
112+ def __init_file (
113+ self ,
114+ f : Union [IOBase , str , int , None ],
115+ data_id : Union [int , None ],
116+ file_type : str ,
117+ make_dirs : bool ,
118+ ):
107119 if isinstance (f , IOBase ):
108120 # consider ``f`` as a file object
109121 if file_type == "i" :
@@ -112,8 +124,12 @@ def __init_file(self, f: Union[IOBase, str, int,
112124 self .output_file = f
113125 elif isinstance (f , int ):
114126 # consider ``f`` as a file descor
115- self .__init_file (open (f , 'w+' , encoding = "utf-8" , newline = '\n ' ),
116- data_id , file_type , make_dirs )
127+ self .__init_file (
128+ open (f , "w+" , encoding = "utf-8" , newline = "\n " ),
129+ data_id ,
130+ file_type ,
131+ make_dirs ,
132+ )
117133 elif f is None :
118134 # consider wanna temp file
119135 fd , self .input_filename = tempfile .mkstemp ()
@@ -133,8 +149,11 @@ def __init_file(self, f: Union[IOBase, str, int,
133149 else :
134150 self .output_filename = filename
135151 self .__init_file (
136- open (filename , 'w+' , newline = '\n ' , encoding = 'utf-8' ), data_id ,
137- file_type , make_dirs )
152+ open (filename , "w+" , newline = "\n " , encoding = "utf-8" ),
153+ data_id ,
154+ file_type ,
155+ make_dirs ,
156+ )
138157
139158 def __escape_format (self , st : str ):
140159 """replace "{}" to "{{}}" """
@@ -211,6 +230,15 @@ def __clear(self, file: IOBase, pos: int = 0):
211230 self .is_first_char [file ] = True
212231 file .seek (pos )
213232
233+ @staticmethod
234+ def _kill_process_and_children (proc : subprocess .Popen ):
235+ if os .name == "posix" :
236+ os .killpg (os .getpgid (proc .pid ), signal .SIGKILL )
237+ elif os .name == "nt" :
238+ os .system (f"TASKKILL /F /T /PID { proc .pid } > nul" )
239+ else :
240+ proc .kill () # Not currently supported
241+
214242 def input_write (self , * args , ** kwargs ):
215243 """
216244 Write every element in *args into the input file. Splits with `separator`.
@@ -243,38 +271,49 @@ def input_clear_content(self, pos: int = 0):
243271
244272 self .__clear (self .input_file , pos )
245273
246- def output_gen (self , shell_cmd , time_limit = None ):
274+ def output_gen (self ,
275+ shell_cmd : Union [str , List [str ]],
276+ time_limit : Optional [float ] = None ,
277+ * ,
278+ replace_EOL : bool = True ):
247279 """
248280 Run the command `shell_cmd` (usually the std program) and send it the input file as stdin.
249281 Write its output to the output file.
250282 Args:
251283 shell_cmd: the command to run, usually the std program.
252284 time_limit: the time limit (seconds) of the command to run.
253285 None means infinity. Defaults to None.
286+ replace_EOL: Set whether to replace the end-of-line sequence with `'\\ n'`.
287+ Defaults to True.
254288 """
255289 if self .output_file is None :
256290 raise ValueError ("Output file is disabled" )
257291 self .flush_buffer ()
258292 origin_pos = self .input_file .tell ()
259293 self .input_file .seek (0 )
260- if time_limit is not None :
261- subprocess .check_call (
262- shell_cmd ,
263- shell = True ,
264- timeout = time_limit ,
265- stdin = self .input_file .fileno (),
266- stdout = self .output_file .fileno (),
267- universal_newlines = True ,
268- )
294+
295+ proc = subprocess .Popen (
296+ shell_cmd ,
297+ shell = True ,
298+ stdin = self .input_file .fileno (),
299+ stdout = subprocess .PIPE ,
300+ universal_newlines = replace_EOL ,
301+ preexec_fn = os .setsid if os .name == "posix" else None ,
302+ )
303+
304+ try :
305+ output , _ = proc .communicate (timeout = time_limit )
306+ except subprocess .TimeoutExpired :
307+ # proc.kill() # didn't work because `shell=True`.
308+ self ._kill_process_and_children (proc )
309+ raise
269310 else :
270- subprocess .check_call (
271- shell_cmd ,
272- shell = True ,
273- stdin = self .input_file .fileno (),
274- stdout = self .output_file .fileno (),
275- universal_newlines = True ,
276- )
277- self .input_file .seek (origin_pos )
311+ if replace_EOL :
312+ self .output_file .write (output )
313+ else :
314+ os .write (self .output_file .fileno (), output )
315+ finally :
316+ self .input_file .seek (origin_pos )
278317
279318 log .debug (self .output_filename , " done" )
280319
@@ -309,6 +348,8 @@ def output_clear_content(self, pos: int = 0):
309348 Args:
310349 pos: Where file will truncate
311350 """
351+ if self .output_file is None :
352+ raise ValueError ("Output file is disabled" )
312353 self .__clear (self .output_file , pos )
313354
314355 def flush_buffer (self ):
0 commit comments