codekingpro/portable-devtools
114k
1"""Helper class to quickly write a loop over all standard input files.2 3Typical use is:4 5 import fileinput6 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 '-' it11is also replaced by sys.stdin and the optional arguments mode and12openhook 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 line16number of the line that has just been read; filelineno() returns its17line number in the current file; isfirstline() returns true iff the18line just read is the first line of its file; isstdin() returns true19iff the line was read from sys.stdin. Function nextfile() closes the20current file so that the next iteration will read the first line from21the next file (if any); lines not read from the file will not count22towards the cumulative line count; the filename is not changed until23after 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 line27numbers are zero; nextfile() has no effect. After all lines have been28read, filename() and the line number functions return the values29pertaining to the last line read; nextfile() has no effect.30 31All files are opened in text mode by default, you can override this by32setting the mode parameter to input() or FileInput.__init__().33If an I/O error occurs during opening or reading a file, the OSError34exception is raised.35 36If sys.stdin is used more than once, the second and further use will37return no lines, except perhaps for interactive use, or if it has been38explicitly reset (e.g. using sys.stdin.seek(0)).39 40Empty files are opened and immediately closed; the only time their41presence in the list of filenames is noticeable at all is when the42last file opened is empty.43 44It is possible that the last line of a file doesn't end in a newline45character; otherwise lines are returned including the trailing46newline.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 a51readline() method which returns the next input line, and a52__getitem__() method which implements the sequence behavior. The53sequence must be accessed in strictly sequential order; sequence54access and readline() cannot be mixed.55 56Optional in-place filtering: if the keyword argument inplace=True is57passed to input() or to the FileInput constructor, the file is moved58to a backup file and standard output is directed to the input file.59This makes it possible to write a filter that rewrites its input file60in place. If the keyword argument backup=".<some extension>" is also61given, it specifies the extension for the backup file, and the backup62file remains around; by default, the extension is ".bak" and it is63deleted when the output file is closed. In-place filtering is64disabled when standard input is read. XXX The current implementation65does not work for MS-DOS 8+3 filesystems.66"""67 68import io69import sys, os70from types import GenericAlias71 72__all__ = ["input", "close", "nextfile", "filename", "lineno", "filelineno",73 "fileno", "isfirstline", "isstdin", "FileInput", "hook_compressed",74 "hook_encoded"]75 76_state = None77 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 _state87 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 _state92 93def close():94 """Close the sequence."""95 global _state96 state = _state97 _state = None98 if state:99 state.close()100 101def nextfile():102 """103 Close the current file so that the next iteration will read the first104 line from the next file (if any); lines not read from the file will105 not count towards the cumulative line count. The filename is not106 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 the109 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 line128 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 line137 has been read, returns 0. After the last line of the last file has138 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 currently147 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 methods175 filename(), lineno(), fileline(), isfirstline(), isstdin(), fileno(),176 nextfile() and close() correspond to the functions of the same name177 in the module.178 In addition it has a readline() method which returns the next179 input line, and a __getitem__() method which implements the180 sequence behavior. The sequence must be accessed in strictly181 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 = files198 self._inplace = inplace199 self._backup = backup200 self._savestdout = None201 self._output = None202 self._filename = None203 self._startlineno = 0204 self._filelineno = 0205 self._file = None206 self._isstdin = False207 self._backupfilename = None208 self._encoding = encoding209 self._errors = errors210 211 # We can not use io.text_encoding() here because old openhook doesn't212 # take encoding parameter.213 if (sys.flags.warn_default_encoding and214 "b" not in mode and encoding is None and openhook is None):215 import warnings216 warnings.warn("'encoding' argument not specified.",217 EncodingWarning, 2)218 219 # restrict mode argument to reading modes220 if mode not in ('r', 'rb'):221 raise ValueError("FileInput opening mode must be 'r' or 'rb'")222 self._mode = mode223 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 = openhook230 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 self242 243 def __exit__(self, type, value, traceback):244 self.close()245 246 def __iter__(self):247 return self248 249 def __next__(self):250 while True:251 line = self._readline()252 if line:253 self._filelineno += 1254 return line255 if not self._file:256 raise StopIteration257 self.nextfile()258 # repeat with next file259 260 def nextfile(self):261 savestdout = self._savestdout262 self._savestdout = None263 if savestdout:264 sys.stdout = savestdout265 266 output = self._output267 self._output = None268 try:269 if output:270 output.close()271 finally:272 file = self._file273 self._file = None274 try:275 del self._readline # restore FileInput._readline276 except AttributeError:277 pass278 try:279 if file and not self._isstdin:280 file.close()281 finally:282 backupfilename = self._backupfilename283 self._backupfilename = None284 if backupfilename and not self._backup:285 try: os.unlink(backupfilename)286 except OSError: pass287 288 self._isstdin = False289 290 def readline(self):291 while True:292 line = self._readline()293 if line:294 self._filelineno += 1295 return line296 if not self._file:297 return line298 self.nextfile()299 # repeat with next file300 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 = 0311 self._file = None312 self._isstdin = False313 self._backupfilename = 0314 315 # EncodingWarning is emitted in __init__() already316 if "b" not in self._mode:317 encoding = self._encoding or "locale"318 else:319 encoding = None320 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.stdin327 self._isstdin = True328 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 pass336 # The next few lines may raise OSError337 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_mode342 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_TRUNC347 if hasattr(os, 'O_BINARY'):348 mode |= os.O_BINARY349 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 pass357 self._savestdout = sys.stdout358 sys.stdout = self._output359 else:360 # This may raise OSError361 if self._openhook:362 # Custom hooks made previous to Python 3.10 didn't have363 # encoding argument364 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._readline372 return self._readline()373 374 def filename(self):375 return self._filename376 377 def lineno(self):378 return self._startlineno + self._filelineno379 380 def filelineno(self):381 return self._filelineno382 383 def fileno(self):384 if self._file:385 try:386 return self._file.fileno()387 except ValueError:388 return -1389 else:390 return -1391 392 def isfirstline(self):393 return self._filelineno == 1394 395 def isstdin(self):396 return self._isstdin397 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 gzip407 stream = gzip.open(filename, mode)408 elif ext == '.bz2':409 import bz2410 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 stream418 419 420def hook_encoded(encoding, errors=None):421 def openhook(filename, mode):422 return open(filename, mode, encoding=encoding, errors=errors)423 return openhook424 425 426def _test():427 import getopt428 inplace = False429 backup = False430 opts, args = getopt.getopt(sys.argv[1:], "ib:")431 for o, a in opts:432 if o == '-i': inplace = True433 if o == '-b': backup = a434 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 