1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
_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
|