Skip to content

Commit 036d097

Browse files
authored
Add some utility to IO class (#137)
1 parent c92f60d commit 036d097

File tree

2 files changed

+112
-17
lines changed

2 files changed

+112
-17
lines changed

cyaron/io.py

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def __init__(self,
2222
input_file: Optional[Union[IOBase, str, int]] = None,
2323
output_file: Optional[Union[IOBase, str, int]] = None,
2424
data_id: Optional[int] = None,
25-
disable_output: bool = False):
25+
disable_output: bool = False,
26+
make_dirs: bool = False):
2627
...
2728

2829
@overload
@@ -31,7 +32,8 @@ def __init__(self,
3132
file_prefix: Optional[str] = None,
3233
input_suffix: str = '.in',
3334
output_suffix: str = '.out',
34-
disable_output: bool = False):
35+
disable_output: bool = False,
36+
make_dirs: bool = False):
3537
...
3638

3739
def __init__( # type: ignore
@@ -42,20 +44,22 @@ def __init__( # type: ignore
4244
file_prefix: Optional[str] = None,
4345
input_suffix: str = '.in',
4446
output_suffix: str = '.out',
45-
disable_output: bool = False):
47+
disable_output: bool = False,
48+
make_dirs: bool = False):
4649
"""
4750
Args:
48-
input_file (optional): input file object or filename or file descriptor.
51+
input_file (optional): input file object or filename or file descriptor.
4952
If it's None, make a temp file. Defaults to None.
50-
output_file (optional): input file object or filename or file descriptor.
53+
output_file (optional): input file object or filename or file descriptor.
5154
If it's None, make a temp file. Defaults to None.
52-
data_id (optional): the id of the data. It will be add after
55+
data_id (optional): the id of the data. It will be add after
5356
`input_file` and `output_file` when they are str.
5457
If it's None, the file names will not contain the id. Defaults to None.
5558
file_prefix (optional): the prefix for the input and output files. Defaults to None.
5659
input_suffix (optional): the suffix of the input file. Defaults to '.in'.
5760
output_suffix (optional): the suffix of the output file. Defaults to '.out'.
5861
disable_output (optional): set to True to disable output file. Defaults to False.
62+
make_dirs (optional): set to True to create dir if path is not found. Defaults to False.
5963
Examples:
6064
>>> IO("a","b")
6165
# create input file "a" and output file "b"
@@ -75,7 +79,12 @@ def __init__( # type: ignore
7579
# create input file "data2.in" and output file "data2.out"
7680
>>> IO(open('data.in', 'w+'), open('data.out', 'w+'))
7781
# input file "data.in" and output file "data.out"
82+
>>> IO("./io/data.in", "./io/data.out", disable_output = True)
83+
# input file "./io/data.in" and output file "./io/data.out"
84+
# if the dir "./io" not found it will be created
7885
"""
86+
self.__closed = False
87+
self.input_file, self.output_file = None, None
7988
if file_prefix is not None:
8089
# legacy mode
8190
input_file = '{}{{}}{}'.format(self.__escape_format(file_prefix),
@@ -85,16 +94,16 @@ def __init__( # type: ignore
8594
self.__escape_format(output_suffix))
8695
self.input_filename, self.output_filename = None, None
8796
self.__input_temp, self.__output_temp = False, False
88-
self.__init_file(input_file, data_id, "i")
97+
self.__init_file(input_file, data_id, "i", make_dirs)
8998
if not disable_output:
90-
self.__init_file(output_file, data_id, "o")
99+
self.__init_file(output_file, data_id, "o", make_dirs)
91100
else:
92101
self.output_file = None
93-
self.__closed = False
94102
self.is_first_char = {}
95103

96-
def __init_file(self, f: Union[IOBase, str, int, None],
97-
data_id: Union[int, None], file_type: str):
104+
def __init_file(self, f: Union[IOBase, str, int,
105+
None], data_id: Union[int, None],
106+
file_type: str, make_dirs: bool):
98107
if isinstance(f, IOBase):
99108
# consider ``f`` as a file object
100109
if file_type == "i":
@@ -104,30 +113,36 @@ def __init_file(self, f: Union[IOBase, str, int, None],
104113
elif isinstance(f, int):
105114
# consider ``f`` as a file descor
106115
self.__init_file(open(f, 'w+', encoding="utf-8", newline='\n'),
107-
data_id, file_type)
116+
data_id, file_type, make_dirs)
108117
elif f is None:
109118
# consider wanna temp file
110119
fd, self.input_filename = tempfile.mkstemp()
111-
self.__init_file(fd, data_id, file_type)
120+
self.__init_file(fd, data_id, file_type, make_dirs)
112121
if file_type == "i":
113122
self.__input_temp = True
114123
else:
115124
self.__output_temp = True
116125
else:
117126
# consider ``f`` as filename template
118127
filename = f.format(data_id or "")
128+
# be sure dir is existed
129+
if make_dirs:
130+
self.__make_dirs(filename)
119131
if file_type == "i":
120132
self.input_filename = filename
121133
else:
122134
self.output_filename = filename
123135
self.__init_file(
124136
open(filename, 'w+', newline='\n', encoding='utf-8'), data_id,
125-
file_type)
137+
file_type, make_dirs)
126138

127139
def __escape_format(self, st: str):
128140
"""replace "{}" to "{{}}" """
129141
return re.sub(r"\{", "{{", re.sub(r"\}", "}}", st))
130142

143+
def __make_dirs(self, pth: str):
144+
os.makedirs(os.path.dirname(pth), exist_ok=True)
145+
131146
def __del_files(self):
132147
"""delete files"""
133148
if self.__input_temp and self.input_filename is not None:
@@ -170,7 +185,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
170185

171186
def __write(self, file: IOBase, *args, **kwargs):
172187
"""
173-
Write every element in *args into file. If the element isn't "\n", insert `separator`.
188+
Write every element in *args into file. If the element isn't "\n", insert `separator`.
174189
It will convert every element into str.
175190
"""
176191
separator = kwargs.get("separator", " ")
@@ -185,6 +200,17 @@ def __write(self, file: IOBase, *args, **kwargs):
185200
if arg == "\n":
186201
self.is_first_char[file] = True
187202

203+
def __clear(self, file: IOBase, pos: int = 0):
204+
"""
205+
Clear the content use truncate()
206+
Args:
207+
file: Which file to clear
208+
pos: Where file will truncate.
209+
"""
210+
file.truncate(pos)
211+
self.is_first_char[file] = True
212+
file.seek(pos)
213+
188214
def input_write(self, *args, **kwargs):
189215
"""
190216
Write every element in *args into the input file. Splits with `separator`.
@@ -208,6 +234,15 @@ def input_writeln(self, *args, **kwargs):
208234
args.append("\n")
209235
self.input_write(*args, **kwargs)
210236

237+
def input_clear_content(self, pos: int = 0):
238+
"""
239+
Clear the content of input
240+
Args:
241+
pos: Where file will truncate.
242+
"""
243+
244+
self.__clear(self.input_file, pos)
245+
211246
def output_gen(self, shell_cmd, time_limit=None):
212247
"""
213248
Run the command `shell_cmd` (usually the std program) and send it the input file as stdin.
@@ -268,6 +303,14 @@ def output_writeln(self, *args, **kwargs):
268303
args.append("\n")
269304
self.output_write(*args, **kwargs)
270305

306+
def output_clear_content(self, pos: int = 0):
307+
"""
308+
Clear the content of output
309+
Args:
310+
pos: Where file will truncate
311+
"""
312+
self.__clear(self.output_file, pos)
313+
271314
def flush_buffer(self):
272315
"""Flush the input file"""
273316
self.input_file.flush()

cyaron/tests/io_test.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ def test_write_stuff(self):
5656
input = f.read()
5757
with open("test_write.out") as f:
5858
output = f.read()
59-
self.assertEqual(input.split(), ["1", "2", "3", "4", "5", "6", "7", "8", "9"])
60-
self.assertEqual(output.split(), ["9", "8", "7", "6", "5", "4", "3", "2", "1"])
59+
self.assertEqual(input.split(),
60+
["1", "2", "3", "4", "5", "6", "7", "8", "9"])
61+
self.assertEqual(output.split(),
62+
["9", "8", "7", "6", "5", "4", "3", "2", "1"])
6163
self.assertEqual(input.count("\n"), 2)
6264
self.assertEqual(output.count("\n"), 2)
6365

@@ -111,3 +113,53 @@ def test_init_overload(self):
111113
with IO(fin, fout) as test:
112114
self.assertEqual(test.input_file, fin)
113115
self.assertEqual(test.output_file, fout)
116+
117+
def test_make_dirs(self):
118+
mkdir_true = False
119+
with IO("./automkdir_true/data.in",
120+
"./automkdir_true/data.out",
121+
make_dirs=True):
122+
mkdir_true = os.path.exists("./automkdir_true")
123+
self.assertEqual(mkdir_true, True)
124+
125+
mkdir_false = False
126+
try:
127+
with IO(
128+
"./automkdir_false/data.in",
129+
"./automkdir_false/data.out",
130+
):
131+
pass
132+
except FileNotFoundError:
133+
mkdir_false = True
134+
mkdir_false &= not os.path.exists("./automkdir_false")
135+
self.assertEqual(mkdir_false, True)
136+
137+
def test_output_clear_content(self):
138+
with IO("test_clear.in", "test_clear.out") as test:
139+
test.input_write("This is a test.")
140+
test.input_clear_content()
141+
test.input_write("Cleared content.")
142+
test.output_write("This is a test.")
143+
test.output_clear_content()
144+
test.output_write("Cleared content.")
145+
with open("test_clear.in", encoding="utf-8") as f:
146+
input_text = f.read()
147+
with open("test_clear.out", encoding="utf-8") as f:
148+
output_text = f.read()
149+
self.assertEqual(input_text, "Cleared content.")
150+
self.assertEqual(output_text, "Cleared content.")
151+
152+
def test_output_clear_content_with_position(self):
153+
with IO("test_clear_pos.in", "test_clear_pos.out") as test:
154+
test.input_write("This is a test.")
155+
test.input_clear_content(5)
156+
test.input_write("Cleared content.")
157+
test.output_write("This is a test.")
158+
test.output_clear_content(5)
159+
test.output_write("Cleared content.")
160+
with open("test_clear_pos.in", encoding="utf-8") as f:
161+
input_text = f.read()
162+
with open("test_clear_pos.out", encoding="utf-8") as f:
163+
output_text = f.read()
164+
self.assertEqual(input_text, "This Cleared content.")
165+
self.assertEqual(output_text, "This Cleared content.")

0 commit comments

Comments
 (0)