173 lines
5.8 KiB
Python
173 lines
5.8 KiB
Python
|
|
_MYPY = False
|
||
|
|
if _MYPY:
|
||
|
|
import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression
|
||
|
|
from stone.ir import ApiNamespace
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
|
||
|
|
from stone.backend import CodeBackend
|
||
|
|
from stone.backends.js_helpers import (
|
||
|
|
check_route_name_conflict,
|
||
|
|
fmt_error_type,
|
||
|
|
fmt_func,
|
||
|
|
fmt_obj,
|
||
|
|
fmt_type,
|
||
|
|
fmt_url,
|
||
|
|
)
|
||
|
|
from stone.ir import Void
|
||
|
|
|
||
|
|
_cmdline_parser = argparse.ArgumentParser(prog='js-client-backend')
|
||
|
|
|
||
|
|
_cmdline_parser.add_argument(
|
||
|
|
'filename',
|
||
|
|
help=('The name to give the single Javascript file that is created and '
|
||
|
|
'contains all of the routes.'),
|
||
|
|
)
|
||
|
|
|
||
|
|
_cmdline_parser.add_argument(
|
||
|
|
'-c',
|
||
|
|
'--class-name',
|
||
|
|
type=str,
|
||
|
|
help=('The name of the class the generated functions will be attached to. '
|
||
|
|
'The name will be added to each function documentation, which makes '
|
||
|
|
'it available for tools like JSDoc.'),
|
||
|
|
)
|
||
|
|
|
||
|
|
_cmdline_parser.add_argument(
|
||
|
|
'--wrap-response-in',
|
||
|
|
type=str,
|
||
|
|
default='',
|
||
|
|
help=('Wraps the response in a response class')
|
||
|
|
)
|
||
|
|
|
||
|
|
_cmdline_parser.add_argument(
|
||
|
|
'--wrap-error-in',
|
||
|
|
type=str,
|
||
|
|
default='',
|
||
|
|
help=('Wraps the error in an error class')
|
||
|
|
)
|
||
|
|
|
||
|
|
_cmdline_parser.add_argument(
|
||
|
|
'-a',
|
||
|
|
'--attribute-comment',
|
||
|
|
action='append',
|
||
|
|
type=str,
|
||
|
|
default=[],
|
||
|
|
help=('Attributes to include in route documentation comments.'),
|
||
|
|
)
|
||
|
|
|
||
|
|
_header = """\
|
||
|
|
// Auto-generated by Stone, do not modify.
|
||
|
|
var routes = {};
|
||
|
|
"""
|
||
|
|
|
||
|
|
|
||
|
|
class JavascriptClientBackend(CodeBackend):
|
||
|
|
"""Generates a single Javascript file with all of the routes defined."""
|
||
|
|
|
||
|
|
cmdline_parser = _cmdline_parser
|
||
|
|
|
||
|
|
# Instance var of the current namespace being generated
|
||
|
|
cur_namespace = None # type: typing.Optional[ApiNamespace]
|
||
|
|
|
||
|
|
preserve_aliases = True
|
||
|
|
|
||
|
|
def generate(self, api):
|
||
|
|
# first check for route name conflict
|
||
|
|
with self.output_to_relative_path(self.args.filename):
|
||
|
|
self.emit_raw(_header)
|
||
|
|
for namespace in api.namespaces.values():
|
||
|
|
# Hack: needed for _docf()
|
||
|
|
self.cur_namespace = namespace
|
||
|
|
|
||
|
|
check_route_name_conflict(namespace)
|
||
|
|
for route in namespace.routes:
|
||
|
|
self._generate_route(api.route_schema, namespace, route)
|
||
|
|
self.emit()
|
||
|
|
self.emit('export { routes };')
|
||
|
|
|
||
|
|
def _generate_route(self, route_schema, namespace, route):
|
||
|
|
function_name = fmt_func(namespace.name + '_' + route.name, route.version)
|
||
|
|
self.emit()
|
||
|
|
self.emit('/**')
|
||
|
|
if route.doc:
|
||
|
|
self.emit_wrapped_text(self.process_doc(route.doc, self._docf), prefix=' * ')
|
||
|
|
|
||
|
|
attrs_lines = []
|
||
|
|
if self.args.attribute_comment and route.attrs:
|
||
|
|
for attribute in self.args.attribute_comment:
|
||
|
|
if attribute in route.attrs and route.attrs[attribute] is not None:
|
||
|
|
attrs_lines.append(' * {}: {}'.format(attribute, route.attrs[attribute]))
|
||
|
|
if attrs_lines:
|
||
|
|
self.emit(' * Route attributes:')
|
||
|
|
for a in attrs_lines:
|
||
|
|
self.emit(a)
|
||
|
|
|
||
|
|
if self.args.class_name:
|
||
|
|
self.emit(' * @function {}#{}'.format(self.args.class_name,
|
||
|
|
function_name))
|
||
|
|
if route.deprecated:
|
||
|
|
self.emit(' * @deprecated')
|
||
|
|
|
||
|
|
return_type = None
|
||
|
|
if self.args.wrap_response_in:
|
||
|
|
return_type = '{}<{}>'.format(self.args.wrap_response_in,
|
||
|
|
fmt_type(route.result_data_type))
|
||
|
|
else:
|
||
|
|
return_type = fmt_type(route.result_data_type)
|
||
|
|
|
||
|
|
if route.arg_data_type.__class__ != Void:
|
||
|
|
self.emit(' * @arg {%s} arg - The request parameters.' %
|
||
|
|
fmt_type(route.arg_data_type))
|
||
|
|
self.emit(' * @returns {Promise.<%s, %s>}' %
|
||
|
|
(return_type,
|
||
|
|
fmt_error_type(route.error_data_type, self.args.wrap_error_in)))
|
||
|
|
self.emit(' */')
|
||
|
|
|
||
|
|
if route.arg_data_type.__class__ != Void:
|
||
|
|
self.emit('routes.%s = function (arg) {' % (function_name))
|
||
|
|
else:
|
||
|
|
self.emit('routes.%s = function () {' % (function_name))
|
||
|
|
with self.indent(dent=2):
|
||
|
|
url = fmt_url(namespace.name, route.name, route.version)
|
||
|
|
if route_schema.fields:
|
||
|
|
additional_args = []
|
||
|
|
for field in route_schema.fields:
|
||
|
|
additional_args.append(fmt_obj(route.attrs[field.name]))
|
||
|
|
if route.arg_data_type.__class__ != Void:
|
||
|
|
self.emit(
|
||
|
|
"return this.request('{}', arg, {});".format(
|
||
|
|
url, ', '.join(additional_args)))
|
||
|
|
else:
|
||
|
|
self.emit(
|
||
|
|
"return this.request('{}', null, {});".format(
|
||
|
|
url, ', '.join(additional_args)))
|
||
|
|
else:
|
||
|
|
if route.arg_data_type.__class__ != Void:
|
||
|
|
self.emit(
|
||
|
|
'return this.request("%s", arg);' % url)
|
||
|
|
else:
|
||
|
|
self.emit(
|
||
|
|
'return this.request("%s", null);' % url)
|
||
|
|
self.emit('};')
|
||
|
|
|
||
|
|
def _docf(self, tag, val):
|
||
|
|
"""
|
||
|
|
Callback used as the handler argument to process_docs(). This converts
|
||
|
|
Stone doc references to JSDoc-friendly annotations.
|
||
|
|
"""
|
||
|
|
# TODO(kelkabany): We're currently just dropping all doc ref tags ...
|
||
|
|
# NOTE(praneshp): ... except for versioned routes
|
||
|
|
if tag == 'route':
|
||
|
|
if ':' in val:
|
||
|
|
val, version = val.split(':', 1)
|
||
|
|
version = int(version)
|
||
|
|
else:
|
||
|
|
version = 1
|
||
|
|
url = fmt_url(self.cur_namespace.name, val, version)
|
||
|
|
# NOTE: In js, for comments, we drop the namespace name and the '/' when
|
||
|
|
# documenting URLs
|
||
|
|
return url[(len(self.cur_namespace.name) + 1):]
|
||
|
|
|
||
|
|
return val
|