NDOJ/judge/jinja2/reference.py
2023-10-10 19:37:36 -05:00

201 lines
5.5 KiB
Python

import re
from collections import defaultdict
from urllib.parse import urljoin
from ansi2html import Ansi2HTMLConverter
from django.contrib.auth.models import AbstractUser
from django.urls import reverse
from django.utils.safestring import mark_safe
from lxml.html import Element
from judge import lxml_tree
from judge.models import Contest, Problem, Profile
from judge.ratings import rating_class, rating_progress
from . import registry
rereference = re.compile(r"\[(r?user):(\w+)\]")
def get_user(username, data):
if not data:
element = Element("span")
element.text = username
return element
element = Element("span", {"class": Profile.get_user_css_class(*data)})
link = Element("a", {"href": reverse("user_page", args=[username])})
link.text = username
element.append(link)
return element
def get_user_rating(username, data):
if not data:
element = Element("span")
element.text = username
return element
rating = data[1]
element = Element(
"a", {"class": "rate-group", "href": reverse("user_page", args=[username])}
)
if rating:
rating_css = rating_class(rating)
rate_box = Element("span", {"class": "rate-box " + rating_css})
rate_box.append(
Element("span", {"style": "height: %3.fem" % rating_progress(rating)})
)
user = Element("span", {"class": "rating " + rating_css})
user.text = username
element.append(rate_box)
element.append(user)
else:
element.text = username
return element
def get_user_info(usernames):
return {
name: (rank, rating)
for name, rank, rating in Profile.objects.filter(
user__username__in=usernames
).values_list("user__username", "display_rank", "rating")
}
def get_user_from_text(text):
user_list = set()
for i in rereference.finditer(text):
user_list.add(text[i.start() + 6 : i.end() - 1])
return Profile.objects.filter(user__username__in=user_list)
reference_map = {
"user": (get_user, get_user_info),
"ruser": (get_user_rating, get_user_info),
}
def process_reference(text):
# text/tail -> text/tail + elements
last = 0
tail = text
prev = None
elements = []
for piece in rereference.finditer(text):
if prev is None:
tail = text[last : piece.start()]
else:
prev.append(text[last : piece.start()])
prev = list(piece.groups())
elements.append(prev)
last = piece.end()
if prev is not None:
prev.append(text[last:])
return tail, elements
def populate_list(queries, list, element, tail, children):
if children:
for elem in children:
queries[elem[0]].append(elem[1])
list.append((element, tail, children))
def update_tree(list, results, is_tail=False):
for element, text, children in list:
after = []
for type, name, tail in children:
child = reference_map[type][0](name, results[type].get(name))
child.tail = tail
after.append(child)
after = iter(reversed(after))
if is_tail:
element.tail = text
link = element.getnext()
if link is None:
link = next(after)
element.getparent().append(link)
else:
element.text = text
link = next(after)
element.insert(0, link)
for child in after:
link.addprevious(child)
link = child
@registry.filter
def reference(text):
tree = lxml_tree.fromstring(text)
texts = []
tails = []
queries = defaultdict(list)
for element in tree.iter():
if element.text:
populate_list(queries, texts, element, *process_reference(element.text))
if element.tail:
populate_list(queries, tails, element, *process_reference(element.tail))
results = {type: reference_map[type][1](values) for type, values in queries.items()}
update_tree(texts, results, is_tail=False)
update_tree(tails, results, is_tail=True)
return tree
@registry.filter
def item_title(item):
if isinstance(item, Problem):
return item.name
elif isinstance(item, Contest):
return item.name
return "<Unknown>"
@registry.function
@registry.render_with("user/link.html")
def link_user(user):
if isinstance(user, Profile):
profile = user
elif isinstance(user, AbstractUser):
profile = user.profile
elif type(user).__name__ == "ContestRankingProfile":
profile = user
else:
raise ValueError("Expected profile or user, got %s" % (type(user),))
return {"profile": profile}
@registry.function
@registry.render_with("user/link-list.html")
def link_users(users):
return {"users": users}
@registry.function
@registry.render_with("runtime-version-fragment.html")
def runtime_versions(versions):
return {"runtime_versions": versions}
@registry.filter(name="absolutify")
def absolute_links(text, url):
tree = lxml_tree.fromstring(text)
for anchor in tree.xpath(".//a"):
href = anchor.get("href")
if href:
anchor.set("href", urljoin(url, href))
return tree
@registry.function(name="urljoin")
def join(first, second, *rest):
if not rest:
return urljoin(first, second)
return urljoin(urljoin(first, second), *rest)
@registry.filter(name="ansi2html")
def ansi2html(s):
return mark_safe(Ansi2HTMLConverter(inline=True).convert(s, full=False))