745 lines
19 KiB
Python
Executable file
745 lines
19 KiB
Python
Executable file
#! /usr/bin/env python3
|
|
# encoding: utf-8
|
|
|
|
import datetime
|
|
import os
|
|
import pwd
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
COLOR_LOAD_IDLE = 10
|
|
COLOR_LOAD_OK = 2
|
|
COLOR_LOAD_WARN = 3
|
|
COLOR_LOAD_ERROR = 1
|
|
COLOR_USER_USER = 10
|
|
COLOR_USER_DAEMON = 32
|
|
COLOR_USER_ROOT = 196
|
|
COLOR_SESSION_LOCAL = 7
|
|
COLOR_SESSION_REMOTE = 202
|
|
COLOR_TERM_SCREEN = 27
|
|
|
|
COLOR_CLOCK = 27
|
|
COLOR_CLOCK_DELTA = 11
|
|
COLOR_MEMSWAP = 7
|
|
COLOR_SPACE_BTRFS = 13
|
|
COLOR_SPACE_OK = 2
|
|
COLOR_SPACE_WARN = 3
|
|
COLOR_SPACE_ERROR = 1
|
|
|
|
COLOR_DIR_RW = 34
|
|
COLOR_DIR_RO = 202
|
|
COLOR_DIR_NO = 196
|
|
|
|
COLOR_GIT_CLEAN = 10
|
|
COLOR_GIT_DIRTY = 1
|
|
COLOR_GIT_MERGE = 196
|
|
COLOR_GIT_UNTRACKED = 202
|
|
COLOR_GIT_MODIFIED = 1
|
|
COLOR_GIT_STAGED = 10
|
|
|
|
COLOR_PROMPT_OK = 14
|
|
COLOR_PROMPT_ERROR = 1
|
|
|
|
COLOR_OK = 10
|
|
COLOR_ERROR = 196
|
|
COLOR_GREY = 240
|
|
|
|
MOUNT_IGNORE_FS = ["iso9660", "tmpfs", "rootfs"]
|
|
MOUNT_IGNORE_DIR = ["/dev", "/proc", "/sys"]
|
|
|
|
def get_username():
|
|
return pwd.getpwuid(os.geteuid())[0]
|
|
|
|
def style_color(fg):
|
|
return "\033[38;5;%dm" %(fg)
|
|
|
|
def style_bold():
|
|
return "\033[1m"
|
|
|
|
def style_invert():
|
|
return "\033[7m"
|
|
|
|
def style_reset():
|
|
return "\033[0m"
|
|
|
|
def colored_strlen(raw):
|
|
"""
|
|
Returns string length without ANSI control escapes.
|
|
"""
|
|
|
|
l = 0
|
|
reading_escape = False
|
|
|
|
for c in raw:
|
|
if c == "\x1b":
|
|
reading_escape = True
|
|
continue
|
|
if reading_escape and c == "m":
|
|
reading_escape = False
|
|
continue
|
|
|
|
if not reading_escape:
|
|
l += 1
|
|
|
|
return l
|
|
|
|
def si_number(raw):
|
|
if raw > 2**40:
|
|
raw /= 2**40
|
|
si = "Ti"
|
|
elif raw > 2**30:
|
|
raw /= 2**30
|
|
si = "Gi"
|
|
elif raw > 2**20:
|
|
raw /= 2**20
|
|
si = "Mi"
|
|
elif raw > 2**10:
|
|
raw /= 2**10
|
|
si = "ki"
|
|
else:
|
|
si = ""
|
|
|
|
return (raw, si)
|
|
|
|
def space_string(free):
|
|
"""
|
|
Returns a formatting string suitable for printing out free space: float for >1k, int otherwise.
|
|
"""
|
|
|
|
return "%.1f%s" if free > 1024 else "%d%s"
|
|
|
|
def timestamp_to_hms(t):
|
|
"""
|
|
Converts a duration in seconds to a [[?h ]?m ]?s representation.
|
|
|
|
>>> timestamp_to_hms(12345)
|
|
'3h 25m 45s'
|
|
|
|
>>> timestamp_to_hms(3600)
|
|
'1h 0m 0s'
|
|
|
|
>>> timestamp_to_hms(1234)
|
|
'20m 34s'
|
|
|
|
>>> timestamp_to_hms(12)
|
|
'12s'
|
|
"""
|
|
|
|
hh = t // 3600
|
|
mm = (t % 3600) // 60
|
|
ss = t % 60
|
|
|
|
out = []
|
|
|
|
if hh:
|
|
out.append("%dh" %(hh))
|
|
|
|
if mm or hh:
|
|
out.append("%dm" %(mm))
|
|
|
|
out.append("%ds" %(ss))
|
|
|
|
return " ".join(out)
|
|
|
|
class Sysload:
|
|
def __init__(self):
|
|
loadavg = open("/proc/loadavg").read().split()
|
|
cores = os.sysconf(os.sysconf_names["SC_NPROCESSORS_ONLN"])
|
|
|
|
self.load1 = float(loadavg[0]) / cores
|
|
self.load5 = float(loadavg[1]) / cores
|
|
self.load15 = float(loadavg[2]) / cores
|
|
self.tasks_ready, self.tasks_total = [int(x) for x in loadavg[3].split("/")]
|
|
|
|
class Settings:
|
|
def __init__(self, argv):
|
|
try:
|
|
self.term_width = int(argv[1])
|
|
self.cwd = argv[2]
|
|
self.last_refresh_time = int(argv[3]) if argv[3] else None
|
|
self.load_warn = float(argv[4])
|
|
self.load_error = float(argv[5])
|
|
self.space_warn = float(argv[6])
|
|
self.space_error = float(argv[7])
|
|
self.cmd_start_time = int(argv[8]) if argv[8] else None
|
|
self.is_root = os.geteuid() == 0
|
|
self.is_daemon = os.geteuid() < 1000
|
|
self.horizontal_sep = "--no-horizontal-line" not in argv
|
|
except:
|
|
print("Usage: %s $COLUMNS $PWD last_refresh_time load_warn load_error space_warn space_error")
|
|
print(" e.g. %s 1 $COLUMNS $PWD 25 1.25 2.5 0.15 0.05")
|
|
print()
|
|
|
|
raise
|
|
|
|
def get_load_color(self, load):
|
|
if load > self.load_error:
|
|
return COLOR_LOAD_ERROR
|
|
elif load > self.load_warn:
|
|
return COLOR_LOAD_WARN
|
|
elif load > 0.1:
|
|
return COLOR_LOAD_OK
|
|
else:
|
|
return COLOR_LOAD_IDLE
|
|
|
|
def get_space_color(self, free, total):
|
|
free = free / total
|
|
|
|
if free < self.space_error:
|
|
return COLOR_SPACE_ERROR
|
|
elif free < self.space_warn:
|
|
return COLOR_SPACE_WARN
|
|
else:
|
|
return COLOR_SPACE_OK
|
|
|
|
class Part:
|
|
def __init__(self):
|
|
self.fragments = []
|
|
|
|
def __str__(self):
|
|
return "".join(self.fragments)
|
|
|
|
def __len__(self):
|
|
return colored_strlen(str(self))
|
|
|
|
class LoginPart(Part):
|
|
"""
|
|
username@hostname:screen
|
|
- username is green for users, red for root
|
|
- @ is a literal "@" if this is a local session, " ⇄ " when remote (ssh)
|
|
- hostname is colored according to sysload
|
|
- screen is the screen window ID if available
|
|
"""
|
|
|
|
def __init__(self, settings, sysload):
|
|
Part.__init__(self)
|
|
is_remote = bool(os.getenv("SSH_CLIENT"))
|
|
screen = os.getenv("WINDOW")
|
|
|
|
# username
|
|
if settings.is_root:
|
|
self.fragments.append(style_color(COLOR_USER_ROOT))
|
|
elif settings.is_daemon:
|
|
self.fragments.append(style_color(COLOR_USER_DAEMON))
|
|
else:
|
|
self.fragments.append(style_color(COLOR_USER_USER))
|
|
|
|
self.fragments.append(style_bold())
|
|
user = get_username()
|
|
self.fragments.append(user)
|
|
self.fragments.append(style_reset())
|
|
|
|
# sign
|
|
if is_remote:
|
|
self.fragments.append(style_color(COLOR_SESSION_REMOTE))
|
|
else:
|
|
self.fragments.append(style_color(COLOR_SESSION_LOCAL))
|
|
|
|
sign = " ⮂ " if is_remote else "@"
|
|
self.fragments.append(sign)
|
|
|
|
# hostname
|
|
self.fragments.append(style_color(settings.get_load_color(sysload.load1)))
|
|
hostname = socket.gethostname()
|
|
self.fragments.append(hostname)
|
|
|
|
# screen window ID
|
|
if screen:
|
|
self.fragments.append(style_reset())
|
|
self.fragments.append(":")
|
|
self.fragments.append(style_bold())
|
|
self.fragments.append(style_color(COLOR_TERM_SCREEN))
|
|
self.fragments.append(screen)
|
|
|
|
self.fragments.append(style_reset())
|
|
|
|
def list_homes():
|
|
usernames = []
|
|
home_dirs = []
|
|
|
|
if os.path.exists("/etc/passwd"):
|
|
with open("/etc/passwd") as f:
|
|
for line in f:
|
|
tokens = line.split(":")
|
|
|
|
if tokens[5] != "/dev/null":
|
|
if tokens[5] not in home_dirs:
|
|
usernames.append(tokens[0])
|
|
home_dirs.append(tokens[5])
|
|
|
|
return list(zip(home_dirs, usernames))
|
|
|
|
class Dir:
|
|
def __init__(self, path, name=None, slash="/"):
|
|
self.path = path
|
|
self.slash = slash
|
|
self.truncated = False
|
|
self.void = False
|
|
self.name = os.path.basename(path) if name is None else name
|
|
|
|
if os.access(path, os.W_OK):
|
|
self.color = COLOR_DIR_RW
|
|
elif os.access(path, os.X_OK):
|
|
self.color = COLOR_DIR_RO
|
|
else:
|
|
self.color = COLOR_DIR_NO
|
|
|
|
if os.path.islink(path) and os.path.realpath(path).startswith("/dev/shm/void"):
|
|
self.name = "«" + self.name + "»"
|
|
self.void = True
|
|
|
|
def __len__(self):
|
|
return len(self.name) + len(self.slash)
|
|
|
|
def truncate(self):
|
|
self.truncated = True
|
|
|
|
if self.name:
|
|
self.name = self.name[0] + "…"
|
|
|
|
def __str__(self):
|
|
output = [self.slash]
|
|
|
|
if self.truncated:
|
|
output.append(style_bold())
|
|
|
|
if self.void:
|
|
output.append(style_invert())
|
|
|
|
output.append(style_color(self.color))
|
|
output.append(self.name)
|
|
|
|
output.append(style_reset())
|
|
|
|
return "".join(output)
|
|
|
|
class PathPart(Part):
|
|
"""
|
|
/path/to/cwd
|
|
|
|
Contructs a list of Dirs from the root to CWD. When shrink_fit is called, some Dirs may be shortened.
|
|
"""
|
|
|
|
def __init__(self, settings, overloaded):
|
|
Part.__init__(self)
|
|
self.term_width = settings.term_width
|
|
self.dirs = []
|
|
|
|
homes = {} if overloaded else dict(list_homes())
|
|
dirs = settings.cwd.split("/")[1:]
|
|
path = ""
|
|
|
|
for dir in dirs:
|
|
path += "/" + dir
|
|
|
|
if path in homes:
|
|
self.dirs = []
|
|
|
|
if homes[path] == get_username():
|
|
self.dirs.append(Dir(path, "", "~"))
|
|
else:
|
|
self.dirs.append(Dir(path, homes[path], "~"))
|
|
|
|
else:
|
|
self.dirs.append(Dir(path))
|
|
|
|
@property
|
|
def full_length(self):
|
|
return sum(len(dir) for dir in self.dirs)
|
|
|
|
def shrink_fit(self, line):
|
|
self.fragments = []
|
|
|
|
available_space = self.term_width - sum(len(part) for part in line if part is not self) - len(line) + 1 # account for single-space separators
|
|
|
|
for dir in self.dirs:
|
|
if self.full_length > available_space:
|
|
dir.truncate()
|
|
|
|
self.fragments.append(str(dir))
|
|
|
|
def command(cmd, timeout=0.5):
|
|
"""
|
|
Executes a command, returns stdout, suppresses stderr.
|
|
|
|
Kills the process on timeout.
|
|
"""
|
|
|
|
if type(cmd) == str:
|
|
cmd = cmd.split()
|
|
try:
|
|
s = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
s.wait(timeout)
|
|
except subprocess.TimeoutExpired:
|
|
s.kill()
|
|
raise
|
|
|
|
return s.stdout.read().decode("utf-8")
|
|
|
|
class VirtualEnvPart(Part):
|
|
"""
|
|
⬢ path
|
|
|
|
Displays the current python virtualenv's path iff set.
|
|
"""
|
|
|
|
def __init__(self):
|
|
Part.__init__(self)
|
|
|
|
virtualenv = os.getenv("VIRTUAL_ENV")
|
|
|
|
if virtualenv:
|
|
self.fragments.append("⬢ ")
|
|
self.fragments.append(style_color(COLOR_OK))
|
|
parts = virtualenv.split("/")
|
|
|
|
if parts[-1] == "env" and len(parts) > 1:
|
|
venv_name = parts[-2]
|
|
else:
|
|
venv_name = parts[-1]
|
|
|
|
self.fragments.append(venv_name)
|
|
self.fragments.append(style_reset())
|
|
|
|
class VoidPart(Part):
|
|
"""
|
|
∅ name
|
|
|
|
Displays the name(s) of the VOIDs held by the current shell and its ancestors.
|
|
"""
|
|
|
|
def __init__(self):
|
|
Part.__init__(self)
|
|
|
|
voids = os.getenv("OVER_PROMPT_VOIDS")
|
|
|
|
if voids:
|
|
self.fragments.append("∅ ")
|
|
|
|
for void in voids.split(":"):
|
|
if void:
|
|
self.fragments.append(style_color(COLOR_OK))
|
|
self.fragments.append(void)
|
|
self.fragments.append(style_reset())
|
|
|
|
class WineprefixPart(Part):
|
|
"""
|
|
♿ name:[32|64]
|
|
|
|
Displays the current WINEPREFIX (iff set) basename and x86 subarchitecture (32 or 64).
|
|
|
|
The basename is green if the directory exists, red otherwise.
|
|
"""
|
|
|
|
def __init__(self):
|
|
Part.__init__(self)
|
|
|
|
wineprefix = os.getenv("WINEPREFIX")
|
|
arch = os.getenv("WINEARCH")
|
|
|
|
if wineprefix:
|
|
self.fragments.append(" ")
|
|
|
|
self.fragments.append(style_color(COLOR_OK if os.path.isdir(wineprefix) else COLOR_ERROR))
|
|
self.fragments.append(os.path.basename(wineprefix))
|
|
self.fragments.append(style_reset())
|
|
|
|
if arch == "win32":
|
|
self.fragments.append(":32")
|
|
elif arch:
|
|
self.fragments.append(":")
|
|
self.fragments.append(style_color(COLOR_ERROR))
|
|
self.fragments.append("?")
|
|
self.fragments.append(style_reset())
|
|
else:
|
|
self.fragments.append(":64")
|
|
|
|
class TimeBudget:
|
|
def __init__(self, budget, min=0.1):
|
|
self.end = time.monotonic() + budget
|
|
self.min = min
|
|
|
|
@property
|
|
def trem(self):
|
|
return max(self.min, self.end - time.monotonic())
|
|
|
|
class GitPart(Part):
|
|
"""
|
|
branchname ↘2 ↗4 !M master ?11 *6 s10
|
|
|
|
- 2 commits are available for pulling (remote is ahead)
|
|
- 4 commits are available for pushing (local is ahead)
|
|
- we're merging
|
|
- branch name is master
|
|
- 11 untracked files
|
|
- 6 modified files
|
|
- 10 modified and staged files
|
|
|
|
Numeric elements are hidden if the value is 0.
|
|
"""
|
|
|
|
def __init__(self):
|
|
Part.__init__(self)
|
|
tb = TimeBudget(1) # don't spend more than a second here
|
|
|
|
try:
|
|
branch_name = command("git name-rev --name-only --no-undefined --always HEAD", tb.trem).strip()
|
|
|
|
if branch_name:
|
|
count_to_pull = command("git log --oneline ..@{u}", tb.trem).count("\n")
|
|
count_to_push = command("git log --oneline @{u}..", tb.trem).count("\n")
|
|
git_dir = command("git rev-parse --git-dir", tb.trem).strip()
|
|
merging = os.path.exists(os.path.join(git_dir, "MERGE_HEAD"))
|
|
untracked = command("git ls-files --other --exclude-standard", tb.trem).count("\n")
|
|
modified = command("git diff --name-only", tb.trem).count("\n")
|
|
staged = command("git diff --name-only --staged", tb.trem).count("\n")
|
|
|
|
if count_to_pull:
|
|
self.fragments.append("↘%d " %(count_to_pull))
|
|
|
|
if count_to_push:
|
|
self.fragments.append("↗%d " %(count_to_push))
|
|
|
|
if merging:
|
|
self.fragments.append(style_color(COLOR_GIT_MERGE))
|
|
self.fragments.append(style_bold())
|
|
self.fragments.append("M ")
|
|
elif modified or staged:
|
|
self.fragments.append(style_color(COLOR_GIT_DIRTY))
|
|
else:
|
|
self.fragments.append(style_color(COLOR_GIT_CLEAN))
|
|
|
|
self.fragments.append(branch_name)
|
|
self.fragments.append(style_reset())
|
|
|
|
if untracked:
|
|
self.fragments.append(style_color(COLOR_GIT_UNTRACKED))
|
|
self.fragments.append(" ?%d" %(untracked))
|
|
|
|
if modified:
|
|
self.fragments.append(style_color(COLOR_GIT_MODIFIED))
|
|
self.fragments.append(" *%d" %(modified))
|
|
|
|
if staged:
|
|
self.fragments.append(style_color(COLOR_GIT_STAGED))
|
|
self.fragments.append(" s%d" %(staged))
|
|
|
|
self.fragments.append(style_reset())
|
|
|
|
except subprocess.TimeoutExpired:
|
|
self.fragments = []
|
|
self.fragments.append(style_color(COLOR_GIT_DIRTY))
|
|
self.fragments.append("!! git timeout !!")
|
|
self.fragments.append(style_reset())
|
|
|
|
class Padding(Part):
|
|
def __init__(self, term_width):
|
|
Part.__init__(self)
|
|
self.term_width = term_width
|
|
|
|
def expand_fit(self, line):
|
|
length = self.term_width - sum(len(part) for part in line if part is not self) - len(line) + 1 # account for single-space separators
|
|
|
|
self.fragments = [" " * length]
|
|
|
|
class StatsPart(Part):
|
|
"""
|
|
[ [duration @] [Δx] clock | sysload memory[+swap] | mountpoint_spaces...
|
|
- duration is shown if > 5s
|
|
- delta is shown if the last prompt refresh was before midnight, and displays how many midnights elapsed since
|
|
- clock shows refresh time (HH:MM)
|
|
- sysload is current loadavg divided by amount of cores
|
|
- memory shows current free memory and swap, if available
|
|
- mountpoint_spaces list one free space per mountpoint
|
|
"""
|
|
def __init__(self, settings, sysload):
|
|
Part.__init__(self)
|
|
|
|
self.fragments.append("[ ")
|
|
self.fragments.append(style_bold())
|
|
|
|
now = datetime.datetime.now()
|
|
|
|
# last command duration
|
|
if settings.cmd_start_time:
|
|
duration = (now.timestamp() - settings.cmd_start_time)
|
|
|
|
if duration > 5:
|
|
self.fragments.append("%s " %(timestamp_to_hms(duration)))
|
|
self.fragments.append("@ ")
|
|
|
|
# delta
|
|
if settings.last_refresh_time:
|
|
last_cmd_midnight = datetime.datetime.fromtimestamp(settings.last_refresh_time).replace(hour=0, minute=0, second=0, microsecond=0)
|
|
last_midnight = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
midnights = (last_midnight - last_cmd_midnight).days
|
|
|
|
if midnights:
|
|
self.fragments.append(style_color(COLOR_CLOCK_DELTA))
|
|
self.fragments.append("Δ%d " %(midnights))
|
|
|
|
# clock
|
|
self.fragments.append(datetime.datetime.now().strftime("\033[38;5;%dm%%H:%%M\033[0m | " %(COLOR_CLOCK)))
|
|
|
|
# sysload
|
|
self.fragments.append(style_color(settings.get_load_color(sysload.load1)))
|
|
self.fragments.append("%.1f " %(sysload.load1))
|
|
self.fragments.append(style_reset())
|
|
|
|
if sysload.load1 >= settings.load_error:
|
|
self.fragments.append(" | ")
|
|
self.fragments.append(style_color(COLOR_ERROR))
|
|
self.fragments.append(style_bold())
|
|
self.fragments.append("!! OVERLOAD !! ")
|
|
self.fragments.append(style_reset())
|
|
|
|
else:
|
|
# memory (and swap, if used)
|
|
mem_total = 0
|
|
mem_free = 0
|
|
swap_total = 0
|
|
swap_free = 0
|
|
|
|
with open("/proc/meminfo") as f:
|
|
for line in f:
|
|
if line.startswith("MemTotal"):
|
|
mem_total = int(line.split()[1]) * 1024
|
|
elif line.startswith("MemAvailable"):
|
|
mem_free = int(line.split()[1]) * 1024
|
|
elif line.startswith("SwapTotal"):
|
|
swap_total = int(line.split()[1]) * 1024
|
|
elif line.startswith("SwapFree"):
|
|
swap_free = int(line.split()[1]) * 1024
|
|
|
|
self.fragments.append(style_bold())
|
|
self.fragments.append(style_color(settings.get_space_color(mem_free, mem_total)))
|
|
mem_free_si = si_number(mem_free)
|
|
self.fragments.append(space_string(mem_free) %(mem_free_si[0], mem_free_si[1]))
|
|
|
|
self.fragments.append(style_reset())
|
|
|
|
if swap_total:
|
|
self.fragments.append("+")
|
|
self.fragments.append(style_color(settings.get_space_color(swap_free, swap_total)))
|
|
swap_free_si = si_number(swap_free)
|
|
self.fragments.append(space_string(swap_free) %(swap_free_si[0], swap_free_si[1]))
|
|
self.fragments.append(style_reset())
|
|
|
|
# mountpoints
|
|
names = []
|
|
first_mountpoint = True
|
|
btrfs_encountered_devices = set()
|
|
|
|
if os.path.isdir("/dev/disk/by-label"):
|
|
device_to_label = {os.path.join("/dev", os.path.basename(os.readlink(os.path.join("/dev/disk/by-label", x)))): x for x in os.listdir("/dev/disk/by-label")}
|
|
else:
|
|
device_to_label = {}
|
|
|
|
with open("/proc/self/mounts") as f:
|
|
for line in f:
|
|
device, dir, type, options, *rest = line.split()
|
|
|
|
# skip non-storage mounts
|
|
if type in MOUNT_IGNORE_FS:
|
|
continue
|
|
|
|
if any([dir.startswith(d) for d in MOUNT_IGNORE_DIR]):
|
|
continue
|
|
|
|
if "rw" not in options.split(","):
|
|
continue
|
|
|
|
# /proc/self/mounts uses a literal \040 string to escape spaces
|
|
dir = dir.replace("\\040", " ")
|
|
|
|
basename = os.path.basename(dir)
|
|
|
|
if basename:
|
|
short_name = " "
|
|
|
|
for c in basename:
|
|
short_name += c
|
|
|
|
if short_name not in names:
|
|
break
|
|
else:
|
|
short_name = " /"
|
|
|
|
# handle btrfs subvolumes
|
|
if type == "btrfs":
|
|
if device in btrfs_encountered_devices:
|
|
continue
|
|
|
|
btrfs_encountered_devices.add(device)
|
|
try:
|
|
btrfs_raw = command(["/sbin/btrfs", "fi", "usage", "-b", dir])
|
|
|
|
for line in btrfs_raw.split("\n"):
|
|
if "Device size" in line:
|
|
stor_total = int(line.split()[-1])
|
|
elif "Free" in line:
|
|
stor_free = int(line.split()[-3])
|
|
break
|
|
except subprocess.TimeoutExpired:
|
|
stor_total = 1
|
|
stor_free = 0
|
|
|
|
else:
|
|
try:
|
|
stat = os.statvfs(dir)
|
|
except PermissionError:
|
|
continue
|
|
|
|
stor_total = stat.f_blocks * stat.f_bsize
|
|
stor_free = stat.f_bavail * stat.f_bsize
|
|
|
|
# ignore virtual filesystems
|
|
if stor_total == 0:
|
|
continue
|
|
|
|
if first_mountpoint:
|
|
self.fragments.append(" |")
|
|
first_mountpoint = False
|
|
|
|
if type == "btrfs": self.fragments.append(style_color(COLOR_SPACE_BTRFS))
|
|
self.fragments.append(short_name)
|
|
if type == "btrfs": self.fragments.append(style_reset())
|
|
self.fragments.append(style_bold())
|
|
self.fragments.append(style_color(settings.get_space_color(stor_free, stor_total)))
|
|
stor_free_si = si_number(stor_free)
|
|
self.fragments.append(space_string(stor_free) %(stor_free_si[0], stor_free_si[1]))
|
|
self.fragments.append(style_reset())
|
|
|
|
def make_horizontal_line(settings):
|
|
if settings.horizontal_sep:
|
|
return style_color(COLOR_GREY) + "─" * settings.term_width + "\n"
|
|
|
|
if __name__ == "__main__":
|
|
settings = Settings(sys.argv)
|
|
sysload = Sysload()
|
|
overloaded = sysload.load1 >= settings.load_error
|
|
|
|
hr = make_horizontal_line(settings)
|
|
if hr:
|
|
sys.stderr.write(hr)
|
|
|
|
lp = LoginPart(settings, sysload)
|
|
pp = PathPart(settings, overloaded)
|
|
gp = "" if overloaded else GitPart()
|
|
vep = VirtualEnvPart()
|
|
vop = VoidPart()
|
|
wp = WineprefixPart()
|
|
pad = Padding(settings.term_width)
|
|
sp = StatsPart(settings, sysload)
|
|
|
|
line = [lp, pp, gp, vep, vop, wp, pad, sp]
|
|
|
|
pp.shrink_fit(line)
|
|
pad.expand_fit(line)
|
|
|
|
line_str = " ".join(str(part) for part in line)
|
|
sys.stderr.write(line_str)
|
|
sys.stderr.write("\n")
|
|
sys.stderr.flush()
|