move status line helper to utils module
This commit is contained in:
parent
72bd33660b
commit
cc38046c1b
3 changed files with 99 additions and 73 deletions
|
|
@ -231,7 +231,7 @@ def cmd_ingest_rclone_json(args: argparse.Namespace) -> None:
|
||||||
metadex.close()
|
metadex.close()
|
||||||
|
|
||||||
msg = f"Checked {context.seen} files, {context.added} new, {context.changed} changed, {context.ignored} ignored, {context.removed} removed"
|
msg = f"Checked {context.seen} files, {context.added} new, {context.changed} changed, {context.ignored} ignored, {context.removed} removed"
|
||||||
print(msg.ljust(metadex._terminal_width))
|
print(msg.ljust(utils._terminal_width))
|
||||||
|
|
||||||
|
|
||||||
@command("ingest-ls")
|
@command("ingest-ls")
|
||||||
|
|
@ -259,7 +259,7 @@ def cmd_ingest_db(args: argparse.Namespace) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = f"Checked {context.seen} files, {context.added} new, {context.changed} changed, {context.ignored} ignored, {context.removed} removed"
|
msg = f"Checked {context.seen} files, {context.added} new, {context.changed} changed, {context.ignored} ignored, {context.removed} removed"
|
||||||
print(msg.ljust(metadex._terminal_width))
|
print(msg.ljust(utils._terminal_width))
|
||||||
|
|
||||||
metadex.close()
|
metadex.close()
|
||||||
|
|
||||||
|
|
@ -278,7 +278,7 @@ def cmd_scan(args: argparse.Namespace) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = f"{basedir}: Checked {context.seen} files, {context.added} new, {context.changed} changed, {context.ignored} ignored, {context.removed} removed"
|
msg = f"{basedir}: Checked {context.seen} files, {context.added} new, {context.changed} changed, {context.ignored} ignored, {context.removed} removed"
|
||||||
print(msg.ljust(metadex._terminal_width))
|
print(msg.ljust(utils._terminal_width))
|
||||||
|
|
||||||
metadex.close()
|
metadex.close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,14 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from shutil import get_terminal_size
|
|
||||||
from typing import Any, Iterable, Literal, TextIO
|
from typing import Any, Iterable, Literal, TextIO
|
||||||
from typing_extensions import TypeAlias
|
from typing_extensions import TypeAlias
|
||||||
|
|
||||||
from wcwidth import wcwidth
|
from . import config, db, ignore, ls_parser, models, utils
|
||||||
|
|
||||||
from . import config, db, ignore, ls_parser, models
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
@ -33,69 +28,6 @@ def scan(
|
||||||
return f(path, ignore_file=ignore_file, map_pathspecs=map_pathspecs)
|
return f(path, ignore_file=ignore_file, map_pathspecs=map_pathspecs)
|
||||||
|
|
||||||
|
|
||||||
_terminal_width = get_terminal_size().columns
|
|
||||||
_last_log = 0
|
|
||||||
|
|
||||||
|
|
||||||
def _cut_and_fill(
|
|
||||||
s: str,
|
|
||||||
*,
|
|
||||||
term_width: int = _terminal_width,
|
|
||||||
suffix: str = "...",
|
|
||||||
fill_char: str = " ",
|
|
||||||
):
|
|
||||||
"""Return the given string cut off to fit into the given terminal width
|
|
||||||
and fill in the remaining cells of the line.
|
|
||||||
|
|
||||||
Some (unicode) characters require more (or less) than a single cell when
|
|
||||||
printed to terminal. This function correctly takes these into account.
|
|
||||||
The given `suffix` is appended when a string is cut off.
|
|
||||||
"""
|
|
||||||
width = _wcswidth(s)
|
|
||||||
if width <= term_width:
|
|
||||||
return s + fill_char * (term_width - width)
|
|
||||||
|
|
||||||
suffix_len = _wcswidth(suffix)
|
|
||||||
idx = suffix_len + 1
|
|
||||||
term_width -= suffix_len
|
|
||||||
while True:
|
|
||||||
width = _wcswidth(s[:-idx])
|
|
||||||
if width <= term_width:
|
|
||||||
break
|
|
||||||
idx += 1
|
|
||||||
return s[:-idx] + suffix + fill_char * (term_width - width)
|
|
||||||
|
|
||||||
|
|
||||||
def _wcswidth(pwcs, /):
|
|
||||||
"""
|
|
||||||
Given a unicode string, return its printable length on a terminal.
|
|
||||||
:param str pwcs: Measure width of given unicode string.
|
|
||||||
:rtype: int
|
|
||||||
:returns: The width, in cells, necessary to display the first ``n``
|
|
||||||
characters of the unicode string ``pwcs``.
|
|
||||||
"""
|
|
||||||
width = 0
|
|
||||||
for char in pwcs:
|
|
||||||
wcw = wcwidth(char)
|
|
||||||
if wcw < 0:
|
|
||||||
wcw = 1 # Assume width 1 for "non-printables", seems to do the trick.
|
|
||||||
width += wcw
|
|
||||||
return width
|
|
||||||
|
|
||||||
|
|
||||||
def _log_ephemeral(msg: str, *, debounce_ms: "int | None" = 200):
|
|
||||||
global _last_log
|
|
||||||
|
|
||||||
if debounce_ms is not None:
|
|
||||||
now = time.monotonic()
|
|
||||||
if _last_log + (debounce_ms / 1000) > now:
|
|
||||||
return
|
|
||||||
_last_log = now
|
|
||||||
|
|
||||||
msg = msg.encode(errors="replace").decode()
|
|
||||||
sys.stderr.write(_cut_and_fill(msg) + "\r")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class _LogContext:
|
class _LogContext:
|
||||||
seen: int = 0
|
seen: int = 0
|
||||||
|
|
@ -109,7 +41,7 @@ def _log_context(path: "str | Path", context: _LogContext) -> None:
|
||||||
if config.is_stdout_piped:
|
if config.is_stdout_piped:
|
||||||
return
|
return
|
||||||
|
|
||||||
_log_ephemeral(
|
utils.set_status_line(
|
||||||
f"{context.seen} a:{context.added} c:{context.changed} i:{context.ignored} r:{context.removed} {path}"
|
f"{context.seen} a:{context.added} c:{context.changed} i:{context.ignored} r:{context.removed} {path}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from shutil import get_terminal_size
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
|
from wcwidth import wcwidth
|
||||||
|
|
||||||
_size_quantifiers = "BKMGTP"
|
_size_quantifiers = "BKMGTP"
|
||||||
_size_map: "dict[str, int]" = {
|
_size_map: "dict[str, int]" = {
|
||||||
_size_quantifiers[i]: 2 ** (10 * i) for i in range(len(_size_quantifiers))
|
_size_quantifiers[i]: 2 ** (10 * i) for i in range(len(_size_quantifiers))
|
||||||
|
|
@ -53,3 +58,92 @@ def abspath(path: Path) -> Path:
|
||||||
|
|
||||||
Similar to Path.resolve(strict=False), but doesn't resolve symlinks."""
|
Similar to Path.resolve(strict=False), but doesn't resolve symlinks."""
|
||||||
return Path(os.path.abspath(path))
|
return Path(os.path.abspath(path))
|
||||||
|
|
||||||
|
|
||||||
|
_terminal_width = get_terminal_size().columns
|
||||||
|
|
||||||
|
|
||||||
|
def _cut_and_fill(
|
||||||
|
s: str,
|
||||||
|
*,
|
||||||
|
term_width: int = _terminal_width,
|
||||||
|
suffix: str = "...",
|
||||||
|
fill_char: str = " ",
|
||||||
|
) -> str:
|
||||||
|
"""Return the given string cut off to fit into the given terminal width
|
||||||
|
and fill in the remaining cells of the line.
|
||||||
|
|
||||||
|
Some (unicode) characters require more (or less) than a single cell when
|
||||||
|
printed to terminal. This function correctly takes these into account.
|
||||||
|
The given `suffix` is appended when a string is cut off.
|
||||||
|
"""
|
||||||
|
width = _wcswidth(s)
|
||||||
|
if width <= term_width:
|
||||||
|
return s + fill_char * (term_width - width)
|
||||||
|
|
||||||
|
suffix_len = _wcswidth(suffix)
|
||||||
|
idx = suffix_len + 1
|
||||||
|
term_width -= suffix_len
|
||||||
|
while True:
|
||||||
|
width = _wcswidth(s[:-idx])
|
||||||
|
if width <= term_width:
|
||||||
|
break
|
||||||
|
idx += 1
|
||||||
|
return s[:-idx] + suffix + fill_char * (term_width - width)
|
||||||
|
|
||||||
|
|
||||||
|
def _wcswidth(pwcs: str, /) -> int:
|
||||||
|
"""
|
||||||
|
Given a unicode string, return its printable length on a terminal.
|
||||||
|
:param str pwcs: Measure width of given unicode string.
|
||||||
|
:rtype: int
|
||||||
|
:returns: The width, in cells, necessary to display the first ``n``
|
||||||
|
characters of the unicode string ``pwcs``.
|
||||||
|
"""
|
||||||
|
width = 0
|
||||||
|
for char in pwcs:
|
||||||
|
wcw = wcwidth(char)
|
||||||
|
if wcw < 0:
|
||||||
|
wcw = 1 # Assume width 1 for "non-printables", seems to do the trick.
|
||||||
|
width += wcw
|
||||||
|
return width
|
||||||
|
|
||||||
|
|
||||||
|
_last_log = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def set_status_line(msg: str, *, debounce_ms: "int | None" = 200) -> None:
|
||||||
|
"""Report on the status of a running process.
|
||||||
|
|
||||||
|
The status line is here is meant as a kind of ephemeral and shared single
|
||||||
|
line of logging output. It writes a full line of text to a terminal and will
|
||||||
|
cut off any text exceeding the line length, and will fill up any remaining
|
||||||
|
space of the line with whitespace.
|
||||||
|
It does not mix well with any other kind of logging output. When you want
|
||||||
|
to use this function, stop logging with any other logger, use the status
|
||||||
|
line logger exclusively, and finally call `clean_status_line()`.
|
||||||
|
|
||||||
|
This function has a built-in debounce controlled via `debounce_ms` which
|
||||||
|
will make sure the performance of the application isn't affected too much
|
||||||
|
by excessive logging.
|
||||||
|
"""
|
||||||
|
global _last_log
|
||||||
|
|
||||||
|
if debounce_ms is not None:
|
||||||
|
now = time.monotonic()
|
||||||
|
if _last_log + (debounce_ms / 1000) > now:
|
||||||
|
return
|
||||||
|
_last_log = now
|
||||||
|
|
||||||
|
msg = msg.encode(errors="replace").decode()
|
||||||
|
sys.stderr.write(_cut_and_fill(msg) + "\r")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_status_line() -> None:
|
||||||
|
"""Clean up the status line.
|
||||||
|
|
||||||
|
Call this function after you're done with whatever process you were
|
||||||
|
reporting status for.
|
||||||
|
This will make sure there's no garbage left on the status line.
|
||||||
|
"""
|
||||||
|
set_status_line("", debounce_ms=None)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue