io.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. """Defines any IO utilities used by isort"""
  2. import dataclasses
  3. import re
  4. import tokenize
  5. from contextlib import contextmanager
  6. from io import BytesIO, StringIO, TextIOWrapper
  7. from pathlib import Path
  8. from typing import Any, Callable, Iterator, TextIO, Union
  9. from isort.exceptions import UnsupportedEncoding
  10. _ENCODING_PATTERN = re.compile(rb"^[ \t\f]*#.*?coding[:=][ \t]*([-_.a-zA-Z0-9]+)")
  11. @dataclasses.dataclass(frozen=True)
  12. class File:
  13. stream: TextIO
  14. path: Path
  15. encoding: str
  16. @staticmethod
  17. def detect_encoding(filename: Union[str, Path], readline: Callable[[], bytes]) -> str:
  18. try:
  19. return tokenize.detect_encoding(readline)[0]
  20. except Exception:
  21. raise UnsupportedEncoding(filename)
  22. @staticmethod
  23. def from_contents(contents: str, filename: str) -> "File":
  24. encoding = File.detect_encoding(filename, BytesIO(contents.encode("utf-8")).readline)
  25. return File(stream=StringIO(contents), path=Path(filename).resolve(), encoding=encoding)
  26. @property
  27. def extension(self) -> str:
  28. return self.path.suffix.lstrip(".")
  29. @staticmethod
  30. def _open(filename: Union[str, Path]) -> TextIOWrapper:
  31. """Open a file in read only mode using the encoding detected by
  32. detect_encoding().
  33. """
  34. buffer = open(filename, "rb")
  35. try:
  36. encoding = File.detect_encoding(filename, buffer.readline)
  37. buffer.seek(0)
  38. text = TextIOWrapper(buffer, encoding, line_buffering=True, newline="")
  39. text.mode = "r" # type: ignore
  40. return text
  41. except Exception:
  42. buffer.close()
  43. raise
  44. @staticmethod
  45. @contextmanager
  46. def read(filename: Union[str, Path]) -> Iterator["File"]:
  47. file_path = Path(filename).resolve()
  48. stream = None
  49. try:
  50. stream = File._open(file_path)
  51. yield File(stream=stream, path=file_path, encoding=stream.encoding)
  52. finally:
  53. if stream is not None:
  54. stream.close()
  55. class _EmptyIO(StringIO):
  56. def write(self, *args: Any, **kwargs: Any) -> None: # type: ignore # skipcq: PTC-W0049
  57. pass
  58. Empty = _EmptyIO()