1
0
Fork 0
arangodb/Documentation/Scripts/generateSwagger.py

690 lines
22 KiB
Python
Executable File

#!/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 2004-2014 triAGENS GmbH, Cologne, Germany
###
### Licensed under the Apache License, Version 2.0 (the "License");
### you may not use this file except in compliance with the License.
### You may obtain a copy of the License at
###
### http://www.apache.org/licenses/LICENSE-2.0
###
### Unless required by applicable law or agreed to in writing, software
### distributed under the License is distributed on an "AS IS" BASIS,
### WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
### See the License for the specific language governing permissions and
### limitations under the License.
###
### Copyright holder is triAGENS GmbH, Cologne, Germany
###
### @author Dr. Frank Celler
### @author Thomas Richter
### @author Copyright 2014, triAGENS GmbH, Cologne, Germany
################################################################################
import sys, re, json, string, os
rc = re.compile
MS = re.M | re.S
################################################################################
### @brief swagger
################################################################################
swagger = {
'apiVersion': '0.1',
'swaggerVersion': '1.1',
'basePath': '/',
'apis': []
}
################################################################################
### @brief operation
################################################################################
operation = {}
################################################################################
### @brief C_FILE
################################################################################
C_FILE = False
################################################################################
### @brief DEBUG
################################################################################
DEBUG = False
################################################################################
### @brief trim_text
################################################################################
def trim_text(txt):
r = rc(r"""[ \t]+$""")
txt = r.sub("", txt)
return txt
################################################################################
### @brief parameters
###
### suche die erste {
### suche die letzten }
### gib alles dazwischen zurck
################################################################################
def parameters(line):
(l, c, line) = line.partition('{')
(line, c , r) = line.rpartition('}')
line = BackTicks(line, wordboundary = ['{','}'])
return line
################################################################################
### @brief BackTicks
###
### `word` -> <b>word</b>
################################################################################
def BackTicks(txt, wordboundary = ['<em>','</em>']):
r = rc(r"""([\(\s'/">]|^|.)\`(.*?)\`([<\s\.\),:;'"?!/-]|$)""", MS)
subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3'
return r.sub(subpattern, txt)
################################################################################
### @brief AsteriskItalic
###
### *word* -> <b>word</b>
################################################################################
def AsteriskItalic(txt, wordboundary = ['<em>','</em>']):
r = rc(r"""([\(\s'/">]|^|.)\*(.*?)\*([<\s\.\),:;'"?!/-]|$)""", MS)
subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3'
return r.sub(subpattern, txt)
################################################################################
### @brief AsteriskBold
###
### **word** -> <b>word</b>
################################################################################
def AsteriskBold(txt, wordboundary = ['<em>','</em>']):
r = rc(r"""([\(\s'/">]|^|.)\*\*(.*?)\*\*([<\s\.\),:;'"?!/-]|$)""", MS)
subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3'
return r.sub(subpattern, txt)
################################################################################
### @brief FA
###
### @FA{word} -> <b>word</b>
################################################################################
def FA(txt, wordboundary = ['<b>','</b>']):
r = rc(r"""([\(\s'/">]|^|.)@FA\{(.*?)\}([<\s\.\),:;'"?!/-]|$)""", MS)
subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3'
return r.sub(subpattern, txt)
################################################################################
### @brief FN
###
### @FN{word} -> <b>word</b>
################################################################################
def FN(txt, wordboundary = ['<b>','</b>']):
r = rc(r"""([\(\s'/">]|^|.)@FN\{(.*?)\}([<\s\.\),:;'"?!/-])""", MS)
subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3'
return r.sub(subpattern, txt)
################################################################################
### @brief LIT
###
### @LIT{word} -> <b>word</b>
################################################################################
def LIT(txt, wordboundary = ['<b>','</b>']):
r = rc(r"""([\(\s'/">]|^)@LIT\{(.*?)\}([<\s\.\),:;'"?!/-])""", MS)
subpattern = '\\1' + wordboundary[0] + '\\2' + wordboundary[1] + '\\3'
return r.sub(subpattern, txt)
################################################################################
### @brief Typegraphy
################################################################################
def Typography(txt):
if C_FILE:
txt = txt[4:-1]
else:
txt = txt[0:-1]
txt = BackTicks(txt)
txt = AsteriskBold(txt)
txt = AsteriskItalic(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)
txt = re.sub(r"@endDocuBlock", "", txt)
return txt
################################################################################
### @brief InitializationError
################################################################################
class InitializationError(Exception): pass
################################################################################
### @brief StateMachine
################################################################################
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
################################################################################
### @brief Regexen
################################################################################
class Regexen:
def __init__(self):
self.DESCRIPTION_LI = re.compile('^-\s.*$')
self.DESCRIPTION_SP = re.compile('^\s\s.*$')
self.DESCRIPTION_BL = re.compile('^\s*$')
self.EMPTY_LINE = re.compile('^\s*$')
self.END_EXAMPLE_ARANGOSH_RUN = re.compile('.*@END_EXAMPLE_ARANGOSH_RUN')
self.EXAMPLES = re.compile('.*@EXAMPLES')
self.EXAMPLE_ARANGOSH_RUN = re.compile('.*@EXAMPLE_ARANGOSH_RUN{')
self.FILE = re.compile('.*@file')
self.RESTBODYPARAM = re.compile('.*@RESTBODYPARAM')
self.RESTDESCRIPTION = re.compile('.*@RESTDESCRIPTION')
self.RESTDONE = re.compile('.*@RESTDONE')
self.RESTHEADER = re.compile('.*@RESTHEADER{')
self.RESTHEADERPARAM = re.compile('.*@RESTHEADERPARAM{')
self.RESTHEADERPARAMETERS = re.compile('.*@RESTHEADERPARAMETERS')
self.RESTQUERYPARAM = re.compile('.*@RESTQUERYPARAM{')
self.RESTQUERYPARAMETERS = re.compile('.*@RESTQUERYPARAMETERS')
self.RESTRETURNCODE = re.compile('.*@RESTRETURNCODE{')
self.RESTRETURNCODES = re.compile('.*@RESTRETURNCODES')
self.RESTURLPARAM = re.compile('.*@RESTURLPARAM{')
self.RESTURLPARAMETERS = re.compile('.*@RESTURLPARAMETERS')
self.NON_COMMENT = re.compile('^[^/].*')
################################################################################
### @brief checks for end of comment
################################################################################
def check_end_of_comment(line, r):
if C_FILE:
return r.NON_COMMENT.match(line)
else:
return r.RESTDONE.match(line)
################################################################################
### @brief next_step
################################################################################
def next_step(fp, line, r):
global operation
if not line: return eof, (fp, line)
elif check_end_of_comment(line, r): return skip_code, (fp, line)
elif r.EXAMPLE_ARANGOSH_RUN.match(line): return example_arangosh_run, (fp, line)
elif r.RESTBODYPARAM.match(line): return restbodyparam, (fp, line)
elif r.RESTDESCRIPTION.match(line): return restdescription, (fp, line)
elif r.RESTHEADER.match(line): return restheader, (fp, line)
elif r.RESTHEADERPARAM.match(line): return restheaderparam, (fp, line)
elif r.RESTHEADERPARAMETERS.match(line): return restheaderparameters, (fp, line)
elif r.RESTQUERYPARAM.match(line): return restqueryparam, (fp, line)
elif r.RESTQUERYPARAMETERS.match(line): return restqueryparameters, (fp, line)
elif r.RESTRETURNCODE.match(line): return restreturncode, (fp, line)
elif r.RESTRETURNCODES.match(line): return restreturncodes, (fp, line)
elif r.RESTURLPARAM.match(line): return resturlparam, (fp, line)
elif r.RESTURLPARAMETERS.match(line): return resturlparameters, (fp, line)
if r.EXAMPLES.match(line):
operation['examples'] = ""
return examples, (fp, line)
return None, None
################################################################################
### @brief generic handler
################################################################################
def generic_handler(cargo, r, message):
global DEBUG
if DEBUG: print >> sys.stderr, message
(fp, last) = cargo
while 1:
(next, c) = next_step(fp, fp.readline(), r)
if next:
return next, c
################################################################################
### @brief generic handler with description
################################################################################
def generic_handler_desc(cargo, r, message, op, para, name):
global DEBUG, C_FILE, operation
if DEBUG: print >> sys.stderr, message
(fp, last) = cargo
inLI = False
inUL = False
while 1:
line = fp.readline()
(next, c) = next_step(fp, line, r)
if next:
para[name] = trim_text(para[name])
if op:
operation[op].append(para)
return next, c
if C_FILE and line[0:4] == "////":
continue
line = Typography(line)
if r.DESCRIPTION_LI.match(line):
line = "<li>" + line[2:]
inLI = True
elif inLI and r.DESCRIPTION_SP.match(line):
line = line[2:]
elif inLI and r.DESCRIPTION_BL.match(line):
line = ""
else:
inLI = False
if not inUL and inLI:
line = " <ul class=\"swagger-list\">" + line
inUL = True
elif inUL and not inLI:
line = "</ul> " + line
inUL = False
if not inLI and r.EMPTY_LINE.match(line):
line = "<br><br>"
para[name] += line + ' '
################################################################################
### @brief restheader
################################################################################
def restheader(cargo, r=Regexen()):
global swagger, operation
(fp, last) = cargo
temp = parameters(last).split(',')
(method, path) = temp[0].split()
summary = temp[1]
summaryList = summary.split()
nickname = summaryList[0] + ''.join([word.capitalize() for word in summaryList[1:]])
operation = {
'httpMethod': method,
'nickname': nickname,
'parameters': [],
'summary': summary,
'notes': '',
'examples': '',
'errorResponses': []
}
api = {
'path': FA(path, wordboundary = ['{', '}']),
'operations': [ operation ]
}
swagger['apis'].append(api)
return generic_handler(cargo, r, "resturlparameters")
################################################################################
### @brief resturlparameters
################################################################################
def resturlparameters(cargo, r=Regexen()):
return generic_handler(cargo, r, "resturlparameters")
################################################################################
### @brief resturlparam
################################################################################
def resturlparam(cargo, r=Regexen()):
(fp, last) = cargo
parametersList = parameters(last).split(',')
if parametersList[2] == 'required':
required = True
else:
required = False
para = {
'name': parametersList[0],
'paramType': 'path',
'description': '',
'dataType': parametersList[1].capitalize(),
'required': required
}
return generic_handler_desc(cargo, r, "resturlparam", 'parameters', para, 'description')
################################################################################
### @brief restqueryparameters
################################################################################
def restqueryparameters(cargo, r=Regexen()):
return generic_handler(cargo, r, "restqueryparameters")
################################################################################
### @brief restheaderparameters
################################################################################
def restheaderparameters(cargo, r=Regexen()):
return generic_handler(cargo, r, "restheaderparameters")
################################################################################
### @brief restheaderparameters
################################################################################
def restheaderparam(cargo, r=Regexen()):
(fp, last) = cargo
parametersList = parameters(last).split(',')
para = {
'paramType': 'header',
'dataType': parametersList[1].capitalize(),
'name': parametersList[0],
'description': ''
}
return generic_handler_desc(cargo, r, "restheaderparam", 'parameters', para, 'description')
################################################################################
### @brief restbodyparam
################################################################################
def restbodyparam(cargo, r=Regexen()):
(fp, last) = cargo
parametersList = parameters(last).split(',')
if parametersList[2] == 'required':
required = True
else:
required = False
para = {
'name': parametersList[0],
'paramType': 'body',
'description': '',
'dataType': parametersList[1].capitalize(),
'required': required
}
return generic_handler_desc(cargo, r, "restbodyparam", 'parameters', para, 'description')
################################################################################
### @brief restqueryparam
################################################################################
def restqueryparam(cargo, r=Regexen()):
(fp, last) = cargo
parametersList = parameters(last).split(',')
if parametersList[2] == 'required':
required = True
else:
required = False
para = {
'name': parametersList[0],
'paramType': 'query',
'description': '',
'dataType': parametersList[1].capitalize(),
'required': required
}
return generic_handler_desc(cargo, r, "restqueryparam", 'parameters', para, 'description')
################################################################################
### @brief restdescription
################################################################################
def restdescription(cargo, r=Regexen()):
return generic_handler_desc(cargo, r, "restdescription", None, operation, 'notes')
################################################################################
### @brief restreturncodes
################################################################################
def restreturncodes(cargo, r=Regexen()):
return generic_handler(cargo, r, "restreturncodes")
################################################################################
### @brief restreturncode
################################################################################
def restreturncode(cargo, r=Regexen()):
(fp, last) = cargo
returncode = {
'code': parameters(last),
'reason': ''
}
return generic_handler_desc(cargo, r, "restreturncode", 'errorResponses', returncode, 'reason')
################################################################################
### @brief examples
################################################################################
def examples(cargo, r=Regexen()):
return generic_handler_desc(cargo, r, "examples", None, operation, 'examples')
################################################################################
### @brief example_arangosh_run
################################################################################
def example_arangosh_run(cargo, r=Regexen()):
global DEBUG, C_FILE
if DEBUG: print >> sys.stderr, "example_arangosh_run"
fp, last = cargo
# 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'] += '<br><br><pre><code class="json">'
for line in examplefile.readlines():
operation['examples'] += line
operation['examples'] += '</code></pre><br>'
line = ""
while not r.END_EXAMPLE_ARANGOSH_RUN.match(line):
line = fp.readline()
if not line:
return eof, (fp, line)
return examples, (fp, line)
################################################################################
### @brief eof
################################################################################
def eof(cargo):
global DEBUG, C_FILE
if DEBUG: print >> sys.stderr, "eof"
print json.dumps(swagger, indent=4, separators=(',',': '))
################################################################################
### @brief error
################################################################################
def error(cargo):
global DEBUG, C_FILE
if DEBUG: print >> sys.stderr, "error"
sys.stderr.write('Unidentifiable line:\n' + cargo)
################################################################################
### @brief comment
################################################################################
def comment(cargo, r=Regexen()):
global DEBUG, C_FILE
if DEBUG: print >> sys.stderr, "comment"
(fp, last) = cargo
while 1:
line = fp.readline()
if not line: return eof, (fp, line)
if r.FILE.match(line): C_FILE = True
next, c = next_step(fp, line, r)
if next:
return next, c
################################################################################
### @brief skip_code
###
### skip all non comment lines
################################################################################
def skip_code(cargo, r=Regexen()):
global DEBUG, C_FILE
if DEBUG: print >> sys.stderr, "skip_code"
(fp, last) = cargo
if not C_FILE:
return comment((fp, last), r)
while 1:
line = fp.readline()
if not line:
return eof, (fp, line)
if not r.NON_COMMENT.match(line):
return comment((fp, line), r)
################################################################################
### @brief main
################################################################################
if __name__ == "__main__":
automat = StateMachine()
automat.add_state(comment)
automat.add_state(eof, end_state=1)
automat.add_state(error, end_state=1)
automat.add_state(example_arangosh_run)
automat.add_state(examples)
automat.add_state(skip_code)
automat.add_state(restbodyparam)
automat.add_state(restdescription)
automat.add_state(restheader)
automat.add_state(restheaderparam)
automat.add_state(restheaderparameters)
automat.add_state(restqueryparam)
automat.add_state(restqueryparameters)
automat.add_state(restreturncode)
automat.add_state(restreturncodes)
automat.add_state(resturlparam)
automat.add_state(resturlparameters)
automat.set_start(skip_code)
automat.run((sys.stdin, ''))
## -----------------------------------------------------------------------------
## --SECTION-- END-OF-FILE
## -----------------------------------------------------------------------------
## Local Variables:
## mode: outline-minor
## outline-regexp: "^\\(### @brief\\|## --SECTION--\\|# -\\*- \\)"
## End:
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4