vibefpmgui.source_text

Reading source files: the one set of rules for the editor, Search, Rename and Replace in Files (so they agree on a file's text, its encoding and its line ends). No Qt.

 1"""Reading source files: the one set of rules for the editor, Search,
 2Rename and Replace in Files (so they agree on a file's text, its encoding
 3and its line ends). No Qt.
 4"""
 5
 6from __future__ import annotations
 7
 8from pathlib import Path
 9from typing import NamedTuple
10
11MAX_FILE_SIZE = 8 * 1024 * 1024  # (bigger files aren't searched: see search_text)
12
13
14class SourceText(NamedTuple):
15    text: str        # with "\n" line ends
16    encoding: str    # "utf-8", or "latin-1" for a file that isn't UTF-8
17    newline: str     # the line end most of its lines have: "\n" or "\r\n"
18    mixed: bool      # it has more than one kind of line end
19
20
21def line_endings(text: str) -> tuple[str, bool]:
22    """(the line end most lines of a file's text have: "\\n" or "\\r\\n",
23    whether it has more than one kind). Old Mac line ends (a lone "\\r") count
24    as another kind: Qt reads them as line breaks, saved as the common one."""
25    crlf = text.count("\r\n")
26    lone_cr = text.count("\r") - crlf
27    lf = text.count("\n") - crlf
28    kinds = sum(1 for n in (crlf, lone_cr, lf) if n)
29    return ("\r\n" if crlf > lf else "\n"), kinds > 1
30
31
32def decode(raw: bytes) -> tuple[str, str]:
33    """(text, encoding) of a file's bytes: UTF-8 (with its byte-order mark:
34    "utf-8-sig", the mark not in the text but written back), or Latin-1
35    (which reads any bytes) for one that isn't. (Qt's editor drops a mark
36    left in the text: saved, the file lost it; blamed, its first line was
37    "not committed yet".)"""
38    try:
39        if raw.startswith(b"\xef\xbb\xbf"):
40            return raw.decode("utf-8-sig"), "utf-8-sig"
41        return raw.decode("utf-8"), "utf-8"
42    except UnicodeDecodeError:
43        return raw.decode("latin-1"), "latin-1"
44
45
46def read_source(path: Path) -> SourceText:
47    """A file's text as the editor shows it, and how to write it back (raises
48    OSError if it can't be read)."""
49    text, encoding = decode(path.read_bytes())
50    newline, mixed = line_endings(text)
51    return SourceText(text.replace("\r\n", "\n"), encoding, newline, mixed)
52
53
54def search_text(path: Path) -> str | None:
55    """A file's text ("\\n" line ends) to search, or None for one that's
56    binary, huge or unreadable."""
57    try:
58        if path.stat().st_size > MAX_FILE_SIZE:
59            return None
60        raw = path.read_bytes()
61    except OSError:
62        return None
63    if b"\0" in raw[:8192]:
64        return None
65    return decode(raw)[0].replace("\r\n", "\n")
MAX_FILE_SIZE = 8388608
class SourceText(typing.NamedTuple):
15class SourceText(NamedTuple):
16    text: str        # with "\n" line ends
17    encoding: str    # "utf-8", or "latin-1" for a file that isn't UTF-8
18    newline: str     # the line end most of its lines have: "\n" or "\r\n"
19    mixed: bool      # it has more than one kind of line end

SourceText(text, encoding, newline, mixed)

SourceText(text: str, encoding: str, newline: str, mixed: bool)

Create new instance of SourceText(text, encoding, newline, mixed)

text: str

Alias for field number 0

encoding: str

Alias for field number 1

newline: str

Alias for field number 2

mixed: bool

Alias for field number 3

def line_endings(text: str) -> tuple[str, bool]:
22def line_endings(text: str) -> tuple[str, bool]:
23    """(the line end most lines of a file's text have: "\\n" or "\\r\\n",
24    whether it has more than one kind). Old Mac line ends (a lone "\\r") count
25    as another kind: Qt reads them as line breaks, saved as the common one."""
26    crlf = text.count("\r\n")
27    lone_cr = text.count("\r") - crlf
28    lf = text.count("\n") - crlf
29    kinds = sum(1 for n in (crlf, lone_cr, lf) if n)
30    return ("\r\n" if crlf > lf else "\n"), kinds > 1

(the line end most lines of a file's text have: "\n" or "\r\n", whether it has more than one kind). Old Mac line ends (a lone "\r") count as another kind: Qt reads them as line breaks, saved as the common one.

def decode(raw: bytes) -> tuple[str, str]:
33def decode(raw: bytes) -> tuple[str, str]:
34    """(text, encoding) of a file's bytes: UTF-8 (with its byte-order mark:
35    "utf-8-sig", the mark not in the text but written back), or Latin-1
36    (which reads any bytes) for one that isn't. (Qt's editor drops a mark
37    left in the text: saved, the file lost it; blamed, its first line was
38    "not committed yet".)"""
39    try:
40        if raw.startswith(b"\xef\xbb\xbf"):
41            return raw.decode("utf-8-sig"), "utf-8-sig"
42        return raw.decode("utf-8"), "utf-8"
43    except UnicodeDecodeError:
44        return raw.decode("latin-1"), "latin-1"

(text, encoding) of a file's bytes: UTF-8 (with its byte-order mark: "utf-8-sig", the mark not in the text but written back), or Latin-1 (which reads any bytes) for one that isn't. (Qt's editor drops a mark left in the text: saved, the file lost it; blamed, its first line was "not committed yet".)

def read_source(path: pathlib.Path) -> SourceText:
47def read_source(path: Path) -> SourceText:
48    """A file's text as the editor shows it, and how to write it back (raises
49    OSError if it can't be read)."""
50    text, encoding = decode(path.read_bytes())
51    newline, mixed = line_endings(text)
52    return SourceText(text.replace("\r\n", "\n"), encoding, newline, mixed)

A file's text as the editor shows it, and how to write it back (raises OSError if it can't be read).

def search_text(path: pathlib.Path) -> str | None:
55def search_text(path: Path) -> str | None:
56    """A file's text ("\\n" line ends) to search, or None for one that's
57    binary, huge or unreadable."""
58    try:
59        if path.stat().st_size > MAX_FILE_SIZE:
60            return None
61        raw = path.read_bytes()
62    except OSError:
63        return None
64    if b"\0" in raw[:8192]:
65        return None
66    return decode(raw)[0].replace("\r\n", "\n")

A file's text ("\n" line ends) to search, or None for one that's binary, huge or unreadable.