2018-09-07 00:09:51 +02:00
|
|
|
import re
|
2020-06-11 00:54:34 +02:00
|
|
|
from typing import Any, Dict, List, Optional
|
2018-09-07 00:09:51 +02:00
|
|
|
|
2020-06-11 00:54:34 +02:00
|
|
|
import markdown
|
2018-09-07 00:09:51 +02:00
|
|
|
from markdown.extensions import Extension
|
|
|
|
from markdown.preprocessors import Preprocessor
|
|
|
|
|
|
|
|
START_TABBED_SECTION_REGEX = re.compile(r'^\{start_tabs\}$')
|
|
|
|
END_TABBED_SECTION_REGEX = re.compile(r'^\{end_tabs\}$')
|
|
|
|
TAB_CONTENT_REGEX = re.compile(r'^\{tab\|\s*(.+?)\s*\}$')
|
|
|
|
|
|
|
|
CODE_SECTION_TEMPLATE = """
|
2019-02-08 04:55:50 +01:00
|
|
|
<div class="code-section {tab_class}" markdown="1">
|
2018-09-07 00:09:51 +02:00
|
|
|
{nav_bar}
|
|
|
|
<div class="blocks">
|
|
|
|
{blocks}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
NAV_BAR_TEMPLATE = """
|
|
|
|
<ul class="nav">
|
|
|
|
{tabs}
|
|
|
|
</ul>
|
|
|
|
""".strip()
|
|
|
|
|
|
|
|
NAV_LIST_ITEM_TEMPLATE = """
|
2020-07-29 12:54:39 +02:00
|
|
|
<li data-language="{data_language}" tabindex="0">{name}</li>
|
2018-09-07 00:09:51 +02:00
|
|
|
""".strip()
|
|
|
|
|
|
|
|
DIV_TAB_CONTENT_TEMPLATE = """
|
|
|
|
<div data-language="{data_language}" markdown="1">
|
|
|
|
{content}
|
|
|
|
</div>
|
|
|
|
""".strip()
|
|
|
|
|
2018-09-15 21:09:35 +02:00
|
|
|
# If adding new entries here, also check if you need to update
|
|
|
|
# tabbed-instructions.js
|
2018-09-07 00:09:51 +02:00
|
|
|
TAB_DISPLAY_NAMES = {
|
|
|
|
'desktop-web': 'Desktop/Web',
|
|
|
|
'ios': 'iOS',
|
|
|
|
'android': 'Android',
|
2018-09-15 21:35:25 +02:00
|
|
|
'mac': 'macOS',
|
|
|
|
'windows': 'Windows',
|
|
|
|
'linux': 'Linux',
|
2018-09-17 16:27:32 +02:00
|
|
|
'python': 'Python',
|
|
|
|
'js': 'JavaScript',
|
|
|
|
'curl': 'curl',
|
|
|
|
'zulip-send': 'zulip-send',
|
2018-10-07 01:19:00 +02:00
|
|
|
|
2019-04-03 05:53:52 +02:00
|
|
|
'web': 'Web',
|
|
|
|
'desktop': 'Desktop',
|
|
|
|
'mobile': 'Mobile',
|
|
|
|
|
2018-10-07 01:19:00 +02:00
|
|
|
'cloud': 'HipChat Cloud',
|
|
|
|
'server': 'HipChat Server or Data Center',
|
2019-01-29 21:18:49 +01:00
|
|
|
'stride': 'Stride',
|
2019-02-27 07:14:13 +01:00
|
|
|
|
2019-04-10 14:27:43 +02:00
|
|
|
'mm-default': 'Default installation',
|
2019-04-17 01:07:18 +02:00
|
|
|
'mm-docker': 'Docker',
|
2019-04-12 20:18:01 +02:00
|
|
|
'mm-gitlab-omnibus': 'Gitlab Omnibus',
|
2019-04-10 14:27:43 +02:00
|
|
|
|
2019-02-27 07:14:13 +01:00
|
|
|
'send-email-invitations': 'Send email invitations',
|
|
|
|
'share-an-invite-link': 'Share an invite link',
|
|
|
|
'allow-anyone-to-join': 'Allow anyone to join',
|
|
|
|
'restrict-by-email-domain': 'Restrict by email domain',
|
2019-03-01 01:34:12 +01:00
|
|
|
|
2020-08-08 01:44:45 +02:00
|
|
|
'zoom': 'Zoom',
|
2020-06-14 23:36:14 +02:00
|
|
|
'jitsi-meet': 'Jitsi Meet',
|
2020-04-27 22:41:31 +02:00
|
|
|
'bigbluebutton': 'Big Blue Button',
|
2020-06-14 23:36:14 +02:00
|
|
|
'disable': 'Disabled',
|
2019-03-30 19:38:33 +01:00
|
|
|
|
|
|
|
'chrome': 'Chrome',
|
|
|
|
'firefox': 'Firefox',
|
|
|
|
'desktop-app': 'Desktop app',
|
2019-04-02 23:32:55 +02:00
|
|
|
|
|
|
|
'system-proxy-settings': 'System proxy settings',
|
|
|
|
'custom-proxy-settings': 'Custom proxy settings',
|
2018-09-07 00:09:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class TabbedSectionsGenerator(Extension):
|
|
|
|
def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> None:
|
|
|
|
md.preprocessors.add(
|
|
|
|
'tabbed_sections', TabbedSectionsPreprocessor(md, self.getConfigs()), '_end')
|
|
|
|
|
|
|
|
class TabbedSectionsPreprocessor(Preprocessor):
|
|
|
|
def __init__(self, md: markdown.Markdown, config: Dict[str, Any]) -> None:
|
2020-04-09 21:51:58 +02:00
|
|
|
super().__init__(md)
|
2018-09-07 00:09:51 +02:00
|
|
|
|
|
|
|
def run(self, lines: List[str]) -> List[str]:
|
|
|
|
tab_section = self.parse_tabs(lines)
|
|
|
|
while tab_section:
|
2019-02-08 04:55:50 +01:00
|
|
|
if 'tabs' in tab_section:
|
|
|
|
tab_class = 'has-tabs'
|
|
|
|
else:
|
|
|
|
tab_class = 'no-tabs'
|
|
|
|
tab_section['tabs'] = [{'tab_name': 'null_tab',
|
|
|
|
'start': tab_section['start_tabs_index']}]
|
2018-09-07 00:09:51 +02:00
|
|
|
nav_bar = self.generate_nav_bar(tab_section)
|
|
|
|
content_blocks = self.generate_content_blocks(tab_section, lines)
|
|
|
|
rendered_tabs = CODE_SECTION_TEMPLATE.format(
|
2019-02-08 04:55:50 +01:00
|
|
|
tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks)
|
2018-09-07 00:09:51 +02:00
|
|
|
|
|
|
|
start = tab_section['start_tabs_index']
|
|
|
|
end = tab_section['end_tabs_index'] + 1
|
|
|
|
lines = lines[:start] + [rendered_tabs] + lines[end:]
|
|
|
|
tab_section = self.parse_tabs(lines)
|
|
|
|
return lines
|
|
|
|
|
|
|
|
def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:
|
|
|
|
tab_content_blocks = []
|
|
|
|
for index, tab in enumerate(tab_section['tabs']):
|
|
|
|
start_index = tab['start'] + 1
|
|
|
|
try:
|
|
|
|
# If there are more tabs, we can use the starting index
|
|
|
|
# of the next tab as the ending index of the previous one
|
|
|
|
end_index = tab_section['tabs'][index + 1]['start']
|
|
|
|
except IndexError:
|
|
|
|
# Otherwise, just use the end of the entire section
|
|
|
|
end_index = tab_section['end_tabs_index']
|
|
|
|
|
|
|
|
content = '\n'.join(lines[start_index:end_index]).strip()
|
|
|
|
tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(
|
|
|
|
data_language=tab['tab_name'],
|
|
|
|
# Wrapping the content in two newlines is necessary here.
|
|
|
|
# If we don't do this, the inner Markdown does not get
|
|
|
|
# rendered properly.
|
2020-06-09 00:25:09 +02:00
|
|
|
content=f'\n{content}\n')
|
2018-09-07 00:09:51 +02:00
|
|
|
tab_content_blocks.append(tab_content_block)
|
|
|
|
return '\n'.join(tab_content_blocks)
|
|
|
|
|
|
|
|
def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:
|
|
|
|
li_elements = []
|
|
|
|
for tab in tab_section['tabs']:
|
|
|
|
li = NAV_LIST_ITEM_TEMPLATE.format(
|
|
|
|
data_language=tab.get('tab_name'),
|
|
|
|
name=TAB_DISPLAY_NAMES.get(tab.get('tab_name')))
|
|
|
|
li_elements.append(li)
|
|
|
|
return NAV_BAR_TEMPLATE.format(tabs='\n'.join(li_elements))
|
|
|
|
|
|
|
|
def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:
|
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
|
|
|
block: Dict[str, Any] = {}
|
2018-09-07 00:09:51 +02:00
|
|
|
for index, line in enumerate(lines):
|
|
|
|
start_match = START_TABBED_SECTION_REGEX.search(line)
|
|
|
|
if start_match:
|
|
|
|
block['start_tabs_index'] = index
|
|
|
|
|
|
|
|
tab_content_match = TAB_CONTENT_REGEX.search(line)
|
|
|
|
if tab_content_match:
|
|
|
|
block.setdefault('tabs', [])
|
|
|
|
tab = {'start': index,
|
|
|
|
'tab_name': tab_content_match.group(1)}
|
|
|
|
block['tabs'].append(tab)
|
|
|
|
|
|
|
|
end_match = END_TABBED_SECTION_REGEX.search(line)
|
|
|
|
if end_match:
|
|
|
|
block['end_tabs_index'] = index
|
|
|
|
break
|
|
|
|
return block
|
|
|
|
|
|
|
|
def makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:
|
2018-12-20 08:28:40 +01:00
|
|
|
return TabbedSectionsGenerator(**kwargs)
|