196 lines
5.7 KiB
Python
196 lines
5.7 KiB
Python
from flask_nav.renderers import Renderer, SimpleRenderer
|
|
from dominate import tags
|
|
import asteval
|
|
import operator as op
|
|
import textwrap
|
|
import math
|
|
import sys
|
|
import random
|
|
import string
|
|
from functools import wraps
|
|
from urllib.request import urlopen
|
|
from io import BytesIO
|
|
import subprocess as SP
|
|
import shlex
|
|
import json
|
|
import os
|
|
|
|
from PIL import Image
|
|
from PIL import ImageFont
|
|
from PIL import ImageDraw
|
|
|
|
|
|
def handle_config(cfg=None):
|
|
if cfg is None:
|
|
if os.path.isfile("config.json"):
|
|
with open("config.json") as fh:
|
|
return json.load(fh)
|
|
with open("config.json", "w") as fh:
|
|
cfg = json.dump(cfg, fh, indent=4)
|
|
return
|
|
|
|
|
|
def with_application_context(app):
|
|
def inner(func):
|
|
@wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
with app.app_context():
|
|
return func(*args, **kwargs)
|
|
|
|
return wrapper
|
|
|
|
return inner
|
|
|
|
|
|
def getsize(text, font_size):
|
|
font = ImageFont.truetype("arial.ttf", font_size)
|
|
return font.getsize_multiline(text)
|
|
|
|
|
|
def does_text_fit(text, width, height, font_size):
|
|
w, h = getsize(text, font_size)
|
|
return w < width and h < height
|
|
|
|
|
|
def make_placeholder_image(text, width, height, poster=None, wrap=0):
|
|
width = int(width)
|
|
height = int(height)
|
|
wrap = int(wrap)
|
|
font_size = 1
|
|
bounds = (0, 1)
|
|
if wrap:
|
|
text = textwrap.fill(text, wrap)
|
|
while True:
|
|
if not does_text_fit(text, width, height, bounds[1]):
|
|
break
|
|
bounds = (bounds[1], bounds[1] * 2)
|
|
prev_bounds = None
|
|
while True:
|
|
if does_text_fit(text, width, height, bounds[1]):
|
|
bounds = (int(round(sum(bounds) / 2, 0)), bounds[1])
|
|
else:
|
|
bounds = (bounds[0], int(round(sum(bounds) / 2, 0)))
|
|
if prev_bounds == bounds:
|
|
break
|
|
prev_bounds = bounds
|
|
font_size = bounds[0]
|
|
io = BytesIO()
|
|
im = Image.new("RGBA", (width, height), "#222")
|
|
draw = ImageDraw.Draw(im)
|
|
font = ImageFont.truetype("arial.ttf", font_size)
|
|
w, h = getsize(text, font_size)
|
|
if poster:
|
|
try:
|
|
with urlopen(poster) as fh:
|
|
poster = Image.open(fh)
|
|
except Exception as e:
|
|
poster = None
|
|
else:
|
|
poster_size = poster.size
|
|
factor = width / poster_size[0]
|
|
new_size = (
|
|
math.ceil(poster_size[0] * factor),
|
|
math.ceil(poster_size[1] * factor),
|
|
)
|
|
poster = poster.resize(new_size)
|
|
mid = -int((poster.size[1] - height) / 2)
|
|
im.paste(poster, (0, mid))
|
|
draw.text(((width - w) / 2, (height - h) / 2), text, fill="#eee", font=font)
|
|
im.save(io, "PNG")
|
|
io.seek(0)
|
|
return io
|
|
|
|
|
|
def make_tree(files, child_key="children"):
|
|
tree = {}
|
|
for file in files:
|
|
root = tree
|
|
parts = file["name"].split("/")
|
|
for item in parts:
|
|
if item not in root:
|
|
root[item] = {}
|
|
prev_root = root
|
|
root = root[item]
|
|
prev_root[item] = {"__info__": file}
|
|
return tree
|
|
|
|
|
|
class BootsrapRenderer(Renderer):
|
|
def visit_Navbar(self, node):
|
|
sub = []
|
|
for item in node.items:
|
|
sub.append(self.visit(item))
|
|
ret = tags.ul(sub, cls="navbar-nav mr-auto")
|
|
return ret
|
|
|
|
def visit_View(self, node):
|
|
classes = ["nav-link"]
|
|
if node.active:
|
|
classes.append("active")
|
|
return tags.li(
|
|
tags.a(node.text, href=node.get_url(), cls=" ".join(classes)),
|
|
cls="nav-item",
|
|
)
|
|
|
|
def visit_Subgroup(self, node):
|
|
url = "#"
|
|
classes = []
|
|
child_active = False
|
|
if node.title == "":
|
|
active = False
|
|
for item in node.items:
|
|
if item.active:
|
|
classes.append("active")
|
|
break
|
|
node, *children = node.items
|
|
for c in children:
|
|
if c.active:
|
|
child_active = True
|
|
break
|
|
node.items = children
|
|
node.title = node.text
|
|
url = node.get_url()
|
|
dropdown = tags.ul(
|
|
[
|
|
tags.li(
|
|
tags.a(
|
|
item.text,
|
|
href=item.get_url(),
|
|
cls="nav-link active" if item.active else "nav-link",
|
|
style="",
|
|
),
|
|
cls="nav-item",
|
|
)
|
|
for item in node.items
|
|
],
|
|
cls="dropdown-menu ",
|
|
)
|
|
link = tags.a(
|
|
node.title,
|
|
href=url,
|
|
cls="nav-link active" if node.active else "nav-link",
|
|
style="",
|
|
)
|
|
toggle = tags.a(
|
|
[],
|
|
cls="dropdown-toggle nav-link active"
|
|
if child_active
|
|
else "dropdown-toggle nav-link",
|
|
data_toggle="dropdown",
|
|
href="#",
|
|
style="padding-left: 0px; padding-top: 10px",
|
|
)
|
|
# almost the same as visit_Navbar, but written a bit more concise
|
|
return [link, tags.li([toggle, dropdown], cls="dropdown nav-item")]
|
|
|
|
|
|
def eval_expr(expr, ctx=None):
|
|
aeval = asteval.Interpreter(minimal=True, use_numpy=False, symtable=ctx)
|
|
return aeval(expr)
|
|
|
|
|
|
def sort_by(values, expr):
|
|
return sorted(value, key=lambda v: eval_expr(expr, v))
|
|
|
|
def genpw(num=20):
|
|
return "".join(random.choice(string.ascii_lowercase+string.ascii_uppercase+string.digits) for _ in range(num))
|