diff --git a/stone/backends/js_client.py b/stone/backends/js_client.py index 53d00642..14076850 100644 --- a/stone/backends/js_client.py +++ b/stone/backends/js_client.py @@ -25,11 +25,13 @@ 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', @@ -38,6 +40,7 @@ '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, @@ -52,6 +55,15 @@ 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 = {}; @@ -88,6 +100,17 @@ def _generate_route(self, route_schema, namespace, route): 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)) diff --git a/stone/backends/python_client.py b/stone/backends/python_client.py index f9957da4..edc88fd3 100644 --- a/stone/backends/python_client.py +++ b/stone/backends/python_client.py @@ -111,10 +111,7 @@ action='append', type=str, default=[], - help=('Route attributes that the backend will have access to and ' - 'presumably expose in generated code. Use ":all" to select all ' - 'attributes defined in stone_cfg.Route. Attributes will be ' - "exposed in the documentation, as the client doesn't use them."), + help=('Attributes to include in route documentation comments.'), ) diff --git a/stone/backends/tsd_client.py b/stone/backends/tsd_client.py index cf72939f..6c07b4f7 100644 --- a/stone/backends/tsd_client.py +++ b/stone/backends/tsd_client.py @@ -95,6 +95,14 @@ help=('If using the --import-namespaces flag, this is the file that contains ' 'the named exports to import here.') ) +_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. @@ -178,6 +186,18 @@ def _generate_route(self, namespace, route): if route.doc: self.emit_wrapped_text(self.process_doc(route.doc, self._docf), prefix=' * ') self.emit(' *') + + 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) + self.emit(' *') + self.emit_wrapped_text('When an error occurs, the route rejects the promise with type %s.' % fmt_error_type(route.error_data_type, wrap_error_in=self.args.wrap_error_in), prefix=' * ') diff --git a/test/test_js_client.py b/test/test_js_client.py index f03456a3..a8f605e0 100644 --- a/test/test_js_client.py +++ b/test/test_js_client.py @@ -20,11 +20,17 @@ def _get_api(self): api = Api(version='0.1b1') api.route_schema = Struct('Route', 'stone_cfg', None) route1 = ApiRoute('get_metadata', 1, None) - route1.set_attributes(None, ':route:`get_metadata`', Void(), Void(), Void(), {}) + route1.set_attributes(None, ':route:`get_metadata`', Void(), Void(), Void(), { + 'scope': 'events.read' + }) route2 = ApiRoute('get_metadata', 2, None) - route2.set_attributes(None, ':route:`get_metadata:2`', Void(), Int32(), Void(), {}) + route2.set_attributes(None, ':route:`get_metadata:2`', Void(), Int32(), Void(), { + 'scope': 'events.read' + }) route3 = ApiRoute('get_metadata', 3, None) - route3.set_attributes(None, ':route:`get_metadata:3`', Int32(), Int32(), Void(), {}) + route3.set_attributes(None, ':route:`get_metadata:3`', Int32(), Int32(), Void(), { + 'scope': None + }) ns = ApiNamespace('files') ns.add_route(route1) ns.add_route(route2) @@ -141,3 +147,54 @@ def test_route_with_version_number_conflict(self): backend.generate(api) self.assertTrue(str(cm.exception).startswith( 'There is a name conflict between')) + + def test_route_with_attributes_in_docstring(self): + # type: () -> None + + api, _ = self._get_api() + backend = JavascriptClientBackend( + target_folder_path='output', + args=['files', '-c', 'DropboxBase', '-a', 'scope']) + get_result = _mock_output(backend) + backend.generate(api) + result = get_result() + + expected = textwrap.dedent('''\ + // Auto-generated by Stone, do not modify. + var routes = {}; + + /** + * get_metadata + * Route attributes: + * scope: events.read + * @function DropboxBase#filesGetMetadata + * @returns {Promise.>} + */ + routes.filesGetMetadata = function () { + return this.request("files/get_metadata", null); + }; + + /** + * get_metadata_v2 + * Route attributes: + * scope: events.read + * @function DropboxBase#filesGetMetadataV2 + * @returns {Promise.>} + */ + routes.filesGetMetadataV2 = function () { + return this.request("files/get_metadata_v2", null); + }; + + /** + * get_metadata_v3 + * @function DropboxBase#filesGetMetadataV3 + * @arg {number} arg - The request parameters. + * @returns {Promise.>} + */ + routes.filesGetMetadataV3 = function (arg) { + return this.request("files/get_metadata_v3", arg); + }; + + export { routes }; + ''') + assert result == expected diff --git a/test/test_tsd_client.py b/test/test_tsd_client.py index f6e7a31d..e59fb898 100644 --- a/test/test_tsd_client.py +++ b/test/test_tsd_client.py @@ -19,11 +19,17 @@ def _get_api(self): api = Api(version='0.1b1') api.route_schema = Struct('Route', 'stone_cfg', None) route1 = ApiRoute('get_metadata', 1, None) - route1.set_attributes(None, ':route:`get_metadata`', Void(), Void(), Void(), {}) + route1.set_attributes(None, ':route:`get_metadata`', Void(), Void(), Void(), { + 'scope': 'events.read' + }) route2 = ApiRoute('get_metadata', 2, None) - route2.set_attributes(None, ':route:`get_metadata:2`', Void(), Int32(), Void(), {}) + route2.set_attributes(None, ':route:`get_metadata:2`', Void(), Int32(), Void(), { + 'scope': 'events.read' + }) route3 = ApiRoute('get_metadata', 3, None) - route3.set_attributes(None, ':route:`get_metadata:3`', Int32(), Int32(), Void(), {}) + route3.set_attributes(None, ':route:`get_metadata:3`', Int32(), Int32(), Void(), { + 'scope': None + }) ns = ApiNamespace('files') ns.add_route(route1) ns.add_route(route2) @@ -120,3 +126,45 @@ def test_route_with_version_number_conflict(self): backend._generate_routes(api, 0, 0) self.assertTrue(str(cm.exception).startswith( 'There is a name conflict between')) + + def test_route_with_attributes_in_docstring(self): + # type: () -> None + api, _ = self._get_api() + backend = TSDClientBackend( + target_folder_path="output", + args=['files', 'files', '-a', 'scope'] + ) + backend._generate_routes(api, 0, 0) + result = backend.output_buffer_to_string() + expected = textwrap.dedent( + '''\ + + /** + * getMetadata() + * + * Route attributes: + * scope: events.read + * + * When an error occurs, the route rejects the promise with type Error. + */ + public filesGetMetadata(): Promise; + + /** + * getMetadataV2() + * + * Route attributes: + * scope: events.read + * + * When an error occurs, the route rejects the promise with type Error. + */ + public filesGetMetadataV2(): Promise; + + /** + * getMetadataV3() + * + * When an error occurs, the route rejects the promise with type Error. + * @param arg The request parameters. + */ + public filesGetMetadataV3(arg: number): Promise; + ''') + self.assertEqual(result, expected)