Initial commit
This commit is contained in:
commit
3f631fe1ab
|
@ -0,0 +1,8 @@
|
|||
.venv
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
__pycache__
|
||||
*.egg-info
|
||||
.env
|
||||
.ycm_extra_conf.py
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
import pyte
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def dconf_ls(path):
|
||||
output = subprocess.check_output(["dconf", "list", str(path) + "/"])
|
||||
return [path / line for line in output.decode().strip().split("\n")]
|
||||
|
||||
def dconf_read(path):
|
||||
output = subprocess.check_output(["dconf", "read", str(path)])
|
||||
return ast.literal_eval(output.decode())
|
||||
|
||||
def rgb_to_arr(rgb):
|
||||
return "".join(["{0:02x}".format(int(x)) for x in re.findall("[0-9]+", rgb)])
|
||||
|
||||
def get_terminal_conf():
|
||||
profiles = dconf_ls(Path("/org/gnome/terminal/legacy/profiles:/"))
|
||||
bgc = dconf_read(profiles[0] / "background-color")
|
||||
fgc = dconf_read(profiles[0] / "foreground-color")
|
||||
palette = dconf_read(profiles[0] / "palette")
|
||||
return (rgb_to_arr(bgc),
|
||||
rgb_to_arr(fgc),
|
||||
[rgb_to_arr(c) for c in palette])
|
||||
|
||||
def get_screen(filename):
|
||||
width = 0
|
||||
height = 0
|
||||
data = ""
|
||||
with open(filename, "r") as file:
|
||||
hdr = True
|
||||
for strline in file:
|
||||
line = json.loads(strline)
|
||||
if hdr:
|
||||
width = line['width']
|
||||
height = line['height']
|
||||
hdr = False
|
||||
else:
|
||||
data += line[2]
|
||||
# snip out weird escape code (??? what is this, some sort of window title?)
|
||||
data = re.sub(r'\x1b_.*?\x1b\\', "", data)
|
||||
screen = pyte.screens.HistoryScreen(width, height, history=9999999)
|
||||
stream = pyte.Stream(screen)
|
||||
stream.feed(data)
|
||||
lines = [l for l in screen.history.top]
|
||||
for i in range(len(screen.buffer)):
|
||||
lines.append(screen.buffer[i])
|
||||
return (width, height, lines)
|
||||
|
||||
def convert(filename, output):
|
||||
print("Generating latex")
|
||||
(bgc, fgc, palette) = get_terminal_conf()
|
||||
print(bgc, fgc, palette)
|
||||
(w, h, scr)= get_screen(filename)
|
||||
latexsrc = toTikz(scr, w, len(scr), bgc, fgc, palette)
|
||||
print("Compiling")
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmpdir = Path(tmpdir)
|
||||
with (tmpdir / "console.tex").open("w") as file:
|
||||
file.write(latexsrc)
|
||||
subprocess.check_call(["xelatex", "console.tex"], cwd=str(tmpdir))
|
||||
shutil.copyfile(tmpdir / "console.pdf", output)
|
||||
|
||||
# adapted from https://github.com/misc0110/asciicast2vector (MIT License)
|
||||
|
||||
def sanitizeLatexChar(c):
|
||||
if c in "&%$#_{}":
|
||||
return "\\" + c
|
||||
elif c == "~":
|
||||
return "\\textasciitilde"
|
||||
elif c == "^":
|
||||
return "\\textasciicircum"
|
||||
elif c == "\\":
|
||||
return "\\textbackslash"
|
||||
else:
|
||||
return c
|
||||
|
||||
|
||||
def toTikz(lines, max_col, max_row, bgc, fgc, palette):
|
||||
pline = ""
|
||||
|
||||
pline += tikzHeader(lines, bgc, fgc, palette)
|
||||
|
||||
pline += "\\begin{tikzpicture}[yscale=-1]\\ttfamily\n"
|
||||
lspace = 1.8
|
||||
pline += "\\draw[fill=%s,draw=none] (-1em,-1em) rectangle +(%.4fem,%dem);\n" % ("defaultbg", (max_col + 4) / lspace, max_row + 2)
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
for x in range(len(line)):
|
||||
char = line[x]
|
||||
if char.data == ' ' and ((char.reverse == False and char.bg == "default") or (char.reverse and char.fg == "default")):
|
||||
continue
|
||||
|
||||
if char.reverse:
|
||||
fg = char.bg
|
||||
bg = char.fg
|
||||
else:
|
||||
fg = char.fg
|
||||
bg = char.bg
|
||||
|
||||
if fg == "default":
|
||||
fg = "defaultfg"
|
||||
if bg == "default":
|
||||
bg = "defaultbg"
|
||||
|
||||
content = sanitizeLatexChar(char.data)
|
||||
if char.bold:
|
||||
content = "\\textbf{" + content + "}"
|
||||
if char.italics:
|
||||
content = "\\textit{" + content + "}"
|
||||
|
||||
pline += "\\draw[fill=%s,draw=none] (%.4fem,%dem) rectangle +(0.6em,1em) node[pos=.5, anchor=base, yshift=-0.5ex] {\\textcolor{%s}{%s}};\n" % (bg, x / lspace, y, fg, content)
|
||||
|
||||
pline += "\\end{tikzpicture}"
|
||||
|
||||
pline += tikzFooter()
|
||||
|
||||
return pline
|
||||
|
||||
def color2tex(color, name=None):
|
||||
if name is None:
|
||||
name = color
|
||||
line = ""
|
||||
line += "\\definecolor{" + name + "}{RGB}{"
|
||||
line += str(int(color[:2], 16)) + ","
|
||||
line += str(int(color[2:4], 16)) + ","
|
||||
line += str(int(color[4:], 16)) + "}\n"
|
||||
return line
|
||||
|
||||
def tikzColors(lines, bgc, fgc, palette):
|
||||
line = ""
|
||||
line += color2tex(bgc, "defaultbg")
|
||||
line += color2tex(fgc, "defaultfg")
|
||||
for i,c in enumerate(["black", "red", "green", "brown", "blue", "magenta", "cyan", "white"]):
|
||||
line += color2tex(palette[i], c)
|
||||
colors = set(["default", "black", "red", "green", "brown", "blue", "magenta", "cyan", "white"])
|
||||
for y in range(len(lines)):
|
||||
_line = lines[y]
|
||||
for x in range(len(_line)):
|
||||
char = _line[x]
|
||||
if not char.bg in colors:
|
||||
colors.add(char.bg)
|
||||
line += color2tex(char.bg)
|
||||
if not char.fg in colors:
|
||||
colors.add(char.fg)
|
||||
line += color2tex(char.fg)
|
||||
|
||||
return line
|
||||
|
||||
def tikzHeader(lines, bgc, fgc, palette):
|
||||
line = "\\documentclass[class=minimal,border=0pt]{standalone}\n"
|
||||
line += "\\usepackage[utf8]{inputenc}\n"
|
||||
line += "\\usepackage{tikz}\n"
|
||||
line += "\\usepackage[utf8]{inputenc}\n"
|
||||
line += "\\usepackage{fontspec}\n"
|
||||
line += "\\setmonofont[Ligatures=TeX]{Hack Regular}\n"
|
||||
# line += "\\usetikzlibrary{external}\n"
|
||||
# line += "\\tikzexternalize\n"
|
||||
line += "\\usepackage{xcolor}\n"
|
||||
line += "\\usepackage{amssymb}\n"
|
||||
line += "\\usepackage{pmboxdraw}\n"
|
||||
line += tikzColors(lines, bgc, fgc, palette)
|
||||
line += "\\begin{document}\n"
|
||||
return line
|
||||
|
||||
def tikzFooter():
|
||||
return "\\end{document}\n"
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: asciicast2latex <cast file> <pdf output>")
|
||||
sys.exit(-1)
|
||||
convert(sys.argv[1], sys.argv[2])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,20 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(name='asciicast2latex',
|
||||
version='0.1',
|
||||
description='Converts asciinema casts to latex (and compiles to pdf)',
|
||||
url='https://notabug.org/haskal/asciicast2latex',
|
||||
author='haskal',
|
||||
author_email='haskal@bepis.xyz',
|
||||
license='GPLv3',
|
||||
packages=['asciicast2latex'],
|
||||
install_requires=[
|
||||
"pyte"
|
||||
],
|
||||
include_package_data=True,
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'asciicast2latex=asciicast2latex:main'
|
||||
]
|
||||
},
|
||||
zip_safe=False)
|
Loading…
Reference in New Issue