We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Clean up beta app installation instructions
Following up on #28884, we should convert the beta app installation instructions into our standard tabbed instructions format. I'm not sure about the best tab labels. Perhaps something like this?

- Most systems
- Linux with APT


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `zerver/lib/markdown/tabbed_sections.py`
Content:
```
1 import re
2 from typing import Any, Dict, List, Mapping, Optional
3 
4 import markdown
5 from markdown.extensions import Extension
6 from markdown.preprocessors import Preprocessor
7 from typing_extensions import override
8 
9 from zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES
10 
11 START_TABBED_SECTION_REGEX = re.compile(r"^\{start_tabs\}$")
12 END_TABBED_SECTION_REGEX = re.compile(r"^\{end_tabs\}$")
13 TAB_CONTENT_REGEX = re.compile(r"^\{tab\|([^}]+)\}$")
14 
15 TABBED_SECTION_TEMPLATE = """
16 <div class="tabbed-section {tab_class}" markdown="1">
17 {nav_bar}
18 <div class="blocks">
19 {blocks}
20 </div>
21 </div>
22 """.strip()
23 
24 NAV_BAR_TEMPLATE = """
25 <ul class="nav">
26 {tabs}
27 </ul>
28 """.strip()
29 
30 NAV_LIST_ITEM_TEMPLATE = """
31 <li data-tab-key="{data_tab_key}" tabindex="0">{label}</li>
32 """.strip()
33 
34 DIV_TAB_CONTENT_TEMPLATE = """
35 <div data-tab-key="{data_tab_key}" markdown="1">
36 {content}
37 </div>
38 """.strip()
39 
40 # If adding new entries here, also check if you need to update
41 # tabbed-instructions.js
42 TAB_SECTION_LABELS = {
43     "desktop-web": "Desktop/Web",
44     "ios": "iOS",
45     "android": "Android",
46     "mac": "macOS",
47     "windows": "Windows",
48     "linux": "Linux",
49     "python": "Python",
50     "js": "JavaScript",
51     "curl": "curl",
52     "zulip-send": "zulip-send",
53     "web": "Web",
54     "desktop": "Desktop",
55     "mobile": "Mobile",
56     "mm-default": "Default installation",
57     "mm-cloud": "Cloud instance",
58     "mm-docker": "Docker",
59     "mm-gitlab-omnibus": "GitLab Omnibus",
60     "mm-self-hosting-cloud-export": "Self hosting (cloud export)",
61     "require-invitations": "Require invitations",
62     "allow-anyone-to-join": "Allow anyone to join",
63     "restrict-by-email-domain": "Restrict by email domain",
64     "zoom": "Zoom",
65     "jitsi-meet": "Jitsi Meet",
66     "bigbluebutton": "BigBlueButton",
67     "disable": "Disabled",
68     "chrome": "Chrome",
69     "firefox": "Firefox",
70     "desktop-app": "Desktop app",
71     "system-proxy-settings": "System proxy settings",
72     "custom-proxy-settings": "Custom proxy settings",
73     "stream": "From a stream view",
74     "not-stream": "From other views",
75     "via-recent-conversations": "Via recent conversations",
76     "via-inbox-view": "Via inbox view",
77     "via-left-sidebar": "Via left sidebar",
78     "instructions-for-all-platforms": "Instructions for all platforms",
79     "public-streams": "Public streams",
80     "private-streams": "Private streams",
81     "web-public-streams": "Web-public streams",
82     "via-user-card": "Via user card",
83     "via-user-profile": "Via user profile",
84     "via-organization-settings": "Via organization settings",
85     "via-personal-settings": "Via personal settings",
86     "via-stream-settings": "Via stream settings",
87     "default-subdomain": "Default subdomain",
88     "custom-subdomain": "Custom subdomain",
89     "zulip-cloud-standard": "Zulip Cloud Standard",
90     "zulip-cloud-plus": "Zulip Cloud Plus",
91     "request-sponsorship": "Request sponsorship",
92     "request-education-pricing": "Request education pricing",
93     "zulip-cloud": "Zulip Cloud",
94     "self-hosting": "Self hosting",
95     "okta": "Okta",
96     "onelogin": "OneLogin",
97     "azuread": "AzureAD",
98     "keycloak": "Keycloak",
99     "auth0": "Auth0",
100     "logged-in": "If you are logged in",
101     "logged-out": "If you are logged out",
102     "user": "User",
103     "bot": "Bot",
104     "on-sign-up": "On sign-up",
105     "via-paste": "Via paste",
106     "via-drag-and-drop": "Via drag-and-drop",
107     "via-markdown": "Via Markdown",
108     "via-compose-box-buttons": "Via compose box buttons",
109     "stream-compose": "Compose to a stream",
110     "dm-compose": "Compose a DM",
111     "v8": "Zulip Server 8.0+",
112     "v6": "Zulip Server 6.0+",
113     "v4": "Zulip Server 4.0+",
114     "all-versions": "All versions",
115     "for-a-bot": "For a bot",
116     "for-yourself": "For yourself",
117 }
118 
119 
120 class TabbedSectionsGenerator(Extension):
121     @override
122     def extendMarkdown(self, md: markdown.Markdown) -> None:
123         md.preprocessors.register(
124             TabbedSectionsPreprocessor(md, self.getConfigs()),
125             "tabbed_sections",
126             PREPROCESSOR_PRIORITES["tabbed_sections"],
127         )
128 
129 
130 class TabbedSectionsPreprocessor(Preprocessor):
131     def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:
132         super().__init__(md)
133 
134     @override
135     def run(self, lines: List[str]) -> List[str]:
136         tab_section = self.parse_tabs(lines)
137         while tab_section:
138             if "tabs" in tab_section:
139                 tab_class = "has-tabs"
140             else:
141                 tab_class = "no-tabs"
142                 tab_section["tabs"] = [
143                     {
144                         "tab_key": "instructions-for-all-platforms",
145                         "start": tab_section["start_tabs_index"],
146                     }
147                 ]
148             nav_bar = self.generate_nav_bar(tab_section)
149             content_blocks = self.generate_content_blocks(tab_section, lines)
150             rendered_tabs = TABBED_SECTION_TEMPLATE.format(
151                 tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks
152             )
153 
154             start = tab_section["start_tabs_index"]
155             end = tab_section["end_tabs_index"] + 1
156             lines = [*lines[:start], rendered_tabs, *lines[end:]]
157             tab_section = self.parse_tabs(lines)
158         return lines
159 
160     def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:
161         tab_content_blocks = []
162         for index, tab in enumerate(tab_section["tabs"]):
163             start_index = tab["start"] + 1
164             try:
165                 # If there are more tabs, we can use the starting index
166                 # of the next tab as the ending index of the previous one
167                 end_index = tab_section["tabs"][index + 1]["start"]
168             except IndexError:
169                 # Otherwise, just use the end of the entire section
170                 end_index = tab_section["end_tabs_index"]
171 
172             content = "\n".join(lines[start_index:end_index]).strip()
173             tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(
174                 data_tab_key=tab["tab_key"],
175                 # Wrapping the content in two newlines is necessary here.
176                 # If we don't do this, the inner Markdown does not get
177                 # rendered properly.
178                 content=f"\n{content}\n",
179             )
180             tab_content_blocks.append(tab_content_block)
181         return "\n".join(tab_content_blocks)
182 
183     def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:
184         li_elements = []
185         for tab in tab_section["tabs"]:
186             tab_key = tab.get("tab_key")
187             tab_label = TAB_SECTION_LABELS.get(tab_key)
188             if tab_label is None:
189                 raise ValueError(
190                     f"Tab '{tab_key}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py"
191                 )
192 
193             li = NAV_LIST_ITEM_TEMPLATE.format(data_tab_key=tab_key, label=tab_label)
194             li_elements.append(li)
195 
196         return NAV_BAR_TEMPLATE.format(tabs="\n".join(li_elements))
197 
198     def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:
199         block: Dict[str, Any] = {}
200         for index, line in enumerate(lines):
201             start_match = START_TABBED_SECTION_REGEX.search(line)
202             if start_match:
203                 block["start_tabs_index"] = index
204 
205             tab_content_match = TAB_CONTENT_REGEX.search(line)
206             if tab_content_match:
207                 block.setdefault("tabs", [])
208                 tab = {"start": index, "tab_key": tab_content_match.group(1)}
209                 block["tabs"].append(tab)
210 
211             end_match = END_TABBED_SECTION_REGEX.search(line)
212             if end_match:
213                 block["end_tabs_index"] = index
214                 break
215         return block
216 
217 
218 def makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:
219     return TabbedSectionsGenerator(**kwargs)
220
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/zerver/lib/markdown/tabbed_sections.py b/zerver/lib/markdown/tabbed_sections.py
--- a/zerver/lib/markdown/tabbed_sections.py
+++ b/zerver/lib/markdown/tabbed_sections.py
@@ -46,6 +46,8 @@
     "mac": "macOS",
     "windows": "Windows",
     "linux": "Linux",
+    "most-systems": "Most systems",
+    "linux-with-apt": "Linux with APT",
     "python": "Python",
     "js": "JavaScript",
     "curl": "curl",

{"golden_diff": "diff --git a/zerver/lib/markdown/tabbed_sections.py b/zerver/lib/markdown/tabbed_sections.py\n--- a/zerver/lib/markdown/tabbed_sections.py\n+++ b/zerver/lib/markdown/tabbed_sections.py\n@@ -46,6 +46,8 @@\n     \"mac\": \"macOS\",\n     \"windows\": \"Windows\",\n     \"linux\": \"Linux\",\n+    \"most-systems\": \"Most systems\",\n+    \"linux-with-apt\": \"Linux with APT\",\n     \"python\": \"Python\",\n     \"js\": \"JavaScript\",\n     \"curl\": \"curl\",\n", "issue": "Clean up beta app installation instructions\nFollowing up on #28884, we should convert the beta app installation instructions into our standard tabbed instructions format. I'm not sure about the best tab labels. Perhaps something like this?\r\n\r\n- Most systems\r\n- Linux with APT\r\n\n", "before_files": [{"content": "import re\nfrom typing import Any, Dict, List, Mapping, Optional\n\nimport markdown\nfrom markdown.extensions import Extension\nfrom markdown.preprocessors import Preprocessor\nfrom typing_extensions import override\n\nfrom zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES\n\nSTART_TABBED_SECTION_REGEX = re.compile(r\"^\\{start_tabs\\}$\")\nEND_TABBED_SECTION_REGEX = re.compile(r\"^\\{end_tabs\\}$\")\nTAB_CONTENT_REGEX = re.compile(r\"^\\{tab\\|([^}]+)\\}$\")\n\nTABBED_SECTION_TEMPLATE = \"\"\"\n<div class=\"tabbed-section {tab_class}\" markdown=\"1\">\n{nav_bar}\n<div class=\"blocks\">\n{blocks}\n</div>\n</div>\n\"\"\".strip()\n\nNAV_BAR_TEMPLATE = \"\"\"\n<ul class=\"nav\">\n{tabs}\n</ul>\n\"\"\".strip()\n\nNAV_LIST_ITEM_TEMPLATE = \"\"\"\n<li data-tab-key=\"{data_tab_key}\" tabindex=\"0\">{label}</li>\n\"\"\".strip()\n\nDIV_TAB_CONTENT_TEMPLATE = \"\"\"\n<div data-tab-key=\"{data_tab_key}\" markdown=\"1\">\n{content}\n</div>\n\"\"\".strip()\n\n# If adding new entries here, also check if you need to update\n# tabbed-instructions.js\nTAB_SECTION_LABELS = {\n    \"desktop-web\": \"Desktop/Web\",\n    \"ios\": \"iOS\",\n    \"android\": \"Android\",\n    \"mac\": \"macOS\",\n    \"windows\": \"Windows\",\n    \"linux\": \"Linux\",\n    \"python\": \"Python\",\n    \"js\": \"JavaScript\",\n    \"curl\": \"curl\",\n    \"zulip-send\": \"zulip-send\",\n    \"web\": \"Web\",\n    \"desktop\": \"Desktop\",\n    \"mobile\": \"Mobile\",\n    \"mm-default\": \"Default installation\",\n    \"mm-cloud\": \"Cloud instance\",\n    \"mm-docker\": \"Docker\",\n    \"mm-gitlab-omnibus\": \"GitLab Omnibus\",\n    \"mm-self-hosting-cloud-export\": \"Self hosting (cloud export)\",\n    \"require-invitations\": \"Require invitations\",\n    \"allow-anyone-to-join\": \"Allow anyone to join\",\n    \"restrict-by-email-domain\": \"Restrict by email domain\",\n    \"zoom\": \"Zoom\",\n    \"jitsi-meet\": \"Jitsi Meet\",\n    \"bigbluebutton\": \"BigBlueButton\",\n    \"disable\": \"Disabled\",\n    \"chrome\": \"Chrome\",\n    \"firefox\": \"Firefox\",\n    \"desktop-app\": \"Desktop app\",\n    \"system-proxy-settings\": \"System proxy settings\",\n    \"custom-proxy-settings\": \"Custom proxy settings\",\n    \"stream\": \"From a stream view\",\n    \"not-stream\": \"From other views\",\n    \"via-recent-conversations\": \"Via recent conversations\",\n    \"via-inbox-view\": \"Via inbox view\",\n    \"via-left-sidebar\": \"Via left sidebar\",\n    \"instructions-for-all-platforms\": \"Instructions for all platforms\",\n    \"public-streams\": \"Public streams\",\n    \"private-streams\": \"Private streams\",\n    \"web-public-streams\": \"Web-public streams\",\n    \"via-user-card\": \"Via user card\",\n    \"via-user-profile\": \"Via user profile\",\n    \"via-organization-settings\": \"Via organization settings\",\n    \"via-personal-settings\": \"Via personal settings\",\n    \"via-stream-settings\": \"Via stream settings\",\n    \"default-subdomain\": \"Default subdomain\",\n    \"custom-subdomain\": \"Custom subdomain\",\n    \"zulip-cloud-standard\": \"Zulip Cloud Standard\",\n    \"zulip-cloud-plus\": \"Zulip Cloud Plus\",\n    \"request-sponsorship\": \"Request sponsorship\",\n    \"request-education-pricing\": \"Request education pricing\",\n    \"zulip-cloud\": \"Zulip Cloud\",\n    \"self-hosting\": \"Self hosting\",\n    \"okta\": \"Okta\",\n    \"onelogin\": \"OneLogin\",\n    \"azuread\": \"AzureAD\",\n    \"keycloak\": \"Keycloak\",\n    \"auth0\": \"Auth0\",\n    \"logged-in\": \"If you are logged in\",\n    \"logged-out\": \"If you are logged out\",\n    \"user\": \"User\",\n    \"bot\": \"Bot\",\n    \"on-sign-up\": \"On sign-up\",\n    \"via-paste\": \"Via paste\",\n    \"via-drag-and-drop\": \"Via drag-and-drop\",\n    \"via-markdown\": \"Via Markdown\",\n    \"via-compose-box-buttons\": \"Via compose box buttons\",\n    \"stream-compose\": \"Compose to a stream\",\n    \"dm-compose\": \"Compose a DM\",\n    \"v8\": \"Zulip Server 8.0+\",\n    \"v6\": \"Zulip Server 6.0+\",\n    \"v4\": \"Zulip Server 4.0+\",\n    \"all-versions\": \"All versions\",\n    \"for-a-bot\": \"For a bot\",\n    \"for-yourself\": \"For yourself\",\n}\n\n\nclass TabbedSectionsGenerator(Extension):\n    @override\n    def extendMarkdown(self, md: markdown.Markdown) -> None:\n        md.preprocessors.register(\n            TabbedSectionsPreprocessor(md, self.getConfigs()),\n            \"tabbed_sections\",\n            PREPROCESSOR_PRIORITES[\"tabbed_sections\"],\n        )\n\n\nclass TabbedSectionsPreprocessor(Preprocessor):\n    def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:\n        super().__init__(md)\n\n    @override\n    def run(self, lines: List[str]) -> List[str]:\n        tab_section = self.parse_tabs(lines)\n        while tab_section:\n            if \"tabs\" in tab_section:\n                tab_class = \"has-tabs\"\n            else:\n                tab_class = \"no-tabs\"\n                tab_section[\"tabs\"] = [\n                    {\n                        \"tab_key\": \"instructions-for-all-platforms\",\n                        \"start\": tab_section[\"start_tabs_index\"],\n                    }\n                ]\n            nav_bar = self.generate_nav_bar(tab_section)\n            content_blocks = self.generate_content_blocks(tab_section, lines)\n            rendered_tabs = TABBED_SECTION_TEMPLATE.format(\n                tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks\n            )\n\n            start = tab_section[\"start_tabs_index\"]\n            end = tab_section[\"end_tabs_index\"] + 1\n            lines = [*lines[:start], rendered_tabs, *lines[end:]]\n            tab_section = self.parse_tabs(lines)\n        return lines\n\n    def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:\n        tab_content_blocks = []\n        for index, tab in enumerate(tab_section[\"tabs\"]):\n            start_index = tab[\"start\"] + 1\n            try:\n                # If there are more tabs, we can use the starting index\n                # of the next tab as the ending index of the previous one\n                end_index = tab_section[\"tabs\"][index + 1][\"start\"]\n            except IndexError:\n                # Otherwise, just use the end of the entire section\n                end_index = tab_section[\"end_tabs_index\"]\n\n            content = \"\\n\".join(lines[start_index:end_index]).strip()\n            tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(\n                data_tab_key=tab[\"tab_key\"],\n                # Wrapping the content in two newlines is necessary here.\n                # If we don't do this, the inner Markdown does not get\n                # rendered properly.\n                content=f\"\\n{content}\\n\",\n            )\n            tab_content_blocks.append(tab_content_block)\n        return \"\\n\".join(tab_content_blocks)\n\n    def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:\n        li_elements = []\n        for tab in tab_section[\"tabs\"]:\n            tab_key = tab.get(\"tab_key\")\n            tab_label = TAB_SECTION_LABELS.get(tab_key)\n            if tab_label is None:\n                raise ValueError(\n                    f\"Tab '{tab_key}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py\"\n                )\n\n            li = NAV_LIST_ITEM_TEMPLATE.format(data_tab_key=tab_key, label=tab_label)\n            li_elements.append(li)\n\n        return NAV_BAR_TEMPLATE.format(tabs=\"\\n\".join(li_elements))\n\n    def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:\n        block: Dict[str, Any] = {}\n        for index, line in enumerate(lines):\n            start_match = START_TABBED_SECTION_REGEX.search(line)\n            if start_match:\n                block[\"start_tabs_index\"] = index\n\n            tab_content_match = TAB_CONTENT_REGEX.search(line)\n            if tab_content_match:\n                block.setdefault(\"tabs\", [])\n                tab = {\"start\": index, \"tab_key\": tab_content_match.group(1)}\n                block[\"tabs\"].append(tab)\n\n            end_match = END_TABBED_SECTION_REGEX.search(line)\n            if end_match:\n                block[\"end_tabs_index\"] = index\n                break\n        return block\n\n\ndef makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:\n    return TabbedSectionsGenerator(**kwargs)\n", "path": "zerver/lib/markdown/tabbed_sections.py"}], "after_files": [{"content": "import re\nfrom typing import Any, Dict, List, Mapping, Optional\n\nimport markdown\nfrom markdown.extensions import Extension\nfrom markdown.preprocessors import Preprocessor\nfrom typing_extensions import override\n\nfrom zerver.lib.markdown.priorities import PREPROCESSOR_PRIORITES\n\nSTART_TABBED_SECTION_REGEX = re.compile(r\"^\\{start_tabs\\}$\")\nEND_TABBED_SECTION_REGEX = re.compile(r\"^\\{end_tabs\\}$\")\nTAB_CONTENT_REGEX = re.compile(r\"^\\{tab\\|([^}]+)\\}$\")\n\nTABBED_SECTION_TEMPLATE = \"\"\"\n<div class=\"tabbed-section {tab_class}\" markdown=\"1\">\n{nav_bar}\n<div class=\"blocks\">\n{blocks}\n</div>\n</div>\n\"\"\".strip()\n\nNAV_BAR_TEMPLATE = \"\"\"\n<ul class=\"nav\">\n{tabs}\n</ul>\n\"\"\".strip()\n\nNAV_LIST_ITEM_TEMPLATE = \"\"\"\n<li data-tab-key=\"{data_tab_key}\" tabindex=\"0\">{label}</li>\n\"\"\".strip()\n\nDIV_TAB_CONTENT_TEMPLATE = \"\"\"\n<div data-tab-key=\"{data_tab_key}\" markdown=\"1\">\n{content}\n</div>\n\"\"\".strip()\n\n# If adding new entries here, also check if you need to update\n# tabbed-instructions.js\nTAB_SECTION_LABELS = {\n    \"desktop-web\": \"Desktop/Web\",\n    \"ios\": \"iOS\",\n    \"android\": \"Android\",\n    \"mac\": \"macOS\",\n    \"windows\": \"Windows\",\n    \"linux\": \"Linux\",\n    \"most-systems\": \"Most systems\",\n    \"linux-with-apt\": \"Linux with APT\",\n    \"python\": \"Python\",\n    \"js\": \"JavaScript\",\n    \"curl\": \"curl\",\n    \"zulip-send\": \"zulip-send\",\n    \"web\": \"Web\",\n    \"desktop\": \"Desktop\",\n    \"mobile\": \"Mobile\",\n    \"mm-default\": \"Default installation\",\n    \"mm-cloud\": \"Cloud instance\",\n    \"mm-docker\": \"Docker\",\n    \"mm-gitlab-omnibus\": \"GitLab Omnibus\",\n    \"mm-self-hosting-cloud-export\": \"Self hosting (cloud export)\",\n    \"require-invitations\": \"Require invitations\",\n    \"allow-anyone-to-join\": \"Allow anyone to join\",\n    \"restrict-by-email-domain\": \"Restrict by email domain\",\n    \"zoom\": \"Zoom\",\n    \"jitsi-meet\": \"Jitsi Meet\",\n    \"bigbluebutton\": \"BigBlueButton\",\n    \"disable\": \"Disabled\",\n    \"chrome\": \"Chrome\",\n    \"firefox\": \"Firefox\",\n    \"desktop-app\": \"Desktop app\",\n    \"system-proxy-settings\": \"System proxy settings\",\n    \"custom-proxy-settings\": \"Custom proxy settings\",\n    \"stream\": \"From a stream view\",\n    \"not-stream\": \"From other views\",\n    \"via-recent-conversations\": \"Via recent conversations\",\n    \"via-inbox-view\": \"Via inbox view\",\n    \"via-left-sidebar\": \"Via left sidebar\",\n    \"instructions-for-all-platforms\": \"Instructions for all platforms\",\n    \"public-streams\": \"Public streams\",\n    \"private-streams\": \"Private streams\",\n    \"web-public-streams\": \"Web-public streams\",\n    \"via-user-card\": \"Via user card\",\n    \"via-user-profile\": \"Via user profile\",\n    \"via-organization-settings\": \"Via organization settings\",\n    \"via-personal-settings\": \"Via personal settings\",\n    \"via-stream-settings\": \"Via stream settings\",\n    \"default-subdomain\": \"Default subdomain\",\n    \"custom-subdomain\": \"Custom subdomain\",\n    \"zulip-cloud-standard\": \"Zulip Cloud Standard\",\n    \"zulip-cloud-plus\": \"Zulip Cloud Plus\",\n    \"request-sponsorship\": \"Request sponsorship\",\n    \"request-education-pricing\": \"Request education pricing\",\n    \"zulip-cloud\": \"Zulip Cloud\",\n    \"self-hosting\": \"Self hosting\",\n    \"okta\": \"Okta\",\n    \"onelogin\": \"OneLogin\",\n    \"azuread\": \"AzureAD\",\n    \"keycloak\": \"Keycloak\",\n    \"auth0\": \"Auth0\",\n    \"logged-in\": \"If you are logged in\",\n    \"logged-out\": \"If you are logged out\",\n    \"user\": \"User\",\n    \"bot\": \"Bot\",\n    \"on-sign-up\": \"On sign-up\",\n    \"via-paste\": \"Via paste\",\n    \"via-drag-and-drop\": \"Via drag-and-drop\",\n    \"via-markdown\": \"Via Markdown\",\n    \"via-compose-box-buttons\": \"Via compose box buttons\",\n    \"stream-compose\": \"Compose to a stream\",\n    \"dm-compose\": \"Compose a DM\",\n    \"v8\": \"Zulip Server 8.0+\",\n    \"v6\": \"Zulip Server 6.0+\",\n    \"v4\": \"Zulip Server 4.0+\",\n    \"all-versions\": \"All versions\",\n    \"for-a-bot\": \"For a bot\",\n    \"for-yourself\": \"For yourself\",\n}\n\n\nclass TabbedSectionsGenerator(Extension):\n    @override\n    def extendMarkdown(self, md: markdown.Markdown) -> None:\n        md.preprocessors.register(\n            TabbedSectionsPreprocessor(md, self.getConfigs()),\n            \"tabbed_sections\",\n            PREPROCESSOR_PRIORITES[\"tabbed_sections\"],\n        )\n\n\nclass TabbedSectionsPreprocessor(Preprocessor):\n    def __init__(self, md: markdown.Markdown, config: Mapping[str, Any]) -> None:\n        super().__init__(md)\n\n    @override\n    def run(self, lines: List[str]) -> List[str]:\n        tab_section = self.parse_tabs(lines)\n        while tab_section:\n            if \"tabs\" in tab_section:\n                tab_class = \"has-tabs\"\n            else:\n                tab_class = \"no-tabs\"\n                tab_section[\"tabs\"] = [\n                    {\n                        \"tab_key\": \"instructions-for-all-platforms\",\n                        \"start\": tab_section[\"start_tabs_index\"],\n                    }\n                ]\n            nav_bar = self.generate_nav_bar(tab_section)\n            content_blocks = self.generate_content_blocks(tab_section, lines)\n            rendered_tabs = TABBED_SECTION_TEMPLATE.format(\n                tab_class=tab_class, nav_bar=nav_bar, blocks=content_blocks\n            )\n\n            start = tab_section[\"start_tabs_index\"]\n            end = tab_section[\"end_tabs_index\"] + 1\n            lines = [*lines[:start], rendered_tabs, *lines[end:]]\n            tab_section = self.parse_tabs(lines)\n        return lines\n\n    def generate_content_blocks(self, tab_section: Dict[str, Any], lines: List[str]) -> str:\n        tab_content_blocks = []\n        for index, tab in enumerate(tab_section[\"tabs\"]):\n            start_index = tab[\"start\"] + 1\n            try:\n                # If there are more tabs, we can use the starting index\n                # of the next tab as the ending index of the previous one\n                end_index = tab_section[\"tabs\"][index + 1][\"start\"]\n            except IndexError:\n                # Otherwise, just use the end of the entire section\n                end_index = tab_section[\"end_tabs_index\"]\n\n            content = \"\\n\".join(lines[start_index:end_index]).strip()\n            tab_content_block = DIV_TAB_CONTENT_TEMPLATE.format(\n                data_tab_key=tab[\"tab_key\"],\n                # Wrapping the content in two newlines is necessary here.\n                # If we don't do this, the inner Markdown does not get\n                # rendered properly.\n                content=f\"\\n{content}\\n\",\n            )\n            tab_content_blocks.append(tab_content_block)\n        return \"\\n\".join(tab_content_blocks)\n\n    def generate_nav_bar(self, tab_section: Dict[str, Any]) -> str:\n        li_elements = []\n        for tab in tab_section[\"tabs\"]:\n            tab_key = tab.get(\"tab_key\")\n            tab_label = TAB_SECTION_LABELS.get(tab_key)\n            if tab_label is None:\n                raise ValueError(\n                    f\"Tab '{tab_key}' is not present in TAB_SECTION_LABELS in zerver/lib/markdown/tabbed_sections.py\"\n                )\n\n            li = NAV_LIST_ITEM_TEMPLATE.format(data_tab_key=tab_key, label=tab_label)\n            li_elements.append(li)\n\n        return NAV_BAR_TEMPLATE.format(tabs=\"\\n\".join(li_elements))\n\n    def parse_tabs(self, lines: List[str]) -> Optional[Dict[str, Any]]:\n        block: Dict[str, Any] = {}\n        for index, line in enumerate(lines):\n            start_match = START_TABBED_SECTION_REGEX.search(line)\n            if start_match:\n                block[\"start_tabs_index\"] = index\n\n            tab_content_match = TAB_CONTENT_REGEX.search(line)\n            if tab_content_match:\n                block.setdefault(\"tabs\", [])\n                tab = {\"start\": index, \"tab_key\": tab_content_match.group(1)}\n                block[\"tabs\"].append(tab)\n\n            end_match = END_TABBED_SECTION_REGEX.search(line)\n            if end_match:\n                block[\"end_tabs_index\"] = index\n                break\n        return block\n\n\ndef makeExtension(*args: Any, **kwargs: str) -> TabbedSectionsGenerator:\n    return TabbedSectionsGenerator(**kwargs)\n", "path": "zerver/lib/markdown/tabbed_sections.py"}]}