#!/usr/bin/python # -*- coding: utf-8 -*- ################################################################################ ### @brief creates swagger json files from doc headers of rest files ### ### find files in ### arangod/RestHandler/*.cpp ### js/actions/api-*.js ### @usage generateSwagger.py < RestXXXX.cpp > restSwagger.json ### ### @file ### ### DISCLAIMER ### ### Copyright by triAGENS GmbH - All rights reserved. ### ### The Programs (which include both the software and documentation) ### contain proprietary information of triAGENS GmbH; they are ### provided under a license agreement containing restrictions on use and ### disclosure and are also protected by copyright, patent and other ### intellectual and industrial property laws. Reverse engineering, ### disassembly or decompilation of the Programs, except to the extent ### required to obtain interoperability with other independently created ### software or as specified by law, is prohibited. ### ### The Programs are not intended for use in any nuclear, aviation, mass ### transit, medical, or other inherently dangerous applications. It shall ### be the licensee's responsibility to take all appropriate fail-safe, ### backup, redundancy, and other measures to ensure the safe use of such ### applications if the Programs are used for such purposes, and triAGENS ### GmbH disclaims liability for any damages caused by such use of ### the Programs. ### ### This software is the confidential and proprietary information of ### triAGENS GmbH. You shall not disclose such confidential and ### proprietary information and shall use it only in accordance with the ### terms of the license agreement you entered into with triAGENS GmbH. ### ### Copyright holder is triAGENS GmbH, Cologne, Germany ### ### @author Thomas Richter ### @author Copyright 2013, triAGENS GmbH, Cologne, Germany ################################################################################ import sys, re, json, string operation = {} errorResponse = { 'code': None, 'reason': None } # api = {'path': '/_api/document', 'description': '', 'operations': []} swagger = {'apiVersion': '0.1', 'swaggerVersion': '1.1', 'basePath': '/', 'apis': [], } rc = re.compile MS = re.M | re.S def parameters(line): # suche die erste { # suche die letzten } # gib alles dazwischen zurck l, c, line =line.partition('{') line , c , r = line.rpartition('}') line = BackTicks(line, wordboundary = ['{','}']) return line def BackTicks(txt, wordboundary = ['','']): # `word` -> word r = rc(r"""([\(\s'/">]|^|.)\`(.*?)\`([<\s\.\),:;'"?!/-]|$)""", MS) subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3' return r.sub(subpattern, txt) def FA(txt, wordboundary = ['','']): # @FA{word} -> word r = rc(r"""([\(\s'/">]|^|.)@FA\{(.*?)\}([<\s\.\),:;'"?!/-]|$)""", MS) subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3' return r.sub(subpattern, txt) def FN(txt, wordboundary = ['','']): # @FN{word} -> word r = rc(r"""([\(\s'/">]|^|.)@FN\{(.*?)\}([<\s\.\),:;'"?!/-])""", MS) subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3' return r.sub(subpattern, txt) def LIT(txt, wordboundary = ['','']): # @LIT{word} -> word r = rc(r"""([\(\s'/">]|^)@LIT\{(.*?)\}([<\s\.\),:;'"?!/-])""", MS) subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3' return r.sub(subpattern, txt) def Typography(txt): txt = BackTicks(txt) txt = FN(txt) txt = LIT(txt) txt = FA(txt) # no way to find out the correct link for Swagger, #so replace all @ref elements with just "the manual" r = rc(r"""@ref [a-zA-Z0-9]+""", MS) txt = r.sub("the manual", txt) return txt class InitializationError(Exception): pass # idea from http://gnosis.cx/TPiP/chap4.txt class StateMachine: def __init__(self): self.handlers = [] self.startState = None self.endStates = [] def add_state(self, handler, end_state=0): self.handlers.append(handler) if end_state: self.endStates.append(handler) def set_start(self, handler): self.startState = handler def run(self, cargo=None): if not self.startState: raise InitializationError,\ "must call .set_start() before .run()" if not self.endStates: raise InitializationError, \ "at least one state must be an end_state" handler = self.startState while 1: (newState, cargo) = handler(cargo) if newState in self.endStates: newState(cargo) break elif newState not in self.handlers: raise RuntimeError, "Invalid target %s" % newState else: handler = newState class Regexen: def __init__(self): self.brief = re.compile('.*@brief') self.RESTHEADER = re.compile('.*@RESTHEADER{') self.RESTURLPARAMETERS = re.compile('.*@RESTURLPARAMETERS') self.RESTQUERYPARAMETERS = re.compile('.*@RESTQUERYPARAMETERS') self.RESTHEADERPARAMETERS = re.compile('.*@RESTHEADERPARAMETERS') self.RESTHEADERPARAM = re.compile('.*@RESTHEADERPARAM{') self.RESTBODYPARAM = re.compile('.*@RESTBODYPARAM') self.RESTURLPARAM = re.compile('.*@RESTURLPARAM{') self.RESTQUERYPARAM = re.compile('.*@RESTQUERYPARAM{') self.RESTDESCRIPTION = re.compile('.*@RESTDESCRIPTION') self.RESTRETURNCODES = re.compile('.*@RESTRETURNCODES') self.RESTRETURNCODE = re.compile('.*@RESTRETURNCODE{') self.EXAMPLES = re.compile('.*@EXAMPLES') self.EXAMPLE_ARANGOSH_RUN = re.compile('.*@EXAMPLE_ARANGOSH_RUN{|.*@verbinclude.*') self.END_EXAMPLE_ARANGOSH_RUN = re.compile('.*@END_EXAMPLE_ARANGOSH_RUN') self.read_through = re.compile('^[^/].*') self.EMPTY_COMMENT = re.compile('^/{3}\s*$') self.DESCRIPTION_LI = re.compile('^/{3}\s-.*$') # state maschine rules def resturlparameters(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.RESTURLPARAM.match(line): return resturlparam, (fp, line) elif r.RESTQUERYPARAMETERS.match(line): return restqueryparameters, (fp, line) elif r.RESTHEADERPARAMETERS.match(line): return restheaderparameters, (fp, line) elif r.RESTBODYPARAM.match(line): return restbodyparam, (fp, line) elif r.RESTDESCRIPTION.match(line): return restdescription, (fp, line) else: continue def resturlparam(cargo, r=Regexen()): fp, last = cargo parametersList = parameters(last).split(',') para = {} para['paramType'] = 'path' para['dataType'] = parametersList[1].capitalize() if parametersList[2] == 'required': para['required'] = 'true' else: para['required'] = 'false' para['name'] = parametersList[0] para['description']='' while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.EMPTY_COMMENT.match(line): operation['parameters'].append(para) return resturlparameters, (fp, line) else: para['description'] += Typography(line[4:-1]) + ' ' def restqueryparameters(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.RESTURLPARAMETERS.match(line): return resturlparameters, (fp, line) elif r.RESTHEADERPARAMETERS.match(line): return restheaderparameters, (fp, line) elif r.RESTBODYPARAM.match(line): return restbodyparam, (fp, line) elif r.RESTDESCRIPTION.match(line): return restdescription, (fp, line) elif r.RESTQUERYPARAM.match(line): return restqueryparam, (fp, line) else: continue def restheaderparameters(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.RESTHEADERPARAM.match(line): return restheaderparam, (fp, line) elif r.RESTQUERYPARAMETERS.match(line): return restqueryparameters, (fp, line) elif r.RESTURLPARAMETERS.match(line): return resturlparameters, (fp, line) elif r.RESTBODYPARAM.match(line): return restbodyparam, (fp, line) elif r.RESTDESCRIPTION.match(line): return restdescription, (fp, line) else: continue def restheaderparam(cargo, r=Regexen()): fp, last = cargo parametersList = parameters(last).split(',') para = {} para['paramType'] = 'header' para['dataType'] = parametersList[1].capitalize() para['name'] = parametersList[0] para['description']='' while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.EMPTY_COMMENT.match(line): operation['parameters'].append(para) return restheaderparameters, (fp, line) else: para['description'] += Typography(line[4:-1]) + ' ' def restbodyparam(cargo, r=Regexen()): fp, last = cargo parametersList = parameters(last).split(',') para = {} para['paramType'] = 'body' para['dataType'] = parametersList[1].capitalize() if parametersList[2] == 'required': para['required'] = 'true' para['name'] = parametersList[0] para['description']='' while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.RESTURLPARAMETERS.match(line): return resturlparameters, (fp, line) elif r.RESTHEADERPARAMETERS.match(line): return restheaderparameters, (fp, line) elif r.RESTQUERYPARAMETERS.match(line): return restqueryparameters, (fp, line) elif r.RESTDESCRIPTION.match(line): return restdescription, (fp, line) elif r.EMPTY_COMMENT.match(line): operation['parameters'].append(para) return comment, (fp, line) else: para['description'] += Typography(line[4:-1]) + ' ' def restqueryparam(cargo, r=Regexen()): fp, last = cargo parametersList = parameters(last).split(',') para = {} para['paramType'] = 'query' para['dataType'] = parametersList[1].capitalize() if parametersList[2] == 'required': para['required'] = 'True' para['name'] = parametersList[0] para['description']='' while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.EMPTY_COMMENT.match(line): operation['parameters'].append(para) return restqueryparameters, (fp, line) else: para['description'] += Typography(line[4:-1]) + ' ' def restdescription(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.EMPTY_COMMENT.match(line): if r.DESCRIPTION_LI.match(last): operation['notes'] += '

' else: operation['notes'] += '

' elif r.DESCRIPTION_LI.match(line): operation['notes'] += Typography(line[4:-1]) elif r.read_through.match(line): return read_through, (fp, line) elif r.EXAMPLES.match(line): return examples, (fp, line) elif len(line) >= 4 and line[:4] == "////": continue elif r.RESTRETURNCODES.match(line): return restreturncodes, (fp, line) else: operation['notes'] += Typography(line[4:-1]) + ' ' last = line def restreturncodes(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.RESTRETURNCODE.match(line): return restreturncode, (fp, line) elif r.EXAMPLES.match(line): operation['examples']= "" return examples, (fp, line) else: continue def restreturncode(cargo, r=Regexen()): fp, last = cargo returncode = {} returncode['code'] = parameters(last) returncode['reason'] = '' while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.EXAMPLES.match(line): operation['examples'] = '' return examples, (fp, line) elif r.EMPTY_COMMENT.match(line): operation['errorResponses'].append(returncode) return restreturncodes, (fp, line) else: returncode['reason'] += Typography(line[4:-1]) + ' ' def examples(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.EXAMPLE_ARANGOSH_RUN.match(line): return example_arangosh_run, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) elif r.EMPTY_COMMENT.match(line): continue elif len(line) >= 4 and line[:4] == "////": continue else: operation['examples'] += Typography(line[4:-1]) + ' ' def example_arangosh_run(cargo, r=Regexen()): fp, last = cargo import os # old examples code verbinclude = last[4:-1].split()[0] == "@verbinclude" if verbinclude: examplefile = open(os.path.join(os.path.dirname(__file__), '../Examples/' + last[4:-1].split()[1])) else: # new examples code TODO should include for each example own object in json file examplefile = open(os.path.join(os.path.dirname(__file__), '../Examples/' + parameters(last) + '.generated')) operation['examples'] += '

'
    for line in examplefile.readlines():
        operation['examples'] += line
    operation['examples'] += '

' while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.END_EXAMPLE_ARANGOSH_RUN.match(line) or verbinclude: return examples, (fp, line) elif r.read_through.match(line): return read_through, (fp, line) def eof(cargo): print json.dumps(swagger, indent=4, separators=(',',': ')) def error(cargo): sys.stderr.write('Unidentifiable line:\n' + line) def comment(cargo, r=Regexen()): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif r.RESTHEADER.match(line): temp = parameters(line).split(',') method, path = temp[0].split() summary = temp[1] '# create new api' api = {} api['path'] = FA(path, wordboundary = ['{', '}']) api['operations']=[] swagger['apis'].append(api) _operation = { 'httpMethod': None, 'nickname': None, 'parameters': [], 'summary': None, 'notes': '', 'examples': '', 'errorResponses':[]} _operation['httpMethod'] = method summaryList = summary.split() _operation['nickname'] = summaryList[0] + ''.join([word.capitalize() for word in summaryList[1:]]) _operation['summary'] = summary api['operations'].append(_operation) global operation operation = _operation elif r.RESTURLPARAMETERS.match(line): return resturlparameters, (fp, line) elif r.RESTHEADERPARAMETERS.match(line): return restheaderparameters, (fp, line) elif r.RESTBODYPARAM.match(line): return restbodyparam, (fp, line) elif r.RESTQUERYPARAMETERS.match(line): return restqueryparameters, (fp, line) elif r.RESTDESCRIPTION.match(line): return restdescription, (fp, line) elif len(line) >= 4 and line[:4] == "////": continue elif len(line) >= 3 and line[:3] =="///": continue else: return read_through, (fp, line) # skip all non comment lines def read_through(cargo): fp, last = cargo while 1: line = fp.readline() if not line: return eof, (fp, line) elif len(line) >= 3 and line[:3] == "///": return comment, (fp, line) else: continue if __name__ == "__main__": automat = StateMachine() automat.add_state(read_through) automat.add_state(eof, end_state=1) automat.add_state(comment) automat.add_state(resturlparameters) automat.add_state(resturlparam) automat.add_state(restqueryparameters) automat.add_state(restqueryparam) automat.add_state(restbodyparam) automat.add_state(restheaderparameters) automat.add_state(restheaderparam) automat.add_state(restdescription) automat.add_state(restreturncodes) automat.add_state(restreturncode) automat.add_state(examples) automat.add_state(example_arangosh_run) automat.add_state(error, end_state=1) automat.set_start(read_through) automat.run((sys.stdin, '')) # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4