186 lines
4.6 KiB
Python
186 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Utilities"""
|
|
|
|
import datetime
|
|
import difflib
|
|
import typing as t
|
|
from dataclasses import dataclass
|
|
|
|
import vim
|
|
|
|
TIME_UNITS: t.Final[t.Dict[str, int]] = {
|
|
"year": 31536000, # 365 days
|
|
"month": 2592000, # 30 days
|
|
"day": 86400, # 1 day
|
|
"hour": 3600, # 1 hour
|
|
"minute": 60, # 1 minute
|
|
"second": 1, # 1 second
|
|
}
|
|
|
|
|
|
@dataclass
|
|
class VimID:
|
|
session_id: str
|
|
window_id: int
|
|
buffer_id: int
|
|
|
|
def to_json(self) -> t.Dict[str, t.Any]:
|
|
"""Return json replof the VimID"""
|
|
return {
|
|
"id": self.session_id,
|
|
"win": self.window_id,
|
|
"buf": self.buffer_id,
|
|
}
|
|
|
|
|
|
class VimLoadingBar:
|
|
"""Implements a loading bar for Vim"""
|
|
|
|
def __init__(self, justify: int = 0) -> None:
|
|
self._total: int = 0
|
|
self._justify: int = justify
|
|
|
|
self._msg: str = "Progress...".ljust(justify)
|
|
self._drawn: bool = False
|
|
self._progress: int = 0
|
|
|
|
# Updates every roughly 1%
|
|
self._step: int = 0
|
|
|
|
def render_status(self) -> "VimLoadingBar":
|
|
"""Renders a status"""
|
|
t: str = "?" * len(str(self._total))
|
|
vim.current.buffer[0] = f"{self._msg} ({t}/{t}) [working...{' ' * 40}] ???.??%"
|
|
return self
|
|
|
|
def update(self) -> "VimLoadingBar":
|
|
"""Update bar"""
|
|
|
|
self._progress += 1
|
|
|
|
if self._step == 0 or self._progress % self._step != 0:
|
|
return self
|
|
|
|
self._progress = min(self._progress, self._total)
|
|
|
|
empty: int = int(50 - (self._progress / self._total * 50))
|
|
full: int = 50 - empty
|
|
|
|
t: str = str(self._progress).rjust(len(str(self._total)))
|
|
|
|
self.clear(False)
|
|
vim.current.buffer[0] = (
|
|
f"{self._msg} ({t}/{self._total}) [{ '*' * full}{' ' * empty}] {self._progress / self._total * 100:>6.2f}%"
|
|
)
|
|
vim.command("redraw")
|
|
|
|
return self
|
|
|
|
def clear(self, redraw: bool = True) -> "VimLoadingBar":
|
|
"""Clear bar"""
|
|
|
|
vim.current.buffer[0] = ""
|
|
if redraw:
|
|
vim.command("redraw")
|
|
|
|
return self
|
|
|
|
def reset(self) -> "VimLoadingBar":
|
|
"""Reset progress"""
|
|
self._progress = 0
|
|
return self
|
|
|
|
def set_msg(self, msg: str) -> "VimLoadingBar":
|
|
"""Reset progress"""
|
|
self._msg = msg.ljust(self._justify)
|
|
return self
|
|
|
|
def set_total(self, total: int) -> "VimLoadingBar":
|
|
"""Set a new goal"""
|
|
self._total = total
|
|
self._step = total // 100
|
|
self._progress = 0
|
|
return self
|
|
|
|
|
|
def get_vid() -> VimID:
|
|
"""Get Vim ID"""
|
|
return VimID(
|
|
vim.eval("g:wrapped_stats_id"),
|
|
int(vim.eval("win_getid()")),
|
|
int(vim.eval("bufnr('%')")),
|
|
)
|
|
|
|
|
|
def ts() -> str:
|
|
"""Get UTC timestamp"""
|
|
return datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
|
|
|
|
|
|
def diff_file(filename: str, current_buf: t.List[str]) -> t.Tuple[int, int]:
|
|
"""Diff a file"""
|
|
|
|
with open(filename, "r") as fp:
|
|
file_lines: t.List[str] = fp.readlines()
|
|
|
|
current_lines: t.Tuple[str, ...] = tuple(line.strip() for line in current_buf)
|
|
new_lines: t.Tuple[str, ...] = tuple(line.strip() for line in file_lines)
|
|
|
|
diff: t.Iterator[str] = difflib.ndiff(new_lines, current_lines)
|
|
|
|
added_lines: int = 0
|
|
removed_lines: int = 0
|
|
|
|
for line in diff:
|
|
if line.startswith("+ "):
|
|
added_lines += 1
|
|
|
|
elif line.startswith("- "):
|
|
removed_lines += 1
|
|
|
|
return added_lines, removed_lines
|
|
|
|
|
|
def seconds_to_human_readable(seconds: int) -> str:
|
|
"""Converts seconds to human-readable format"""
|
|
|
|
components: t.List[str] = []
|
|
|
|
for unit, unit_seconds in TIME_UNITS.items():
|
|
if seconds >= unit_seconds:
|
|
count: int = seconds // unit_seconds
|
|
|
|
if count > 0:
|
|
components.append(f"{count} {unit}{'s' if count > 1 else ''}")
|
|
|
|
seconds %= unit_seconds
|
|
|
|
if len(components) > 1:
|
|
if len(components) > 3:
|
|
return ", ".join(components[:-1]) + ", and " + components[-1]
|
|
else:
|
|
return components[0] + " and " + components[1]
|
|
elif components:
|
|
return components[0]
|
|
else:
|
|
return "0 seconds"
|
|
|
|
|
|
def get_median(numbers: t.Iterable[int]) -> t.Union[int, float]:
|
|
"""Median"""
|
|
|
|
numbers = sorted(numbers)
|
|
|
|
n: int = len(numbers)
|
|
|
|
median: t.Union[int, float]
|
|
|
|
if n % 2 == 1:
|
|
median = numbers[n // 2]
|
|
else:
|
|
mid1: int = numbers[n // 2]
|
|
mid2: int = numbers[n // 2 - 1]
|
|
median = (mid1 + mid2) / 2
|
|
|
|
return median
|