# -*- coding: utf-8 -*- # # Copyright (C) 2015 GNS3 Technologies Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import re import os.path import os from gns3server.handlers import * from gns3server.web.route import Route class Documentation(object): """Extract API documentation as Sphinx compatible files""" def __init__(self, route, directory): """ :param route: Route instance :param directory: Output directory """ self._documentation = route.get_documentation() self._directory = directory def write(self): self.write_documentation("compute") # Controller documentation self.write_documentation("controller") def write_documentation(self, doc_type): """ Build all the doc page for handlers :param doc_type: Type of doc to generate (controller, hypervisor) """ for handler_name in sorted(self._documentation): if "controller." in handler_name: server_type = "controller" elif "compute" in handler_name: server_type = "compute" else: server_type = "root" if doc_type != server_type: continue print("Build {}".format(handler_name)) for path in sorted(self._documentation[handler_name]): api_version = self._documentation[handler_name][path]["api_version"] if api_version is None: continue filename = self._file_path(path) handler_doc = self._documentation[handler_name][path] handler = handler_name.replace(server_type + ".", "") self._create_handler_directory(handler, api_version, server_type) with open("{}/api/v{}/{}/{}/{}.rst".format(self._directory, api_version, server_type, handler, filename), 'w+') as f: f.write('{}\n------------------------------------------------------------------------------------------------------------------------------------------\n\n'.format(path)) f.write('.. contents::\n') for method in handler_doc["methods"]: f.write('\n{} {}\n'.format(method["method"], path.replace("{", '**{').replace("}", "}**"))) f.write('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n') f.write('{}\n\n'.format(method["description"])) if len(method["parameters"]) > 0: f.write("Parameters\n**********\n") for parameter in method["parameters"]: desc = method["parameters"][parameter] f.write("- **{}**: {}\n".format(parameter, desc)) f.write("\n") f.write("Response status codes\n**********************\n") for code in method["status_codes"]: desc = method["status_codes"][code] f.write("- **{}**: {}\n".format(code, desc)) f.write("\n") if "properties" in method["input_schema"]: f.write("Input\n*******\n") self._write_definitions(f, method["input_schema"]) self._write_json_schema(f, method["input_schema"]) if "properties" in method["output_schema"]: f.write("Output\n*******\n") self._write_json_schema(f, method["output_schema"]) self._include_query_example(f, method, path, api_version, server_type) def _create_handler_directory(self, handler_name, api_version, server_type): """Create a directory for the handler and add an index inside""" directory = "{}/api/v{}/{}/{}".format(self._directory, api_version, server_type, handler_name) os.makedirs(directory, exist_ok=True) with open("{}/api/v{}/{}/{}.rst".format(self._directory, api_version, server_type, handler_name), "w+") as f: f.write(handler_name.replace("api.", "").replace("_", " ", ).capitalize()) f.write("\n-----------------------------\n\n") f.write(".. toctree::\n :glob:\n :maxdepth: 2\n\n {}/*\n".format(handler_name)) def _include_query_example(self, f, method, path, api_version, server_type): """If a sample session is available we include it in documentation""" m = method["method"].lower() query_path = "{}_{}_{}.txt".format(server_type, m, self._file_path(path)) if os.path.isfile(os.path.join(self._directory, "api", "examples", query_path)): f.write("Sample session\n***************\n") f.write("\n\n.. literalinclude:: ../../../examples/{}\n\n".format(query_path)) def _file_path(self, path): path = path.replace("compute", "") path = path.replace("controller", "") return re.sub("^v2", "", re.sub("[^a-z0-9]", "", path)) def _write_definitions(self, f, schema): if "definitions" in schema: f.write("Types\n+++++++++\n") for definition in sorted(schema['definitions']): desc = schema['definitions'][definition].get("description") f.write("{}\n^^^^^^^^^^^^^^^^^^^^^^\n{}\n\n".format(definition, desc)) self._write_json_schema(f, schema['definitions'][definition]) f.write("Body\n+++++++++\n") def _write_json_schema_object(self, f, obj): """ obj is current object in JSON schema schema is the whole schema including definitions """ for name in sorted(obj.get("properties", {})): prop = obj["properties"][name] mandatory = " " if name in obj.get("required", []): mandatory = "✔" if "enum" in prop: field_type = "enum" prop['description'] = "Possible values: {}".format(', '.join(prop['enum'])) else: field_type = prop.get("type", "") # Resolve oneOf relation to their human type. if field_type == 'object' and 'oneOf' in prop: field_type = ', '.join(map(lambda p: p['$ref'].split('/').pop(), prop['oneOf'])) f.write(" {}\ {} \ {} \ {} \ \n".format( name, mandatory, field_type, prop.get("description", "") )) def _write_json_schema(self, f, schema): # TODO: rewrite this using RST for portability f.write(".. raw:: html\n\n \n") f.write(" \ \ \ \ \ \n") self._write_json_schema_object(f, schema) f.write("
NameMandatoryTypeDescription
\n\n") if __name__ == '__main__': print("Generate API documentation") Documentation(Route, "docs").write()