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
|
from __future__ import absolute_import, division, print_function, unicode_literals
_MYPY = False
if _MYPY:
import typing # noqa: F401 # pylint: disable=import-error,unused-import,useless-suppression
from stone.ir import ApiNamespace
# Hack to get around some of Python 2's standard library modules that
# accept ascii-encodable unicode literals in lieu of strs, but where
# actually passing such literals results in errors with mypy --py2. See
# <https://github.com/python/typeshed/issues/756> and
# <https://github.com/python/mypy/issues/2536>.
import importlib
argparse = importlib.import_module(str('argparse')) # type: typing.Any
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')
)
_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=' * ')
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 = '%s<%s>' % (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
|