monolisa-nerdfont-patch/bin/patch-monolisa

221 lines
6.2 KiB
Python
Executable file

#!/usr/bin/env python3
import argparse
import itertools
import os
import shlex
import subprocess
import sys
import threading
import time
from pathlib import Path
from typing import List, Set
# EXTS = ["otf", "ttf", "woff", "woff2"]
class Color:
def __init__(self):
self.red = "\033[1;31m"
self.green = "\033[1;32m"
self.yellow = "\033[1;33m"
self.magenta = "\033[1;35m"
self.cyan = "\033[1;36m"
self.end = "\033[0m"
if os.getenv("NO_COLOR"):
for attr in self.__dict__:
setattr(self, attr, "")
class Spinner:
# https://raw.githubusercontent.com/Tagar/stuff/master/spinner.py
def __init__(self, message, delay=0.1):
# self.spinner = itertools.cycle(["-", "/", "|", "\\"])
self.spinner = itertools.cycle([f"{c} " for c in "⣾⣽⣻⢿⡿⣟⣯⣷"])
self.delay = delay
self.busy = False
self.spinner_visible = False
sys.stdout.write(message)
def write_next(self):
with self._screen_lock:
if not self.spinner_visible:
sys.stdout.write(next(self.spinner))
self.spinner_visible = True
sys.stdout.flush()
def remove_spinner(self, cleanup=False):
with self._screen_lock:
if self.spinner_visible:
# sys.stdout.write("\b")
sys.stdout.write("\b\b\b")
self.spinner_visible = False
if cleanup:
sys.stdout.write(" ") # overwrite spinner with blank
# sys.stdout.write("\r") # move to next line
sys.stdout.write("\r\033[K")
sys.stdout.flush()
def spinner_task(self):
while self.busy:
self.write_next()
time.sleep(self.delay)
self.remove_spinner()
def __enter__(self):
if sys.stdout.isatty():
self._screen_lock = threading.Lock()
self.busy = True
self.thread = threading.Thread(target=self.spinner_task)
self.thread.start()
def __exit__(self, exc_type, exc_val, exc_traceback):
if sys.stdout.isatty():
self.busy = False
self.remove_spinner(cleanup=True)
else:
sys.stdout.write("\r")
def run_cmd(
command: str, fontfile: Path, verbose: bool, ignore_error: bool = False
) -> None:
"""run a subcommand
Args:
command: Subcommand to be run in subprocess.
fontfile: Path to font file that will be patched
verbose: If true, print subcommand output.
"""
p = subprocess.run(
shlex.split(command),
stdout=None if verbose else subprocess.PIPE,
stderr=None if verbose else subprocess.STDOUT,
universal_newlines=True,
)
if p.returncode != 0 and not ignore_error:
print()
print(p.stdout)
err_msg = (
f"{color.red}ERROR{color.end}: patching font file "
f"{fontfile.name} see above for font-patcher output"
)
echo(err_msg, hue="red")
sys.exit(1)
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument(
"-f",
"--font-path",
help="path to font files",
action="append",
type=Path,
required=True,
)
parser.add_argument(
"-o", "--output", help="target output directory", default="patched", type=Path
)
parser.add_argument(
"-d", "--docker", help="use nerdfonts/patcher docker image", action="store_true"
)
parser.add_argument(
"-v", "--verbose", help="show subprocess output", action="store_true"
)
return parser.parse_known_args()
def patch_single_font(
font_path: Path, output_dir: Path, fp_args: str, verbose: bool
) -> None:
display_name = f"{font_path.parent.name}/{font_path.name}"
output_path = output_dir / font_path.parent.name.replace("MonoLisa/", "")
output_path.mkdir(exist_ok=True)
cmd = (
"fontforge -script "
f"./bin/font-patcher {font_path} "
"--glyphdir ./src/glyphs/ "
f"-o {output_path} "
f"{fp_args}"
)
if verbose:
echo(f"cmd: {cmd}")
run_cmd(cmd, font_path, verbose)
else:
with Spinner(f"{color.yellow}:::{color.end} Patching {display_name}... "):
run_cmd(cmd, font_path, verbose)
echo(f"{display_name} patched!", hue="green")
def collect_files_by_dir(fontfiles: List[Path]) -> Set[Path]:
return set([f.parent for f in fontfiles])
def patch_font_dir_docker(
font_dir_path: Path, output_dir: Path, fp_args: str, verbose: bool
) -> None:
font_dir_path = font_dir_path.resolve()
output_path = (output_dir / font_dir_path.name).resolve()
output_path.mkdir(exist_ok=True)
cmd = (
"docker run --rm "
f"-v '{font_dir_path}:/in' "
f"-v '{output_path}:/out' "
f" -u '{os.getuid()}:{os.getegid()}' "
"nerdfonts/patcher "
f"{fp_args}"
)
# ignoring the fact that docker exits with code 1
if verbose:
echo(f"cmd: {cmd}")
run_cmd(cmd, font_dir_path, verbose, ignore_error=True)
else:
with Spinner(
f"{color.yellow}:::{color.end} Patching fonts in {font_dir_path.name}... "
):
run_cmd(cmd, font_dir_path, verbose, ignore_error=True)
echo(f"{font_dir_path.name}/ fonts patched!", hue="green")
def echo(msg: str, header=False, hue="cyan") -> None:
if header:
print(f"==>{color.magenta} {msg} {color.end}<==")
else:
print(f"{color.__dict__[hue]}::{color.end} {msg}")
def main():
echo("MonoLisa NerdFont Patcher", header=True)
args, fp_args = get_args()
fp_args = " ".join(fp_args)
if fp_args:
echo(f"Flags passed to font-patcher: {fp_args}")
if args.docker:
echo("==> DOCKER MODE ENABLED")
for font_dir in collect_files_by_dir(args.font_path):
patch_font_dir_docker(font_dir, args.output, fp_args, args.verbose)
else:
for fontfile in args.font_path:
patch_single_font(Path(fontfile), args.output,
fp_args, args.verbose)
echo("fonts are patched", hue="green")
echo("Happy typing!", hue="green")
if __name__ == "__main__":
color = Color()
main()