2018-01-26 22:08:42 +01:00
|
|
|
import re
|
2018-02-21 00:40:38 +01:00
|
|
|
import json
|
2018-01-26 22:08:42 +01:00
|
|
|
import inspect
|
|
|
|
|
|
|
|
from markdown.extensions import Extension
|
|
|
|
from markdown.preprocessors import Preprocessor
|
|
|
|
from typing import Any, Dict, Optional, List
|
|
|
|
import markdown
|
|
|
|
|
2018-01-26 22:40:39 +01:00
|
|
|
import zerver.lib.api_test_helpers
|
2018-05-15 19:28:42 +02:00
|
|
|
from zerver.lib.openapi import get_openapi_fixture
|
2018-01-26 22:08:42 +01:00
|
|
|
|
2018-05-15 19:28:42 +02:00
|
|
|
MACRO_REGEXP = re.compile(r'\{generate_code_example(\(\s*(.+?)\s*\))*\|\s*(.+?)\s*\|\s*(.+?)\s*(\(\s*(.+)\s*\))?\}')
|
2018-02-16 04:09:21 +01:00
|
|
|
CODE_EXAMPLE_REGEX = re.compile(r'\# \{code_example\|\s*(.+?)\s*\}')
|
2018-01-26 22:08:42 +01:00
|
|
|
|
2018-02-16 04:09:21 +01:00
|
|
|
PYTHON_CLIENT_CONFIG = """
|
2018-01-26 22:08:42 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import zulip
|
|
|
|
|
2018-10-16 21:23:23 +02:00
|
|
|
# Pass the path to your zuliprc file here.
|
|
|
|
client = zulip.Client(config_file="~/zuliprc")
|
2018-01-26 22:08:42 +01:00
|
|
|
|
|
|
|
"""
|
|
|
|
|
2018-01-31 05:34:53 +01:00
|
|
|
PYTHON_CLIENT_ADMIN_CONFIG = """
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
import zulip
|
|
|
|
|
2018-10-16 21:23:23 +02:00
|
|
|
# The user for this zuliprc file must be an organization administrator
|
2018-01-31 05:34:53 +01:00
|
|
|
client = zulip.Client(config_file="~/zuliprc-admin")
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
2018-02-16 04:09:21 +01:00
|
|
|
def extract_python_code_example(source: List[str], snippet: List[str]) -> List[str]:
|
|
|
|
start = -1
|
|
|
|
end = -1
|
|
|
|
for line in source:
|
|
|
|
match = CODE_EXAMPLE_REGEX.search(line)
|
|
|
|
if match:
|
|
|
|
if match.group(1) == 'start':
|
|
|
|
start = source.index(line)
|
|
|
|
elif match.group(1) == 'end':
|
|
|
|
end = source.index(line)
|
|
|
|
break
|
|
|
|
|
|
|
|
if (start == -1 and end == -1):
|
|
|
|
return snippet
|
|
|
|
|
|
|
|
snippet.extend(source[start + 1: end])
|
|
|
|
snippet.append(' print(result)')
|
|
|
|
snippet.append('\n')
|
|
|
|
source = source[end + 1:]
|
|
|
|
return extract_python_code_example(source, snippet)
|
|
|
|
|
|
|
|
def render_python_code_example(function: str, admin_config: Optional[bool]=False) -> List[str]:
|
|
|
|
method = zerver.lib.api_test_helpers.TEST_FUNCTIONS[function]
|
|
|
|
function_source_lines = inspect.getsourcelines(method)[0]
|
|
|
|
|
|
|
|
if admin_config:
|
|
|
|
config = PYTHON_CLIENT_ADMIN_CONFIG.splitlines()
|
|
|
|
else:
|
|
|
|
config = PYTHON_CLIENT_CONFIG.splitlines()
|
|
|
|
|
|
|
|
snippet = extract_python_code_example(function_source_lines, [])
|
|
|
|
|
|
|
|
code_example = []
|
|
|
|
code_example.append('```python')
|
|
|
|
code_example.extend(config)
|
|
|
|
|
|
|
|
for line in snippet:
|
|
|
|
# Remove one level of indentation and strip newlines
|
|
|
|
code_example.append(line[4:].rstrip())
|
|
|
|
|
|
|
|
code_example.append('```')
|
|
|
|
|
|
|
|
return code_example
|
|
|
|
|
|
|
|
SUPPORTED_LANGUAGES = {
|
|
|
|
'python': {
|
|
|
|
'client_config': PYTHON_CLIENT_CONFIG,
|
|
|
|
'admin_config': PYTHON_CLIENT_ADMIN_CONFIG,
|
|
|
|
'render': render_python_code_example,
|
|
|
|
}
|
|
|
|
} # type: Dict[str, Any]
|
2018-01-26 22:08:42 +01:00
|
|
|
|
|
|
|
class APICodeExamplesGenerator(Extension):
|
|
|
|
def extendMarkdown(self, md: markdown.Markdown, md_globals: Dict[str, Any]) -> None:
|
|
|
|
md.preprocessors.add(
|
|
|
|
'generate_code_example', APICodeExamplesPreprocessor(md, self.getConfigs()), '_begin'
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class APICodeExamplesPreprocessor(Preprocessor):
|
|
|
|
def __init__(self, md: markdown.Markdown, config: Dict[str, Any]) -> None:
|
|
|
|
super(APICodeExamplesPreprocessor, self).__init__(md)
|
|
|
|
|
|
|
|
def run(self, lines: List[str]) -> List[str]:
|
|
|
|
done = False
|
|
|
|
while not done:
|
|
|
|
for line in lines:
|
|
|
|
loc = lines.index(line)
|
2018-02-16 04:09:21 +01:00
|
|
|
match = MACRO_REGEXP.search(line)
|
2018-01-26 22:08:42 +01:00
|
|
|
|
|
|
|
if match:
|
2018-02-16 04:09:21 +01:00
|
|
|
language = match.group(2)
|
|
|
|
function = match.group(3)
|
|
|
|
key = match.group(4)
|
|
|
|
argument = match.group(6)
|
2018-01-26 22:08:42 +01:00
|
|
|
|
|
|
|
if key == 'fixture':
|
2018-02-06 04:07:12 +01:00
|
|
|
if argument:
|
|
|
|
text = self.render_fixture(function, name=argument)
|
|
|
|
else:
|
|
|
|
text = self.render_fixture(function)
|
2018-02-02 19:28:35 +01:00
|
|
|
elif key == 'example':
|
2018-02-06 04:07:12 +01:00
|
|
|
if argument == 'admin_config=True':
|
2018-02-16 04:09:21 +01:00
|
|
|
text = SUPPORTED_LANGUAGES[language]['render'](function, admin_config=True)
|
2018-02-06 04:07:12 +01:00
|
|
|
else:
|
2018-02-16 04:09:21 +01:00
|
|
|
text = SUPPORTED_LANGUAGES[language]['render'](function)
|
2018-01-26 22:08:42 +01:00
|
|
|
|
|
|
|
# The line that contains the directive to include the macro
|
|
|
|
# may be preceded or followed by text or tags, in that case
|
|
|
|
# we need to make sure that any preceding or following text
|
|
|
|
# stays the same.
|
2018-02-16 04:09:21 +01:00
|
|
|
line_split = MACRO_REGEXP.split(line, maxsplit=0)
|
2018-01-26 22:08:42 +01:00
|
|
|
preceding = line_split[0]
|
|
|
|
following = line_split[-1]
|
|
|
|
text = [preceding] + text + [following]
|
|
|
|
lines = lines[:loc] + text + lines[loc+1:]
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
done = True
|
|
|
|
return lines
|
|
|
|
|
2018-02-06 04:07:12 +01:00
|
|
|
def render_fixture(self, function: str, name: Optional[str]=None) -> List[str]:
|
2018-01-26 22:08:42 +01:00
|
|
|
fixture = []
|
|
|
|
|
2018-05-15 19:28:42 +02:00
|
|
|
# We assume that if the function we're rendering starts with a slash
|
|
|
|
# it's a path in the endpoint and therefore it uses the new OpenAPI
|
|
|
|
# format.
|
|
|
|
if function.startswith('/'):
|
|
|
|
path, method = function.rsplit(':', 1)
|
|
|
|
fixture_dict = get_openapi_fixture(path, method, name)
|
2018-02-06 04:07:12 +01:00
|
|
|
else:
|
|
|
|
fixture_dict = zerver.lib.api_test_helpers.FIXTURES[function]
|
|
|
|
|
2018-02-21 00:40:38 +01:00
|
|
|
fixture_json = json.dumps(fixture_dict, indent=4, sort_keys=True,
|
|
|
|
separators=(',', ': '))
|
2018-01-26 22:08:42 +01:00
|
|
|
|
|
|
|
fixture.append('```')
|
|
|
|
fixture.extend(fixture_json.splitlines())
|
|
|
|
fixture.append('```')
|
|
|
|
|
|
|
|
return fixture
|
|
|
|
|
|
|
|
def makeExtension(*args: Any, **kwargs: str) -> APICodeExamplesGenerator:
|
2018-12-20 08:28:40 +01:00
|
|
|
return APICodeExamplesGenerator(**kwargs)
|