mirror of https://gitee.com/bigwinds/arangodb
1590 lines
57 KiB
Python
Executable File
1590 lines
57 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
|
|
import re
|
|
import json
|
|
import operator
|
|
import os
|
|
import os.path
|
|
#string,
|
|
#from pygments import highlight
|
|
#from pygments.lexers import YamlLexer
|
|
#from pygments.formatters import TerminalFormatter
|
|
|
|
#, yaml
|
|
#import ruamel.yaml as yaml
|
|
rc = re.compile
|
|
MS = re.M | re.S
|
|
|
|
################################################################################
|
|
### @brief swagger
|
|
################################################################################
|
|
|
|
swagger = {
|
|
"swagger": "2.0",
|
|
"info": {
|
|
"description": "ArangoDB REST API Interface",
|
|
"version": "1.0",
|
|
"title": "ArangoDB",
|
|
"license": {
|
|
"name": "Apache License, Version 2.0"
|
|
}
|
|
},
|
|
"basePath": "/",
|
|
"definitions": {
|
|
"ARANGO_ERROR": {
|
|
"description": "An ArangoDB Error code",
|
|
"type": "integer"
|
|
},
|
|
"ArangoError": {
|
|
"description": "the arangodb error type",
|
|
"properties": {
|
|
"code": {
|
|
"description": "the HTTP Status code",
|
|
"type": "integer"
|
|
},
|
|
"error": {
|
|
"description": "boolean flag to indicate whether an error occurred (*true* in this case)",
|
|
"type": "boolean"
|
|
},
|
|
"errorMessage": {
|
|
"description": "a descriptive error message describing what happened, may contain additional information",
|
|
"type": "string"
|
|
},
|
|
"errorNum": {
|
|
"description": "the ARANGO_ERROR code",
|
|
"type": "integer"
|
|
}
|
|
}
|
|
}
|
|
|
|
},
|
|
"paths" : {}
|
|
}
|
|
|
|
################################################################################
|
|
### @brief native swagger types
|
|
################################################################################
|
|
|
|
swaggerBaseTypes = [
|
|
'object',
|
|
'array',
|
|
'number',
|
|
'integer',
|
|
'long',
|
|
'float',
|
|
'double',
|
|
'string',
|
|
'byte',
|
|
'binary',
|
|
'boolean',
|
|
'date',
|
|
'dateTime',
|
|
'password'
|
|
]
|
|
|
|
swaggerFormats = {
|
|
"number": ["", "float", "double"],
|
|
"integer": ["", "int32", "int64"]
|
|
}
|
|
|
|
################################################################################
|
|
### @brief length of the swagger definition namespace
|
|
################################################################################
|
|
|
|
defLen = len('#/definitions/')
|
|
|
|
################################################################################
|
|
### @brief operation
|
|
################################################################################
|
|
|
|
httpPath = ''
|
|
|
|
################################################################################
|
|
### @brief operation
|
|
################################################################################
|
|
|
|
method = ''
|
|
|
|
################################################################################
|
|
### @brief operation
|
|
################################################################################
|
|
|
|
operation = {}
|
|
|
|
################################################################################
|
|
### @brief current filename
|
|
################################################################################
|
|
|
|
fn = ''
|
|
|
|
################################################################################
|
|
### @brief current section
|
|
################################################################################
|
|
|
|
currentTag = ''
|
|
|
|
################################################################################
|
|
### @brief current docublock
|
|
################################################################################
|
|
|
|
currentDocuBlock = None
|
|
lastDocuBlock = None
|
|
|
|
################################################################################
|
|
### @brief index of example block we're reading
|
|
################################################################################
|
|
|
|
currentExample = 0
|
|
|
|
################################################################################
|
|
### @brief the current returncode we're working on
|
|
################################################################################
|
|
|
|
currentReturnCode = 0
|
|
|
|
################################################################################
|
|
### @brief collect json body parameter definitions:
|
|
################################################################################
|
|
|
|
restBodyParam = None
|
|
restReplyBodyParam = None
|
|
restSubBodyParam = None
|
|
|
|
|
|
operationIDs = []
|
|
|
|
################################################################################
|
|
### @brief DEBUG
|
|
################################################################################
|
|
|
|
DEBUG = True
|
|
DEBUG = False
|
|
|
|
################################################################################
|
|
### @brief facility to remove leading and trailing html-linebreaks
|
|
################################################################################
|
|
removeTrailingBR = re.compile("<br>$")
|
|
removeLeadingBR = re.compile("^<br>")
|
|
|
|
def brTrim(text):
|
|
return removeLeadingBR.sub("", removeTrailingBR.sub("", text.strip(' ')))
|
|
|
|
################################################################################
|
|
### @brief check for token to be right
|
|
################################################################################
|
|
|
|
reqOpt = ["required", "optional"]
|
|
def CheckReqOpt(token):
|
|
if token not in reqOpt:
|
|
print >> sys.stderr, "This is supposed to be required or optional!"
|
|
raise Exception("invalid value '%s'" % token)
|
|
|
|
################################################################################
|
|
### @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=['<strong>', '</strong>']):
|
|
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=['<strong>', '</strong>']):
|
|
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 LIT
|
|
###
|
|
### \ -> needs to become \\ so \n's in the text can be differciated.
|
|
################################################################################
|
|
|
|
def BACKSLASH(txt):
|
|
return txt.replace('\\', '\\\\\\')
|
|
|
|
################################################################################
|
|
### @brief Typegraphy
|
|
################################################################################
|
|
|
|
def Typography(txt):
|
|
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)
|
|
txt = BACKSLASH(txt)
|
|
return txt
|
|
|
|
################################################################################
|
|
### @brief InitializationError
|
|
################################################################################
|
|
|
|
class InitializationError(Exception):
|
|
pass
|
|
|
|
################################################################################
|
|
### @brief StateMachine
|
|
################################################################################
|
|
|
|
class StateMachine:
|
|
def __init__(self):
|
|
self.handlers = []
|
|
self.startState = None
|
|
self.endStates = []
|
|
self.file = ''
|
|
self.fn = ''
|
|
|
|
def add_state(self, handler, end_state=0):
|
|
self.handlers.append(handler)
|
|
|
|
if end_state:
|
|
self.endStates.append(handler)
|
|
|
|
def set_fn(self, filename):
|
|
self.fn = filename
|
|
global fn
|
|
fn = filename
|
|
|
|
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
|
|
try:
|
|
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
|
|
except Exception as x:
|
|
print >> sys.stderr, "while parsing '" + self.fn + "'"
|
|
print >> sys.stderr, "trying to use handler '" + handler.__name__ + "'"
|
|
raise x
|
|
|
|
################################################################################
|
|
### @brief Regexen
|
|
################################################################################
|
|
|
|
class Regexen:
|
|
def __init__(self):
|
|
self.DESCRIPTION_LI = re.compile(r'^-\s.*$')
|
|
self.DESCRIPTION_SP = re.compile(r'^\s\s.*$')
|
|
self.DESCRIPTION_BL = re.compile(r'^\s*$')
|
|
self.EMPTY_LINE = re.compile(r'^\s*$')
|
|
self.START_DOCUBLOCK = re.compile('.*@startDocuBlock ')
|
|
self.HINTS = re.compile('.*@HINTS')
|
|
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.RESTBODYPARAM = re.compile('.*@RESTBODYPARAM')
|
|
self.RESTSTRUCT = re.compile('.*@RESTSTRUCT')
|
|
self.RESTALLBODYPARAM = re.compile('.*@RESTALLBODYPARAM')
|
|
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.RESTREPLYBODY = re.compile('.*@RESTREPLYBODY')
|
|
self.RESTRETURNCODE = re.compile('.*@RESTRETURNCODE{')
|
|
self.RESTRETURNCODES = re.compile('.*@RESTRETURNCODES')
|
|
self.RESTURLPARAM = re.compile('.*@RESTURLPARAM{')
|
|
self.RESTURLPARAMETERS = re.compile('.*@RESTURLPARAMETERS')
|
|
self.RESTPARAM = re.compile('.*@RESTPARAM')
|
|
self.RESTQUERYPARAMS = re.compile('.*@RESTQUERYPARAMS')
|
|
self.TRIPLENEWLINEATSTART = re.compile(r'^\n\n\n')
|
|
|
|
################################################################################
|
|
### @brief checks for end of comment
|
|
################################################################################
|
|
|
|
def check_end_of_comment(line, r):
|
|
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.START_DOCUBLOCK.match(line):
|
|
return start_docublock, (fp, line)
|
|
elif r.HINTS.match(line):
|
|
return hints, (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.RESTSTRUCT.match(line):
|
|
return reststruct, (fp, line)
|
|
elif r.RESTALLBODYPARAM.match(line):
|
|
return restallbodyparam, (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.RESTREPLYBODY.match(line):
|
|
return restreplybody, (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)
|
|
elif r.RESTPARAM.match(line):
|
|
return restparam, (fp, line)
|
|
elif r.RESTQUERYPARAMS.match(line):
|
|
return restqueryparams, (fp, line)
|
|
elif r.EXAMPLES.match(line):
|
|
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
|
|
last
|
|
while 1:
|
|
(next, c) = next_step(fp, fp.readline(), r)
|
|
|
|
if next:
|
|
return next, c
|
|
|
|
################################################################################
|
|
### @brief generic handler with description
|
|
### @param cargo the file we're working on
|
|
### @param r the regex that matched
|
|
### @param message
|
|
### @param op
|
|
### @param para an object were we should write to
|
|
### @param name the key in para we should write to
|
|
################################################################################
|
|
|
|
def generic_handler_desc(cargo, r, message, op, para, name):
|
|
global DEBUG, operation
|
|
|
|
if DEBUG:
|
|
print >> sys.stderr, message
|
|
(fp, dummy) = cargo
|
|
|
|
while 1:
|
|
line = fp.readline()
|
|
(next, c) = next_step(fp, line, r)
|
|
|
|
if next:
|
|
para[name] = trim_text(para[name])
|
|
|
|
if op:
|
|
try:
|
|
operation[op].append(para)
|
|
except AttributeError as x:
|
|
print >> sys.stderr, "trying to set '%s' on operations - failed. '%s'" % (op, para)
|
|
raise x
|
|
return next, c
|
|
|
|
line = Typography(line)
|
|
para[name] += line + '\n'
|
|
|
|
def start_docublock(cargo, r=Regexen()):
|
|
global currentDocuBlock
|
|
(dummy, last) = cargo
|
|
try:
|
|
# TODO remove when all /// are removed from the docublocks
|
|
if last.startswith('/// '):
|
|
currentDocuBlock = last.split(' ')[2].rstrip()
|
|
else:
|
|
currentDocuBlock = last.split(' ')[1].rstrip()
|
|
except Exception as x:
|
|
print >> sys.stderr, "failed to fetch docublock in '" + last + "': " + str(x)
|
|
raise x
|
|
|
|
return generic_handler(cargo, r, 'start_docublock')
|
|
|
|
|
|
def setRequired(where, which):
|
|
if not 'required' in where:
|
|
where['required'] = []
|
|
where['required'].append(which)
|
|
|
|
################################################################################
|
|
### @brief restparam - deprecated - abort.
|
|
################################################################################
|
|
def restparam(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restBodyParam, fn, currentExample, currentReturnCode, currentDocuBlock, lastDocuBlock, restReplyBodyParam
|
|
print >> sys.stderr, "deprecated RESTPARAM declaration detected:"
|
|
print >> sys.stderr, json.dumps(
|
|
swagger['paths'][httpPath],
|
|
indent=4,
|
|
separators=(', ', ': '),
|
|
sort_keys=True)
|
|
raise Exception("RESTPARAM not supported anymore.")
|
|
|
|
################################################################################
|
|
### @brief restparam - deprecated - abort.
|
|
################################################################################
|
|
def restqueryparams(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restBodyParam, fn, currentExample, currentReturnCode, currentDocuBlock, lastDocuBlock, restReplyBodyParam
|
|
print >> sys.stderr, "deprecated RESTQUERYPARAMS declaration detected:"
|
|
print >> sys.stderr, json.dumps(
|
|
swagger['paths'][httpPath],
|
|
indent=4,
|
|
separators=(', ', ': '),
|
|
sort_keys=True)
|
|
raise Exception("RESTQUERYPARAMS not supported anymore. Use RESTQUERYPARAMETERS instead.")
|
|
|
|
################################################################################
|
|
### @brief restheader
|
|
################################################################################
|
|
|
|
def restheader(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restBodyParam, fn, currentExample, currentReturnCode, currentDocuBlock, lastDocuBlock, restReplyBodyParam
|
|
|
|
currentReturnCode = 0
|
|
currentExample = 0
|
|
restReplyBodyParam = None
|
|
restBodyParam = None
|
|
|
|
(dummy, last) = cargo
|
|
|
|
temp = parameters(last).split(',')
|
|
if temp == "":
|
|
raise Exception("Invalid restheader value. got empty string. Maybe missing closing bracket? " + last)
|
|
|
|
(ucmethod, path) = temp[0].split()
|
|
|
|
#TODO: hier checken, ob der letzte alles hatte (responses)
|
|
summary = temp[1]
|
|
summaryList = summary.split()
|
|
method = ucmethod.lower()
|
|
nickname = summaryList[0] + ''.join([word.capitalize() for word in summaryList[1:]])
|
|
|
|
httpPath = FA(path, wordboundary=['{', '}'])
|
|
if not httpPath in swagger['paths']:
|
|
swagger['paths'][httpPath] = {}
|
|
if method in swagger['paths'][httpPath]:
|
|
print >> sys.stderr, "duplicate route detected:"
|
|
print >> sys.stderr, "There already is a route [" + ucmethod + " " + httpPath + "]: "
|
|
print >> sys.stderr, json.dumps(
|
|
swagger['paths'][httpPath],
|
|
indent=4,
|
|
separators=(', ', ': '),
|
|
sort_keys=True)
|
|
raise Exception("Duplicate route")
|
|
|
|
if currentDocuBlock == None:
|
|
raise Exception("No docublock started for this restheader: " + ucmethod + " " + path)
|
|
|
|
if lastDocuBlock != None and currentDocuBlock == lastDocuBlock:
|
|
raise Exception("No new docublock started for this restheader: " + ucmethod + " " + path + ' : ' + currentDocuBlock)
|
|
|
|
operationId = nickname
|
|
if len(temp) > 2:
|
|
operationId = temp[2]
|
|
if operationId in operationIDs:
|
|
print operationIDs
|
|
raise Exception("duplicate operation ID! " + operationId)
|
|
lastDocuBlock = currentDocuBlock
|
|
|
|
|
|
swagger['paths'][httpPath][method] = {
|
|
'operationId': operationId.strip(),
|
|
'x-filename': fn,
|
|
'x-hints': '',
|
|
'x-examples': [],
|
|
'tags': [currentTag],
|
|
'summary': summary.strip(),
|
|
'description': '',
|
|
'parameters' : [],
|
|
}
|
|
operation = swagger['paths'][httpPath][method]
|
|
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()):
|
|
global swagger, operation, httpPath, method
|
|
(dummy, last) = cargo
|
|
name = ""
|
|
pformat = ""
|
|
required = ""
|
|
|
|
try:
|
|
(name, pformat, required) = parameters(last).split(',')
|
|
except Exception:
|
|
print >> sys.stderr, "RESTURLPARAM: 3 arguments required. You gave me: " + parameters(last)
|
|
raise x
|
|
|
|
if required.strip() != 'required':
|
|
print >> sys.stderr, "only required is supported in RESTURLPARAM"
|
|
raise Exception("invalid url parameter")
|
|
|
|
para = {
|
|
'name': name.strip(),
|
|
'in': 'path',
|
|
'format': pformat.strip(),
|
|
'description': '',
|
|
'type': pformat.strip().lower(),
|
|
'required': True
|
|
}
|
|
swagger['paths'][httpPath][method]['parameters'].append(para)
|
|
|
|
return generic_handler_desc(cargo, r, "resturlparam", None, 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()):
|
|
global swagger, operation, httpPath, method
|
|
(dummy, last) = cargo
|
|
|
|
parametersList = parameters(last).split(',')
|
|
|
|
para = {
|
|
'in': 'header',
|
|
'type': parametersList[1].lower(),
|
|
'name': parametersList[0],
|
|
'description': ''
|
|
}
|
|
swagger['paths'][httpPath][method]['parameters'].append(para)
|
|
|
|
return generic_handler_desc(cargo, r, "restheaderparam", None, para, 'description')
|
|
|
|
################################################################################
|
|
### @brief restbodyparam
|
|
################################################################################
|
|
|
|
def restbodyparam(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restBodyParam, fn, currentDocuBlock
|
|
(dummy, last) = cargo
|
|
|
|
try:
|
|
(name, ptype, required, ptype2) = parameters(last).split(',')
|
|
except Exception:
|
|
print >> sys.stderr, "RESTBODYPARAM: 4 arguments required. You gave me: " + parameters(last)
|
|
print >> sys.stderr, "In this docublock: " + currentDocuBlock
|
|
raise Exception("Argument count error")
|
|
|
|
CheckReqOpt(required)
|
|
if required == 'required':
|
|
required = True
|
|
else:
|
|
required = False
|
|
|
|
if restBodyParam == None:
|
|
# https://github.com/swagger-api/swagger-ui/issues/1430
|
|
# once this is solved we can skip this:
|
|
operation['description'] += "\n**A JSON object with these properties is required:**\n"
|
|
restBodyParam = {
|
|
'name': 'Json Request Body',
|
|
'x-description-offset': len(swagger['paths'][httpPath][method]['description']),
|
|
'in': 'body',
|
|
'required': True,
|
|
'schema': {
|
|
'$ref': '#/definitions/' + currentDocuBlock
|
|
}
|
|
}
|
|
swagger['paths'][httpPath][method]['parameters'].append(restBodyParam)
|
|
|
|
if not currentDocuBlock in swagger['definitions']:
|
|
swagger['definitions'][currentDocuBlock] = {
|
|
'x-filename': fn,
|
|
'type' : 'object',
|
|
'properties': {},
|
|
}
|
|
|
|
swagger['definitions'][currentDocuBlock]['properties'][name] = {
|
|
'type': ptype,
|
|
'description': ''
|
|
}
|
|
|
|
if ptype == 'object' and len(ptype2) > 0:
|
|
swagger['definitions'][currentDocuBlock]['properties'][name] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
|
|
if not ptype2 in swagger['definitions']:
|
|
swagger['definitions'][ptype2] = {
|
|
'x-filename': fn,
|
|
'type': 'object',
|
|
'properties' : {},
|
|
'description': ''
|
|
}
|
|
|
|
if required:
|
|
setRequired(swagger['definitions'][ptype2], name)
|
|
|
|
return generic_handler_desc(cargo, r, "restbodyparam", None,
|
|
swagger['definitions'][ptype2],
|
|
'description')
|
|
|
|
if ptype == 'array':
|
|
if ptype2 not in swaggerBaseTypes:
|
|
swagger['definitions'][currentDocuBlock]['properties'][name]['items'] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
else:
|
|
swagger['definitions'][currentDocuBlock]['properties'][name]['items'] = {
|
|
'type': ptype2
|
|
}
|
|
if ptype2 == 'object':
|
|
swagger['definitions'][currentDocuBlock]['properties'][name]['items']['additionalProperties'] = {}
|
|
elif ptype == 'object':
|
|
swagger['definitions'][currentDocuBlock]['properties'][name]['additionalProperties'] = {}
|
|
elif ptype != 'string':
|
|
if ptype in swaggerFormats and ptype2 not in swaggerFormats[ptype]:
|
|
print >> sys.stderr, "RESTSTRUCT: ptype2 (format)[" + ptype2 + "] not valid: " + parameters(last)
|
|
raise Exception("'%s' is not one of %s!" % (ptype2, str(swaggerFormats)))
|
|
swagger['definitions'][currentDocuBlock]['properties'][name]['format'] = ptype2
|
|
|
|
|
|
if required:
|
|
setRequired(swagger['definitions'][currentDocuBlock], name)
|
|
|
|
return generic_handler_desc(cargo, r, "restbodyparam", None,
|
|
swagger['definitions'][currentDocuBlock]['properties'][name],
|
|
'description')
|
|
|
|
################################################################################
|
|
### @brief restallbodyparam
|
|
################################################################################
|
|
|
|
def restallbodyparam(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restBodyParam
|
|
(dummy, last) = cargo
|
|
|
|
try:
|
|
(_name, _ptype, required) = parameters(last).split(',')
|
|
except Exception:
|
|
print >> sys.stderr, "RESTALLBODYPARAM: 3 arguments required. You gave me: " + parameters(last)
|
|
|
|
CheckReqOpt(required)
|
|
if required == 'required':
|
|
required = True
|
|
else:
|
|
required = False
|
|
if restBodyParam != None:
|
|
raise Exception("May only have one 'ALLBODY'")
|
|
|
|
restBodyParam = {
|
|
'name': 'Json Request Body',
|
|
'description': '',
|
|
'in': 'body',
|
|
'x-description-offset': len(swagger['paths'][httpPath][method]['description']),
|
|
'required': required,
|
|
'schema': {
|
|
'type': 'object',
|
|
'additionalProperties': {}
|
|
}
|
|
}
|
|
swagger['paths'][httpPath][method]['parameters'].append(restBodyParam)
|
|
|
|
return generic_handler_desc(cargo, r, "restbodyparam", None,
|
|
restBodyParam,
|
|
'description')
|
|
|
|
################################################################################
|
|
### @brief reststruct
|
|
################################################################################
|
|
|
|
def reststruct(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restBodyParam, restSubBodyParam, fn
|
|
(dummy, last) = cargo
|
|
|
|
try:
|
|
(name, className, ptype, required, ptype2) = parameters(last).split(',')
|
|
except Exception:
|
|
print >> sys.stderr, "RESTSTRUCT: 5 arguments required (name, className, ptype, required, ptype2). You gave me: " + parameters(last)
|
|
raise Exception("Argument count error")
|
|
|
|
CheckReqOpt(required)
|
|
if required == 'required':
|
|
required = True
|
|
else:
|
|
required = False
|
|
|
|
if className not in swagger['definitions']:
|
|
swagger['definitions'][className] = {
|
|
'type': 'object',
|
|
'properties' : {},
|
|
'description': '',
|
|
'x-filename': fn
|
|
}
|
|
|
|
swagger['definitions'][className]['properties'][name] = {
|
|
'type': ptype,
|
|
'description': ''
|
|
}
|
|
|
|
if ptype == 'array':
|
|
if ptype2 not in swaggerBaseTypes:
|
|
swagger['definitions'][className]['properties'][name]['items'] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
else:
|
|
swagger['definitions'][className]['properties'][name]['items'] = {
|
|
'type': ptype2
|
|
}
|
|
if ptype == 'object' and len(ptype2) > 0:
|
|
if not ptype2 in swagger['definitions']:
|
|
swagger['definitions'][ptype2] = {
|
|
'x-filename': fn,
|
|
'type': 'object',
|
|
'properties' : {},
|
|
'description': ''
|
|
}
|
|
swagger['definitions'][className]['properties'][name] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
|
|
if required:
|
|
setRequired(swagger['definitions'][className], name)
|
|
|
|
return generic_handler_desc(cargo, r, "reststruct", None,
|
|
swagger['definitions'][ptype2],
|
|
'description')
|
|
|
|
elif ptype != 'string' and ptype != 'boolean':
|
|
if ptype in swaggerFormats and ptype2 not in swaggerFormats[ptype]:
|
|
print >> sys.stderr, "RESTSTRUCT: ptype2 (format)[" + ptype2 + "] not valid: " + parameters(last)
|
|
raise Exception("'%s' is not one of %s!" % (ptype2, str(swaggerFormats)))
|
|
swagger['definitions'][className]['properties'][name]['format'] = ptype2
|
|
|
|
return generic_handler_desc(cargo, r, "restbodyparam", None,
|
|
swagger['definitions'][className]['properties'][name],
|
|
'description')
|
|
|
|
################################################################################
|
|
### @brief restqueryparam
|
|
################################################################################
|
|
|
|
def restqueryparam(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, swaggerBaseTypes
|
|
(dummy, last) = cargo
|
|
|
|
parametersList = parameters(last).split(',')
|
|
|
|
CheckReqOpt(parametersList[2])
|
|
if parametersList[2] == 'required':
|
|
required = True
|
|
else:
|
|
required = False
|
|
swaggerType = parametersList[1].lower()
|
|
|
|
if swaggerType not in swaggerBaseTypes:
|
|
print >> sys.stderr, "RESTQUERYPARAM is supposed to be a swagger type."
|
|
raise Exception("'%s' is not one of %s!" % (swaggerType, str(swaggerBaseTypes)))
|
|
|
|
para = {
|
|
'name': parametersList[0],
|
|
'in': 'query',
|
|
'description': '',
|
|
'type': swaggerType,
|
|
'required': required
|
|
}
|
|
|
|
swagger['paths'][httpPath][method]['parameters'].append(para)
|
|
return generic_handler_desc(cargo, r, "restqueryparam", None, para, 'description')
|
|
|
|
################################################################################
|
|
### @brief hints
|
|
################################################################################
|
|
|
|
def hints(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method
|
|
|
|
ret = generic_handler_desc(cargo, r, "hints", None,
|
|
swagger['paths'][httpPath][method], 'x-hints')
|
|
|
|
if r.TRIPLENEWLINEATSTART.match(swagger['paths'][httpPath][method]['x-hints']):
|
|
(fp, dummy) = cargo
|
|
print >> sys.stderr, 'remove newline after @HINTS in file %s' % (fp.name)
|
|
exit(1)
|
|
|
|
return ret
|
|
|
|
################################################################################
|
|
### @brief restdescription
|
|
################################################################################
|
|
|
|
def restdescription(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method
|
|
swagger['paths'][httpPath][method]['description'] += '\n\n'
|
|
|
|
ret = generic_handler_desc(cargo, r, "restdescription", None,
|
|
swagger['paths'][httpPath][method],
|
|
'description')
|
|
|
|
if r.TRIPLENEWLINEATSTART.match(swagger['paths'][httpPath][method]['description']):
|
|
(fp, dummy) = cargo
|
|
print >> sys.stderr, 'remove newline after @RESTDESCRIPTION in file %s' % (fp.name)
|
|
exit(1)
|
|
|
|
return ret
|
|
|
|
################################################################################
|
|
### @brief restreplybody
|
|
################################################################################
|
|
|
|
def restreplybody(cargo, r=Regexen()):
|
|
global swagger, operation, httpPath, method, restReplyBodyParam, fn
|
|
(dummy, last) = cargo
|
|
|
|
try:
|
|
(name, ptype, required, ptype2) = parameters(last).split(',')
|
|
except Exception:
|
|
print >> sys.stderr, "RESTREPLYBODY: 4 arguments required. You gave me: " + parameters(last)
|
|
raise x
|
|
|
|
CheckReqOpt(required)
|
|
if required == 'required':
|
|
required = True
|
|
else:
|
|
required = False
|
|
|
|
if currentReturnCode == 0:
|
|
raise Exception("failed to add text to response body: (have to specify the HTTP-code first) " + parameters(last))
|
|
|
|
rcBlock = ''
|
|
if name == '':
|
|
if ptype == 'object':
|
|
rcBlock = ptype2
|
|
elif ptype == 'array':
|
|
rcBlock = currentDocuBlock + '_rc_' + currentReturnCode
|
|
else:
|
|
rcBlock = currentDocuBlock + '_rc_' + currentReturnCode
|
|
#if currentReturnCode:
|
|
if restReplyBodyParam == None:
|
|
# https://github.com/swagger-api/swagger-ui/issues/1430
|
|
# once this is solved we can skip this:
|
|
operation['description'] += '\n**HTTP ' + currentReturnCode + '**\n'
|
|
operation['description'] += "*A json document with these Properties is returned:*\n"
|
|
operation['responses'][currentReturnCode]['x-description-offset'] = len(operation['description'])
|
|
|
|
operation['responses'][currentReturnCode]['schema'] = {
|
|
'$ref': '#/definitions/' + rcBlock
|
|
}
|
|
swagger['paths'][httpPath][method]['produces'] = [
|
|
"application/json"
|
|
]
|
|
restReplyBodyParam = ''
|
|
|
|
if not rcBlock in swagger['definitions']:
|
|
swagger['definitions'][rcBlock] = {
|
|
'x-filename': fn,
|
|
'type' : 'object',
|
|
'properties': {},
|
|
}
|
|
|
|
if len(name) > 0:
|
|
swagger['definitions'][rcBlock]['properties'][name] = {
|
|
'type': ptype,
|
|
'description': ''
|
|
}
|
|
|
|
if ptype == 'object' and len(ptype2) > 0:
|
|
if len(name) > 0:
|
|
swagger['definitions'][rcBlock]['properties'][name] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
|
|
if not ptype2 in swagger['definitions']:
|
|
swagger['definitions'][ptype2] = {
|
|
'x-filename': fn,
|
|
'type': 'object',
|
|
'properties' : {},
|
|
'description': ''
|
|
}
|
|
|
|
if required:
|
|
setRequired(swagger['definitions'][ptype2], name)
|
|
|
|
return generic_handler_desc(cargo, r, "restbodyparam", None,
|
|
swagger['definitions'][ptype2],
|
|
'description')
|
|
|
|
if ptype == 'array':
|
|
if len(name) == 0:
|
|
swagger['definitions'][rcBlock] = {
|
|
'type': ptype,
|
|
'description': ''
|
|
}
|
|
swagger['definitions'][rcBlock]['items'] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
return generic_handler_desc(cargo, r, "restreplybody", None,
|
|
swagger['definitions'][rcBlock],
|
|
'description')
|
|
else:
|
|
if len(ptype2) == 0:
|
|
swagger['definitions'][rcBlock]['properties'][name]['items'] = {
|
|
}
|
|
elif ptype2 not in swaggerBaseTypes:
|
|
swagger['definitions'][rcBlock]['properties'][name]['items'] = {
|
|
'$ref': '#/definitions/' + ptype2
|
|
}
|
|
else:
|
|
swagger['definitions'][rcBlock]['properties'][name]['items'] = {
|
|
'type': ptype2
|
|
}
|
|
if ptype2 == 'object':
|
|
swagger['definitions'][rcBlock]['properties']\
|
|
[name]['items']['additionalProperties'] = {}
|
|
elif ptype == 'object':
|
|
if len(name) > 0:
|
|
swagger['definitions'][rcBlock]['properties'][name]['additionalProperties'] = {}
|
|
elif ptype != 'string':
|
|
swagger['definitions'][rcBlock]['properties'][name]['format'] = ptype2
|
|
|
|
|
|
if len(name) > 0 & required:
|
|
setRequired(swagger['definitions'][rcBlock], name)
|
|
|
|
if len(name) > 0:
|
|
if 'description' not in swagger['definitions'][rcBlock]['properties']:
|
|
swagger['definitions'][rcBlock]['properties'][name]['description'] = ''
|
|
|
|
return generic_handler_desc(
|
|
cargo, r, "restreplybody", None,
|
|
swagger['definitions'][rcBlock]['properties'][name],
|
|
'description')
|
|
else:
|
|
swagger['definitions'][rcBlock]['description'] = ''
|
|
return generic_handler_desc(cargo, r, "restreplybody", None,
|
|
swagger['definitions'][rcBlock],
|
|
'description')
|
|
|
|
################################################################################
|
|
### @brief restreturncodes
|
|
################################################################################
|
|
|
|
def restreturncodes(cargo, r=Regexen()):
|
|
return generic_handler(cargo, r, "restreturncodes")
|
|
|
|
################################################################################
|
|
### @brief restreturncode
|
|
################################################################################
|
|
|
|
def restreturncode(cargo, r=Regexen()):
|
|
global currentReturnCode, restReplyBodyParam
|
|
(dummy, last) = cargo
|
|
restReplyBodyParam = None
|
|
currentReturnCode = parameters(last)
|
|
|
|
if not 'responses' in swagger['paths'][httpPath][method]:
|
|
swagger['paths'][httpPath][method]['responses'] = {}
|
|
swagger['paths'][httpPath][method]['responses'][currentReturnCode] = {
|
|
#'code': parameters(last),
|
|
'description': ''
|
|
}
|
|
return generic_handler_desc(
|
|
cargo, r, "restreturncode", None,
|
|
swagger['paths'][httpPath][method]['responses'][parameters(last)],
|
|
'description')
|
|
|
|
################################################################################
|
|
### @brief examples
|
|
################################################################################
|
|
|
|
def examples(cargo, r=Regexen()):
|
|
global currentExample
|
|
operation['x-examples'].append('')
|
|
return generic_handler_desc(cargo, r, "x-examples", None, operation['x-examples'], currentExample)
|
|
|
|
################################################################################
|
|
### @brief example_arangosh_run
|
|
################################################################################
|
|
|
|
|
|
def example_arangosh_run(cargo, r=Regexen()):
|
|
global currentExample, DEBUG
|
|
|
|
if DEBUG:
|
|
print >> sys.stderr, "example_arangosh_run"
|
|
(fp, last) = cargo
|
|
|
|
exampleHeader = brTrim(operation['x-examples'][currentExample]).strip()
|
|
|
|
# new examples code TODO should include for each example own object in json file
|
|
fn = os.path.join(os.path.dirname(__file__), '../Documentation/Examples/' + parameters(last) + '.generated')
|
|
try:
|
|
examplefile = open(fn)
|
|
except:
|
|
print >> sys.stderr, "Failed to open example file:\n '%s'" % fn
|
|
raise Exception("failed to open example file:" + fn)
|
|
operation['x-examples'][currentExample] = '\n\n**Example:**\n ' + exampleHeader.strip('\n ') + '\n\n<pre>'
|
|
|
|
for line in examplefile.readlines():
|
|
operation['x-examples'][currentExample] += '<code>' + line + '</code>'
|
|
|
|
operation['x-examples'][currentExample] += '</pre>\n\n\n'
|
|
|
|
line = ""
|
|
|
|
while not r.END_EXAMPLE_ARANGOSH_RUN.match(line):
|
|
line = fp.readline()
|
|
|
|
if not line:
|
|
return eof, (fp, line)
|
|
|
|
currentExample += 1
|
|
|
|
return examples, (fp, line)
|
|
|
|
################################################################################
|
|
### @brief eof
|
|
################################################################################
|
|
|
|
def eof(cargo):
|
|
global DEBUG
|
|
if DEBUG:
|
|
print >> sys.stderr, "eof"
|
|
|
|
################################################################################
|
|
### @brief error
|
|
################################################################################
|
|
|
|
def error(cargo):
|
|
global DEBUG
|
|
if DEBUG:
|
|
print >> sys.stderr, "error"
|
|
|
|
sys.stderr.write('Unidentifiable line:\n' + cargo)
|
|
|
|
################################################################################
|
|
### @brief comment
|
|
################################################################################
|
|
|
|
def comment(cargo, r=Regexen()):
|
|
global DEBUG
|
|
|
|
if DEBUG:
|
|
print >> sys.stderr, "comment"
|
|
(fp, dummy) = cargo
|
|
|
|
while 1:
|
|
line = fp.readline()
|
|
if not line:
|
|
return eof, (fp, line)
|
|
|
|
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
|
|
|
|
if DEBUG:
|
|
print >> sys.stderr, "skip_code"
|
|
(fp, last) = cargo
|
|
|
|
return comment((fp, last), r)
|
|
|
|
################################################################################
|
|
### @brief main
|
|
################################################################################
|
|
|
|
automat = StateMachine()
|
|
|
|
automat.add_state(comment)
|
|
automat.add_state(eof, end_state=1)
|
|
automat.add_state(error, end_state=1)
|
|
automat.add_state(start_docublock)
|
|
automat.add_state(hints)
|
|
automat.add_state(example_arangosh_run)
|
|
automat.add_state(examples)
|
|
automat.add_state(skip_code)
|
|
automat.add_state(restbodyparam)
|
|
automat.add_state(reststruct)
|
|
automat.add_state(restallbodyparam)
|
|
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(restreplybody)
|
|
automat.add_state(resturlparam)
|
|
automat.add_state(resturlparameters)
|
|
automat.add_state(restparam)
|
|
automat.add_state(restqueryparam)
|
|
|
|
|
|
def getOneApi(infile, filename, thisFn):
|
|
automat.set_start(skip_code)
|
|
automat.set_fn(thisFn)
|
|
automat.run((infile, ''))
|
|
|
|
################################################################################
|
|
### Swagger Markdown rendering
|
|
################################################################################
|
|
|
|
def getReference(name, source, verb):
|
|
try:
|
|
ref = name['$ref'][defLen:]
|
|
except Exception:
|
|
print >>sys.stderr, "No reference in: "
|
|
print >>sys.stderr, name
|
|
raise Exception("No reference in: " + name)
|
|
if not ref in swagger['definitions']:
|
|
fn = ''
|
|
if verb:
|
|
fn = swagger['paths'][route][verb]['x-filename']
|
|
else:
|
|
fn = swagger['definitions'][source]['x-filename']
|
|
print >> sys.stderr, json.dumps(
|
|
swagger['definitions'],
|
|
indent=4,
|
|
separators=(', ', ': '),
|
|
sort_keys=True)
|
|
raise Exception("invalid reference: " + ref + " in " + fn)
|
|
return ref
|
|
|
|
removeDoubleLF = re.compile("\n\n")
|
|
removeLF = re.compile("\n")
|
|
|
|
def TrimThisParam(text, indent):
|
|
text = text.rstrip('\n').lstrip('\n')
|
|
text = removeDoubleLF.sub("\n", text)
|
|
if indent > 0:
|
|
indent = (indent + 2) # align the text right of the list...
|
|
return removeLF.sub("\n" + ' ' * indent, text)
|
|
|
|
def unwrapPostJson(reference, layer):
|
|
swaggerDataTypes = ["number", "integer", "string", "boolean", "array", "object"]
|
|
####
|
|
# print >>sys.stderr, "xx" * layer + reference
|
|
global swagger
|
|
rc = ''
|
|
if not 'properties' in swagger['definitions'][reference]:
|
|
if 'items' in swagger['definitions'][reference]:
|
|
if swagger['definitions'][reference]['type'] == 'array':
|
|
rc += '[\n'
|
|
subStructRef = getReference(swagger['definitions'][reference]['items'], reference, None)
|
|
rc += unwrapPostJson(subStructRef, layer + 1)
|
|
if swagger['definitions'][reference]['type'] == 'array':
|
|
rc += ']\n'
|
|
else:
|
|
for param in swagger['definitions'][reference]['properties'].keys():
|
|
thisParam = swagger['definitions'][reference]['properties'][param]
|
|
#required = ('required' in swagger['definitions'][reference] and
|
|
# param in swagger['definitions'][reference]['required'])
|
|
|
|
# print >> sys.stderr, thisParam
|
|
if '$ref' in thisParam:
|
|
subStructRef = getReference(thisParam, reference, None)
|
|
|
|
rc += ' ' * layer + "- **" + param + "**:\n"
|
|
####
|
|
# print >>sys.stderr, "yy" * layer + param
|
|
rc += unwrapPostJson(subStructRef, layer + 1)
|
|
|
|
elif thisParam['type'] == 'object':
|
|
rc += ' ' * layer + "- **" + param + "**: " + TrimThisParam(brTrim(thisParam['description']), layer) + "\n"
|
|
elif thisParam['type'] == 'array':
|
|
rc += ' ' * layer + "- **" + param + "**"
|
|
trySubStruct = False
|
|
lf = ""
|
|
####
|
|
# print >>sys.stderr, "zz" * layer + param
|
|
if 'type' in thisParam['items']:
|
|
rc += " (" + thisParam['items']['type'] + ")"
|
|
lf = "\n"
|
|
else:
|
|
if len(thisParam['items']) == 0:
|
|
rc += " (anonymous json object)"
|
|
lf = "\n"
|
|
else:
|
|
trySubStruct = True
|
|
rc += ": " + TrimThisParam(brTrim(thisParam['description']), layer) + lf
|
|
if trySubStruct:
|
|
try:
|
|
subStructRef = getReference(thisParam['items'], reference, None)
|
|
except:
|
|
print >>sys.stderr, "while analyzing: " + param
|
|
print >>sys.stderr, thisParam
|
|
rc += "\n" + unwrapPostJson(subStructRef, layer + 1)
|
|
else:
|
|
if thisParam['type'] not in swaggerDataTypes:
|
|
print >>sys.stderr, "while analyzing: " + param
|
|
print >>sys.stderr, thisParam['type'] + " is not a valid swagger datatype; supported ones: " + str(swaggerDataTypes)
|
|
raise Exception("invalid swagger type")
|
|
rc += ' ' * layer + "- **" + param + "**: " + TrimThisParam(thisParam['description'], layer) + '\n'
|
|
return rc
|
|
|
|
|
|
|
|
|
|
if len(sys.argv) < 4:
|
|
print >> sys.stderr, "usage: " + sys.argv[0] + " <scriptDir> <outDir> <relDir> <docublockdir> <optional: filter>"
|
|
sys.exit(1)
|
|
|
|
scriptDir = sys.argv[1]
|
|
if not scriptDir.endswith("/"):
|
|
scriptDir += "/"
|
|
|
|
outDir = sys.argv[2]
|
|
if not outDir.endswith("/"):
|
|
outDir += "/"
|
|
|
|
relDir = sys.argv[3]
|
|
if not relDir.endswith("/"):
|
|
relDir += "/"
|
|
|
|
fileFilter = ""
|
|
if len(sys.argv) > 5:
|
|
fileFilter = sys.argv[5]
|
|
print >> sys.stderr, "Filtering for: [" + fileFilter + "]"
|
|
# read ArangoDB version
|
|
f = open(scriptDir + "VERSION", "r")
|
|
for version in f:
|
|
version = version.strip('\n')
|
|
f.close()
|
|
|
|
|
|
paths = {}
|
|
|
|
topdir = sys.argv[4]
|
|
files = {}
|
|
|
|
for chapter in os.listdir(topdir):
|
|
if not os.path.isdir(os.path.join(topdir, chapter)) or chapter[0] == ".":
|
|
continue
|
|
files[chapter] = []
|
|
curPath = os.path.join(topdir, chapter)
|
|
for oneFile in os.listdir(curPath):
|
|
if fileFilter != "" and oneFile != fileFilter:
|
|
print >> sys.stderr, "Skipping: [" + oneFile + "]"
|
|
continue
|
|
curPath2 = os.path.join(curPath, oneFile)
|
|
if os.path.isfile(curPath2) and oneFile[0] != "." and oneFile.endswith(".md"):
|
|
files[chapter].append(os.path.join(topdir, chapter, oneFile))
|
|
|
|
for name, filenames in sorted(files.items(), key=operator.itemgetter(0)):
|
|
currentTag = name
|
|
for fn in filenames:
|
|
thisfn = fn
|
|
infile = open(fn)
|
|
try:
|
|
getOneApi(infile, name + " - " + ', '.join(filenames), fn)
|
|
except Exception as x:
|
|
print >> sys.stderr, "\nwhile parsing file: '%s' error: %s" % (thisfn, x)
|
|
raise Exception("while parsing file '%s' error: %s" %(thisfn, x))
|
|
infile.close()
|
|
currentDocuBlock = None
|
|
lastDocuBlock = None
|
|
|
|
# Sort arrays by offset helper:
|
|
def descOffsetGet(value):
|
|
return value["descOffset"]
|
|
|
|
for route in swagger['paths'].keys():
|
|
for verb in swagger['paths'][route].keys():
|
|
offsetPlus = 0
|
|
thisVerb = swagger['paths'][route][verb]
|
|
if not thisVerb['description']:
|
|
print >> sys.stderr, "Description of Route empty; @RESTDESCRIPTION missing?"
|
|
print >> sys.stderr, "in :" + verb + " " + route
|
|
#raise TODO
|
|
# insert the post json description into the place we extracted it:
|
|
# Collect the blocks we want to work on, sort them by replacement place:
|
|
sortVec = []
|
|
for nParam in range(0, len(thisVerb['parameters'])):
|
|
if thisVerb['parameters'][nParam]['in'] == 'body':
|
|
sortVec.append({
|
|
"nParam": nParam,
|
|
"descOffset": thisVerb['parameters'][nParam]['x-description-offset']
|
|
})
|
|
|
|
sortVec.sort(key=descOffsetGet)
|
|
for oneItem in sortVec:
|
|
nParam = oneItem["nParam"]
|
|
descOffset = thisVerb['parameters'][nParam]['x-description-offset']
|
|
addText = ''
|
|
postText = ''
|
|
paramDesc = thisVerb['description'][:(descOffset+offsetPlus)]
|
|
if paramDesc:
|
|
postText += paramDesc
|
|
if 'additionalProperties' not in thisVerb['parameters'][nParam]['schema']:
|
|
addText = "\n" + unwrapPostJson(
|
|
getReference(thisVerb['parameters'][nParam]['schema'],
|
|
route,
|
|
verb),
|
|
1) + "\n\n"
|
|
|
|
postText += addText
|
|
postText += thisVerb['description'][(offsetPlus+descOffset):]
|
|
offsetPlus += len(addText)
|
|
thisVerb['description'] = postText
|
|
|
|
# insert the reply json description into the place we extracted it:
|
|
if 'responses' in thisVerb:
|
|
|
|
# Collect the blocks we want to work on, sort them by replacement place:
|
|
sortVec = []
|
|
for nRC in thisVerb['responses']:
|
|
if 'x-description-offset' in thisVerb['responses'][nRC]:
|
|
sortVec.append({
|
|
"nParam": nRC,
|
|
"descOffset": thisVerb['responses'][nRC]['x-description-offset']
|
|
})
|
|
|
|
sortVec.sort(key=descOffsetGet)
|
|
for oneItem in sortVec:
|
|
nRC = oneItem["nParam"]
|
|
descOffset = thisVerb['responses'][nRC]['x-description-offset']
|
|
#print descOffset
|
|
#print offsetPlus
|
|
descOffset += offsetPlus
|
|
addText = ''
|
|
#print thisVerb['responses'][nRC]['description']
|
|
postText = thisVerb['description'][:descOffset]
|
|
#print postText
|
|
replyDescription = TrimThisParam(thisVerb['responses'][nRC]['description'], 0)
|
|
if replyDescription:
|
|
addText += '\n' + replyDescription + '\n'
|
|
if 'additionalProperties' not in thisVerb['responses'][nRC]['schema']:
|
|
addText += "\n" + unwrapPostJson(
|
|
getReference(thisVerb['responses'][nRC]['schema'],
|
|
route,
|
|
verb),
|
|
0) + '\n'
|
|
# print addText
|
|
postText += addText
|
|
postText += thisVerb['description'][descOffset:]
|
|
offsetPlus += len(addText)
|
|
thisVerb['description'] = postText
|
|
|
|
#print '-'*80
|
|
#print thisVerb['description']
|
|
|
|
# Simplify hint box code to something that works in Swagger UI
|
|
# Append the result to the description field
|
|
# Place invisible markers, so that hints can be removed again
|
|
if 'x-hints' in thisVerb and thisVerb['x-hints']:
|
|
thisVerb['description'] += '\n<!-- Hints Start -->'
|
|
tmp = re.sub("{% hint '([^']+?)' %}",
|
|
lambda match: "\n\n**{}:** ".format(match.group(1).title()),
|
|
thisVerb['x-hints'])
|
|
tmp = re.sub('{%[^%]*?%}', '', tmp)
|
|
thisVerb['description'] += tmp
|
|
thisVerb['description'] += '\n<!-- Hints End -->'
|
|
|
|
# Append the examples to the description:
|
|
if 'x-examples' in thisVerb and thisVerb['x-examples']:
|
|
thisVerb['description'] += '\n'
|
|
for nExample in range(0, len(thisVerb['x-examples'])):
|
|
thisVerb['description'] += thisVerb['x-examples'][nExample]
|
|
thisVerb['x-examples'] = []# todo unset!
|
|
|
|
#print highlight(yaml.dump(swagger, Dumper=yaml.RoundTripDumper), YamlLexer(), TerminalFormatter())
|
|
#print yaml.dump(swagger, Dumper=yaml.RoundTripDumper)
|
|
print json.dumps(swagger, indent=4, separators=(', ', ': '), sort_keys=True)
|
|
#print json.dumps(swagger['paths'], indent=4, separators=(', ',': '), sort_keys=True)
|
|
#print highlight(yaml.dump(swagger, Dumper=yaml.RoundTripDumper), YamlLexer(), TerminalFormatter())
|
|
|
|
## -----------------------------------------------------------------------------
|
|
## --SECTION-- END-OF-FILE
|
|
## -----------------------------------------------------------------------------
|
|
|
|
## Local Variables:
|
|
## mode: outline-minor
|
|
## outline-regexp: "^\\(### @brief\\|## --SECTION--\\|# -\\*- \\)"
|
|
## End:
|
|
|
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|