2020-06-11 00:54:34 +02:00
|
|
|
from typing import Any, Dict, List, Optional, Tuple
|
2020-06-03 06:37:07 +02:00
|
|
|
from xml.etree.ElementTree import Element, SubElement
|
2018-07-07 22:14:30 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
import markdown
|
|
|
|
from markdown.extensions import Extension
|
|
|
|
|
2020-06-25 15:00:33 +02:00
|
|
|
from zerver.lib.markdown import ResultWithFamily, walk_tree_with_family
|
2020-06-11 00:54:34 +02:00
|
|
|
|
2018-07-07 22:14:30 +02:00
|
|
|
|
|
|
|
class NestedCodeBlocksRenderer(Extension):
|
|
|
|
def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> None:
|
|
|
|
md.treeprocessors.add(
|
|
|
|
'nested_code_blocks',
|
|
|
|
NestedCodeBlocksRendererTreeProcessor(md, self.getConfigs()),
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
'_end',
|
2018-07-07 22:14:30 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
class NestedCodeBlocksRendererTreeProcessor(markdown.treeprocessors.Treeprocessor):
|
|
|
|
def __init__(self, md: markdown.Markdown, config: Dict[str, Any]) -> None:
|
2020-04-09 21:51:58 +02:00
|
|
|
super().__init__(md)
|
2018-07-07 22:14:30 +02:00
|
|
|
|
|
|
|
def run(self, root: Element) -> None:
|
|
|
|
code_tags = walk_tree_with_family(root, self.get_code_tags)
|
|
|
|
nested_code_blocks = self.get_nested_code_blocks(code_tags)
|
|
|
|
for block in nested_code_blocks:
|
|
|
|
tag, text = block.result
|
|
|
|
codehilite_block = self.get_codehilite_block(text)
|
|
|
|
self.replace_element(block.family.grandparent,
|
|
|
|
codehilite_block,
|
|
|
|
block.family.parent)
|
|
|
|
|
|
|
|
def get_code_tags(self, e: Element) -> Optional[Tuple[str, Optional[str]]]:
|
|
|
|
if e.tag == "code":
|
|
|
|
return (e.tag, e.text)
|
|
|
|
return None
|
|
|
|
|
|
|
|
def get_nested_code_blocks(
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
self, code_tags: List[ResultWithFamily[Tuple[str, Optional[str]]]],
|
2020-05-09 02:59:28 +02:00
|
|
|
) -> List[ResultWithFamily[Tuple[str, Optional[str]]]]:
|
2018-07-07 22:14:30 +02:00
|
|
|
nested_code_blocks = []
|
|
|
|
for code_tag in code_tags:
|
python: Convert assignment type annotations to Python 3.6 style.
This commit was split by tabbott; this piece covers the vast majority
of files in Zulip, but excludes scripts/, tools/, and puppet/ to help
ensure we at least show the right error messages for Xenial systems.
We can likely further refine the remaining pieces with some testing.
Generated by com2ann, with whitespace fixes and various manual fixes
for runtime issues:
- invoiced_through: Optional[LicenseLedger] = models.ForeignKey(
+ invoiced_through: Optional["LicenseLedger"] = models.ForeignKey(
-_apns_client: Optional[APNsClient] = None
+_apns_client: Optional["APNsClient"] = None
- notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- signup_notifications_stream: Optional[Stream] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
+ signup_notifications_stream: Optional["Stream"] = models.ForeignKey('Stream', related_name='+', null=True, blank=True, on_delete=CASCADE)
- author: Optional[UserProfile] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
+ author: Optional["UserProfile"] = models.ForeignKey('UserProfile', blank=True, null=True, on_delete=CASCADE)
- bot_owner: Optional[UserProfile] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
+ bot_owner: Optional["UserProfile"] = models.ForeignKey('self', null=True, on_delete=models.SET_NULL)
- default_sending_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
- default_events_register_stream: Optional[Stream] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_sending_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
+ default_events_register_stream: Optional["Stream"] = models.ForeignKey('zerver.Stream', null=True, related_name='+', on_delete=CASCADE)
-descriptors_by_handler_id: Dict[int, ClientDescriptor] = {}
+descriptors_by_handler_id: Dict[int, "ClientDescriptor"] = {}
-worker_classes: Dict[str, Type[QueueProcessingWorker]] = {}
-queues: Dict[str, Dict[str, Type[QueueProcessingWorker]]] = {}
+worker_classes: Dict[str, Type["QueueProcessingWorker"]] = {}
+queues: Dict[str, Dict[str, Type["QueueProcessingWorker"]]] = {}
-AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional[LDAPSearch] = None
+AUTH_LDAP_REVERSE_EMAIL_SEARCH: Optional["LDAPSearch"] = None
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-22 01:09:50 +02:00
|
|
|
parent: Any = code_tag.family.parent
|
|
|
|
grandparent: Any = code_tag.family.grandparent
|
2018-07-07 22:14:30 +02:00
|
|
|
if parent.tag == "p" and grandparent.tag == "li":
|
2018-07-24 21:33:34 +02:00
|
|
|
# if the parent (<p>) has no text, and no children,
|
|
|
|
# that means that the <code> element inside is its
|
|
|
|
# only thing inside the bullet, we can confidently say
|
|
|
|
# that this is a nested code block
|
|
|
|
if parent.text is None and len(list(parent)) == 1 and len(list(parent.itertext())) == 1:
|
2018-07-07 22:14:30 +02:00
|
|
|
nested_code_blocks.append(code_tag)
|
|
|
|
|
|
|
|
return nested_code_blocks
|
|
|
|
|
2020-05-09 02:59:28 +02:00
|
|
|
def get_codehilite_block(self, code_block_text: Optional[str]) -> Element:
|
2020-06-03 06:37:07 +02:00
|
|
|
div = Element("div")
|
2018-07-07 22:14:30 +02:00
|
|
|
div.set("class", "codehilite")
|
2020-06-03 06:37:07 +02:00
|
|
|
pre = SubElement(div, "pre")
|
2018-07-07 22:14:30 +02:00
|
|
|
pre.text = code_block_text
|
|
|
|
return div
|
|
|
|
|
|
|
|
def replace_element(
|
|
|
|
self, parent: Optional[Element],
|
2020-06-03 06:37:07 +02:00
|
|
|
replacement: Element,
|
python: Use trailing commas consistently.
Automatically generated by the following script, based on the output
of lint with flake8-comma:
import re
import sys
last_filename = None
last_row = None
lines = []
for msg in sys.stdin:
m = re.match(
r"\x1b\[35mflake8 \|\x1b\[0m \x1b\[1;31m(.+):(\d+):(\d+): (\w+)", msg
)
if m:
filename, row_str, col_str, err = m.groups()
row, col = int(row_str), int(col_str)
if filename == last_filename:
assert last_row != row
else:
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
with open(filename) as f:
lines = f.readlines()
last_filename = filename
last_row = row
line = lines[row - 1]
if err in ["C812", "C815"]:
lines[row - 1] = line[: col - 1] + "," + line[col - 1 :]
elif err in ["C819"]:
assert line[col - 2] == ","
lines[row - 1] = line[: col - 2] + line[col - 1 :].lstrip(" ")
if last_filename is not None:
with open(last_filename, "w") as f:
f.writelines(lines)
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-04-10 05:23:40 +02:00
|
|
|
element_to_replace: Element,
|
2018-07-07 22:14:30 +02:00
|
|
|
) -> None:
|
|
|
|
if parent is None:
|
|
|
|
return
|
|
|
|
|
2020-06-04 02:15:21 +02:00
|
|
|
for index, child in enumerate(parent):
|
2018-07-07 22:14:30 +02:00
|
|
|
if child is element_to_replace:
|
|
|
|
parent.insert(index, replacement)
|
|
|
|
parent.remove(element_to_replace)
|
|
|
|
|
|
|
|
def makeExtension(*args: Any, **kwargs: str) -> NestedCodeBlocksRenderer:
|
2018-12-20 08:28:40 +01:00
|
|
|
return NestedCodeBlocksRenderer(**kwargs)
|