1"""Helper class to quickly write a loop over all standard input files. 2 3Typical use is: 4 5 import fileinput 6 for line in fileinput.input(encoding="utf-8"): 7 process(line) 8 9This iterates over the lines of all files listed in sys.argv[1:], 10defaulting to sys.stdin if the list is empty. If a filename is '-' it 11is also replaced by sys.stdin and the optional arguments mode and 12openhook are ignored. To specify an alternative list of filenames, 13pass it as the argument to input(). A single file name is also allowed. 14 15Functions filename(), lineno() return the filename and cumulative line 16number of the line that has just been read; filelineno() returns its 17line number in the current file; isfirstline() returns true iff the 18line just read is the first line of its file; isstdin() returns true 19iff the line was read from sys.stdin. Function nextfile() closes the 20current file so that the next iteration will read the first line from 21the next file (if any); lines not read from the file will not count 22towards the cumulative line count; the filename is not changed until 23after the first line of the next file has been read. Function close() 24closes the sequence. 25 26Before any lines have been read, filename() returns None and both line 27numbers are zero; nextfile() has no effect. After all lines have been 28read, filename() and the line number functions return the values 29pertaining to the last line read; nextfile() has no effect. 30 31All files are opened in text mode by default, you can override this by 32setting the mode parameter to input() or FileInput.__init__(). 33If an I/O error occurs during opening or reading a file, the OSError 34exception is raised. 35 36If sys.stdin is used more than once, the second and further use will 37return no lines, except perhaps for interactive use, or if it has been 38explicitly reset (e.g. using sys.stdin.seek(0)). 39 40Empty files are opened and immediately closed; the only time their 41presence in the list of filenames is noticeable at all is when the 42last file opened is empty. 43 44It is possible that the last line of a file doesn't end in a newline 45character; otherwise lines are returned including the trailing 46newline. 47 48Class FileInput is the implementation; its methods filename(), 49lineno(), fileline(), isfirstline(), isstdin(), nextfile() and close() 50correspond to the functions in the module. In addition it has a 51readline() method which returns the next input line, and a 52__getitem__() method which implements the sequence behavior. The 53sequence must be accessed in strictly sequential order; sequence 54access and readline() cannot be mixed. 55 56Optional in-place filtering: if the keyword argument inplace=1 is 57passed to input() or to the FileInput constructor, the file is moved 58to a backup file and standard output is directed to the input file. 59This makes it possible to write a filter that rewrites its input file 60in place. If the keyword argument backup=".<some extension>" is also 61given, it specifies the extension for the backup file, and the backup 62file remains around; by default, the extension is ".bak" and it is 63deleted when the output file is closed. In-place filtering is 64disabled when standard input is read. XXX The current implementation 65does not work for MS-DOS 8+3 filesystems. 66""" 67 68import io 69import sys, os 70from types import GenericAlias 71 72__all__ = ["input", "close", "nextfile", "filename", "lineno", "filelineno", 73 "fileno", "isfirstline", "isstdin", "FileInput", "hook_compressed", 74 "hook_encoded"] 75 76_state = None 77 78def input(files=None, inplace=False, backup="", *, mode="r", openhook=None, 79 encoding=None, errors=None): 80 """Return an instance of the FileInput class, which can be iterated. 81 82 The parameters are passed to the constructor of the FileInput class. 83 The returned instance, in addition to being an iterator, 84 keeps global state for the functions of this module,. 85 """ 86 global _state 87 if _state and _state._file: 88 raise RuntimeError("input() already active") 89 _state = FileInput(files, inplace, backup, mode=mode, openhook=openhook, 90 encoding=encoding, errors=errors) 91 return _state 92 93def close(): 94 """Close the sequence.""" 95 global _state 96 state = _state 97 _state = None 98 if state: 99 state.close() 100 101def nextfile(): 102 """ 103 Close the current file so that the next iteration will read the first 104 line from the next file (if any); lines not read from the file will 105 not count towards the cumulative line count. The filename is not 106 changed until after the first line of the next file has been read. 107 Before the first line has been read, this function has no effect; 108 it cannot be used to skip the first file. After the last line of the 109 last file has been read, this function has no effect. 110 """ 111 if not _state: 112 raise RuntimeError("no active input()") 113 return _state.nextfile() 114 115def filename(): 116 """ 117 Return the name of the file currently being read. 118 Before the first line has been read, returns None. 119 """ 120 if not _state: 121 raise RuntimeError("no active input()") 122 return _state.filename() 123 124def lineno(): 125 """ 126 Return the cumulative line number of the line that has just been read. 127 Before the first line has been read, returns 0. After the last line 128 of the last file has been read, returns the line number of that line. 129 """ 130 if not _state: 131 raise RuntimeError("no active input()") 132 return _state.lineno() 133 134def filelineno(): 135 """ 136 Return the line number in the current file. Before the first line 137 has been read, returns 0. After the last line of the last file has 138 been read, returns the line number of that line within the file. 139 """ 140 if not _state: 141 raise RuntimeError("no active input()") 142 return _state.filelineno() 143 144def fileno(): 145 """ 146 Return the file number of the current file. When no file is currently 147 opened, returns -1. 148 """ 149 if not _state: 150 raise RuntimeError("no active input()") 151 return _state.fileno() 152 153def isfirstline(): 154 """ 155 Returns true the line just read is the first line of its file, 156 otherwise returns false. 157 """ 158 if not _state: 159 raise RuntimeError("no active input()") 160 return _state.isfirstline() 161 162def isstdin(): 163 """ 164 Returns true if the last line was read from sys.stdin, 165 otherwise returns false. 166 """ 167 if not _state: 168 raise RuntimeError("no active input()") 169 return _state.isstdin() 170 171class FileInput: 172 """FileInput([files[, inplace[, backup]]], *, mode=None, openhook=None) 173 174 Class FileInput is the implementation of the module; its methods 175 filename(), lineno(), fileline(), isfirstline(), isstdin(), fileno(), 176 nextfile() and close() correspond to the functions of the same name 177 in the module. 178 In addition it has a readline() method which returns the next 179 input line, and a __getitem__() method which implements the 180 sequence behavior. The sequence must be accessed in strictly 181 sequential order; random access and readline() cannot be mixed. 182 """ 183 184 def __init__(self, files=None, inplace=False, backup="", *, 185 mode="r", openhook=None, encoding=None, errors=None): 186 if isinstance(files, str): 187 files = (files,) 188 elif isinstance(files, os.PathLike): 189 files = (os.fspath(files), ) 190 else: 191 if files is None: 192 files = sys.argv[1:] 193 if not files: 194 files = ('-',) 195 else: 196 files = tuple(files) 197 self._files = files 198 self._inplace = inplace 199 self._backup = backup 200 self._savestdout = None 201 self._output = None 202 self._filename = None 203 self._startlineno = 0 204 self._filelineno = 0 205 self._file = None 206 self._isstdin = False 207 self._backupfilename = None 208 self._encoding = encoding 209 self._errors = errors 210 211 # We can not use io.text_encoding() here because old openhook doesn't 212 # take encoding parameter. 213 if (sys.flags.warn_default_encoding and 214 "b" not in mode and encoding is None and openhook is None): 215 import warnings 216 warnings.warn("'encoding' argument not specified.", 217 EncodingWarning, 2) 218 219 # restrict mode argument to reading modes 220 if mode not in ('r', 'rb'): 221 raise ValueError("FileInput opening mode must be 'r' or 'rb'") 222 self._mode = mode 223 self._write_mode = mode.replace('r', 'w') 224 if openhook: 225 if inplace: 226 raise ValueError("FileInput cannot use an opening hook in inplace mode") 227 if not callable(openhook): 228 raise ValueError("FileInput openhook must be callable") 229 self._openhook = openhook 230 231 def __del__(self): 232 self.close() 233 234 def close(self): 235 try: 236 self.nextfile() 237 finally: 238 self._files = () 239 240 def __enter__(self): 241 return self 242 243 def __exit__(self, type, value, traceback): 244 self.close() 245 246 def __iter__(self): 247 return self 248 249 def __next__(self): 250 while True: 251 line = self._readline() 252 if line: 253 self._filelineno += 1 254 return line 255 if not self._file: 256 raise StopIteration 257 self.nextfile() 258 # repeat with next file 259 260 def nextfile(self): 261 savestdout = self._savestdout 262 self._savestdout = None 263 if savestdout: 264 sys.stdout = savestdout 265 266 output = self._output 267 self._output = None 268 try: 269 if output: 270 output.close() 271 finally: 272 file = self._file 273 self._file = None 274 try: 275 del self._readline # restore FileInput._readline 276 except AttributeError: 277 pass 278 try: 279 if file and not self._isstdin: 280 file.close() 281 finally: 282 backupfilename = self._backupfilename 283 self._backupfilename = None 284 if backupfilename and not self._backup: 285 try: os.unlink(backupfilename) 286 except OSError: pass 287 288 self._isstdin = False 289 290 def readline(self): 291 while True: 292 line = self._readline() 293 if line: 294 self._filelineno += 1 295 return line 296 if not self._file: 297 return line 298 self.nextfile() 299 # repeat with next file 300 301 def _readline(self): 302 if not self._files: 303 if 'b' in self._mode: 304 return b'' 305 else: 306 return '' 307 self._filename = self._files[0] 308 self._files = self._files[1:] 309 self._startlineno = self.lineno() 310 self._filelineno = 0 311 self._file = None 312 self._isstdin = False 313 self._backupfilename = 0 314 315 # EncodingWarning is emitted in __init__() already 316 if "b" not in self._mode: 317 encoding = self._encoding or "locale" 318 else: 319 encoding = None 320 321 if self._filename == '-': 322 self._filename = '<stdin>' 323 if 'b' in self._mode: 324 self._file = getattr(sys.stdin, 'buffer', sys.stdin) 325 else: 326 self._file = sys.stdin 327 self._isstdin = True 328 else: 329 if self._inplace: 330 self._backupfilename = ( 331 os.fspath(self._filename) + (self._backup or ".bak")) 332 try: 333 os.unlink(self._backupfilename) 334 except OSError: 335 pass 336 # The next few lines may raise OSError 337 os.rename(self._filename, self._backupfilename) 338 self._file = open(self._backupfilename, self._mode, 339 encoding=encoding, errors=self._errors) 340 try: 341 perm = os.fstat(self._file.fileno()).st_mode 342 except OSError: 343 self._output = open(self._filename, self._write_mode, 344 encoding=encoding, errors=self._errors) 345 else: 346 mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC 347 if hasattr(os, 'O_BINARY'): 348 mode |= os.O_BINARY 349 350 fd = os.open(self._filename, mode, perm) 351 self._output = os.fdopen(fd, self._write_mode, 352 encoding=encoding, errors=self._errors) 353 try: 354 os.chmod(self._filename, perm) 355 except OSError: 356 pass 357 self._savestdout = sys.stdout 358 sys.stdout = self._output 359 else: 360 # This may raise OSError 361 if self._openhook: 362 # Custom hooks made previous to Python 3.10 didn't have 363 # encoding argument 364 if self._encoding is None: 365 self._file = self._openhook(self._filename, self._mode) 366 else: 367 self._file = self._openhook( 368 self._filename, self._mode, encoding=self._encoding, errors=self._errors) 369 else: 370 self._file = open(self._filename, self._mode, encoding=encoding, errors=self._errors) 371 self._readline = self._file.readline # hide FileInput._readline 372 return self._readline() 373 374 def filename(self): 375 return self._filename 376 377 def lineno(self): 378 return self._startlineno + self._filelineno 379 380 def filelineno(self): 381 return self._filelineno 382 383 def fileno(self): 384 if self._file: 385 try: 386 return self._file.fileno() 387 except ValueError: 388 return -1 389 else: 390 return -1 391 392 def isfirstline(self): 393 return self._filelineno == 1 394 395 def isstdin(self): 396 return self._isstdin 397 398 __class_getitem__ = classmethod(GenericAlias) 399 400 401def hook_compressed(filename, mode, *, encoding=None, errors=None): 402 if encoding is None and "b" not in mode: # EncodingWarning is emitted in FileInput() already. 403 encoding = "locale" 404 ext = os.path.splitext(filename)[1] 405 if ext == '.gz': 406 import gzip 407 stream = gzip.open(filename, mode) 408 elif ext == '.bz2': 409 import bz2 410 stream = bz2.BZ2File(filename, mode) 411 else: 412 return open(filename, mode, encoding=encoding, errors=errors) 413 414 # gzip and bz2 are binary mode by default. 415 if "b" not in mode: 416 stream = io.TextIOWrapper(stream, encoding=encoding, errors=errors) 417 return stream 418 419 420def hook_encoded(encoding, errors=None): 421 def openhook(filename, mode): 422 return open(filename, mode, encoding=encoding, errors=errors) 423 return openhook 424 425 426def _test(): 427 import getopt 428 inplace = False 429 backup = False 430 opts, args = getopt.getopt(sys.argv[1:], "ib:") 431 for o, a in opts: 432 if o == '-i': inplace = True 433 if o == '-b': backup = a 434 for line in input(args, inplace=inplace, backup=backup): 435 if line[-1:] == '\n': line = line[:-1] 436 if line[-1:] == '\r': line = line[:-1] 437 print("%d: %s[%d]%s %s" % (lineno(), filename(), filelineno(), 438 isfirstline() and "*" or "", line)) 439 print("%d: %s[%d]" % (lineno(), filename(), filelineno())) 440 441if __name__ == '__main__': 442 _test() 443