#!/usr/bin/python
import sys, argparse, json, re
def handleException(etype, e=None):
if etype == 'KeyError':
print "Error: Required property {} not found".format(e)
elif etype == 'IOError':
print "Error ({}): {}".format(e.errno, e.strerror)
elif etype == 'ValueError':
print "Error: Unable to parse input as JSON"
elif etype == 'Custom':
print e
sys.exit(1)
def get_json(filename):
try:
with open(filename) as json_file:
json_data = json.load(json_file)
return json_data
except IOError as e:
handleException('IOError', e)
except ValueError:
handleException('ValueError')
except:
print "Unexpected error: {}".format(sys.exc_info()[0])
raise
def write_file(filename, body):
try:
with open(filename, 'w') as md_file:
md_file.write(body)
except IOError as e:
handleException('IOError', e)
def make_header(json_data):
try:
if not 'swagger' in json_data:
raise KeyError
info = json_data['info']
md = "
{}
\n".format(info['title'])
md += "Version: {}
\n".format(info['version'])
if 'license' in info:
md += "License: "
license = info['license']
if 'url' in license:
md += '{}'.format(license['url'], license['name'])
else:
md += license['name']
md += '
\n'
if 'contact' in info:
contact = info['contact']
if 'name' in contact or 'email' in contact:
md += 'Contact: '
if not 'name' in contact:
md += '{0}'.format(contact['email'])
elif not 'email' in contact:
md += contact['name']
else:
md += '{0} <'.format(contact['name'], contact['email'])
md += ' \n'
if 'url' in contact:
md += "Website: {}
\n".format(contact['url'])
if 'termsOfService' in info:
md += 'Terms of Service: {}
\n'.format(info['termsOfService'])
if 'host' in json_data:
md += 'Base URL: '
base = json_data['host']
if 'basePath' in json_data:
base += json_data['basePath']
else:
base += '/'
if 'schemes' in json_data:
md += (', ').join(map(
lambda x: '{0}://{1}'.format(x, base),
json_data['schemes']
))
else:
md += '{0}'.format(base)
md += '
\n'
if 'description' in info:
md += 'Description: {}
\n'.format(info['description'])
md += '\n'
return md
except KeyErrori as e:
handleException('KeyError', e)
def make_ref(ref):
href = ref.split('/')[1:]
return '{}'.format('_'.join(href), href[-1])
def get_ref(ref, raw):
keys = ref.split('/')
return raw[keys[1]][keys[2]]
def make_html(s):
reg = re.compile(r"[*_]{3}(.+?)[*_]{3}")
s = reg.sub(r"\1", s)
reg = re.compile(r"[*_]{2}(.+?)[*_]{2}")
s = reg.sub(r"\1", s)
reg = re.compile(r"[*_](.+?)[*_]")
s = reg.sub(r"\1", s)
reg = re.compile(r"\`(.+?)\`")
s = reg.sub(r"\1
", s)
return s
def make_table(data):
md = '
\n'
md += ' \n'
md += ' \n'
for col in data[0]:
md += ' {} | \n'.format(col)
md += '
\n'
md += ' \n'
md += ' \n'
for row in data[1:]:
md += ' \n'
for cell in row:
md += ' {} | \n'.format(cell)
md += '
\n'
md += ' \n'
md += '
\n'
return md
def make_params_table(itemsraw, raw):
items = []
for item in itemsraw:
if '$ref' in item:
items.append(get_ref(item['$ref'], raw))
else:
items.append(item)
try:
fields = list(set([]).union(*map(lambda x: x.keys(), items)))
row = [ 'Name', 'ParamType' ]
if 'description' in fields:
row.append('Description')
if 'required' in fields:
row.append('Required')
if 'type' in fields:
row.append('DataType')
if 'schema' in fields:
row.append('Schema')
table = [ row ]
for item in items:
row = [ "{}".format(item['name']), item['in'] ]
if 'description' in fields:
if 'description' in item:
row.append(make_html(item['description']))
else:
row.append('')
if 'required' in fields:
required = 'False'
if 'required' in item and item['required']:
required = "True"
row.append(required)
if 'type' in fields:
type = ''
if 'type' in item:
if item['type'] == 'array':
type = "[ {} ]".format(item['items']['type'])
else:
type = item['type']
if 'format' in item:
type += " ({})".format(item['format'])
type = "{}".format(type)
row.append(type)
if 'schema' in fields:
if 'schema' in item:
if '$ref' in item['schema']:
row.append(make_ref(item['schema']['$ref']))
else:
row.append('')
table.append(row)
return make_table(table)
except KeyError as e:
handleException('KeyError', e)
def make_responses_table(responses):
try:
fields = list(
set([]).union(*map(lambda x: x.keys(),
map(lambda x: responses[x], responses.keys())
))
)
row = [ 'Status Code', 'Description' ]
if 'headers' in fields:
row.append('Headers')
if 'schema' in fields:
row.append('Schema')
if 'examples' in fields:
row.append('Examples')
table = [ row ]
for key in sorted(responses):
response = responses[key]
row = [ "{}".format(key), make_html(response['description']) ]
if 'headers' in fields:
header = ''
if 'headers' in response:
hrow = []
for header, h_obj in response['headers'].iteritems():
hrow += "{} ({})".format(header, h_obj['type'])
if 'description' in h_obj:
hrow += ": {}".format(h_obj['description'])
header = ' \n'.join(hrow)
row.append(header)
if 'schema' in fields:
schema = ''
if 'schema' in response:
if '$ref' in response['schema']:
schema += make_ref(response['schema']['$ref'])
if 'type' in response['schema']:
if response['schema']['type'] == 'array':
if '$ref' in response['schema']['items']:
schema += make_ref(response['schema']['items']['$ref'])
schema = "[ {} ]".format(schema)
row.append(schema)
if 'examples' in fields:
if 'examples' in response:
row.append(response['examples'])
else:
row.append('')
table.append(row)
return make_table(table)
except KeyError as e:
handleException('KeyError', e)
def sorted_by_method(section):
sorting_function = lambda x: [ 'GET', 'POST', 'PUT', 'DELETE' ].index(
x['title'].split(' ')[0]
)
return sorted(sorted(section), key=sorting_function)
def make_paths(sections, json_data):
md = 'Paths
\n'
for key in sorted(sections):
md += '{0}
\n'.format(key)
for section in sorted_by_method(sections[key]):
md += '{}
\n'.format(
section['href'], section['title']
)
operation = section['operation']
if 'summary' in operation:
md += 'Summary: {}
\n'.format(make_html(operation['summary']))
if 'description' in operation:
md += 'Description: {}
\n'.format(make_html(operation['description']))
md += 'Parameters
\n'
if 'parameters' in operation and len(operation['parameters']) > 0:
md += make_params_table(operation['parameters'], json_data)
else:
md += "None
\n"
md += 'Responses
\n'
md += make_responses_table(operation['responses'])
md += '\n'
md += '\n'
return md
def make_contents(path_section, json_data):
md = 'Contents
\n'
md += '\n'
md += ' - Paths\n'
md += '
\n'
for key in sorted(path_section):
md += ' - {0}\n'.format(key)
md += '
\n'
for section in sorted_by_method(path_section[key]):
md += ' - {}
\n'.format(
section['href'], section['title']
)
md += '
\n'
md += ' \n'
md += '
\n'
md += ' \n'
md += ' - Models\n'
md += '
\n'
for key in sorted(json_data['definitions']):
md += ' - {0}
\n'.format(key)
md += '
\n'
md += ' \n'
md += '
\n'
return md
def make_definitions(json_data):
md = 'Models
\n'
for name in sorted(json_data['definitions']):
md += '{0}
\n'.format(name)
model = json_data['definitions'][name]
if 'properties' in model:
fields = list(
set(['type']).union(
*map(lambda x: x.keys(),
map(lambda x: model['properties'][x], model['properties'].keys())
)
)
)
row = [ 'Property', 'Type' ]
if 'description' in fields:
row.append('Description')
table = [ row ]
for key in sorted(model['properties']):
property = model['properties'][key]
row = [ "{}".format(key) ]
if 'type' in property:
type = property['type']
if 'format' in property:
type += " ({})".format(property['format'])
row.append("{}".format(type))
elif '$ref' in property:
row.append(make_ref(property['$ref']))
else:
row.append('')
if 'description' in fields:
if 'description' in property:
row.append(make_html(property['description']))
else:
row.append('')
table.append(row)
md += make_table(table)
return md
def make_markdown(json_data):
path_sections = {}
for endpoint in json_data['paths']:
path_split = endpoint.split('/')
path_key = path_split[1]
if not path_key in path_sections:
path_sections[path_key] = []
for method, operation in json_data['paths'][endpoint].iteritems():
if 'operationId' in operation:
link = operation['operationId']
else:
link = ''.join([
c for c in endpoint if c not in ['/', '{', '}']
])
path_sections[path_key].append({
'title': '{} {}'.format(method.upper(), endpoint),
'href': 'paths_{}_{}'.format(link, method.upper()),
'operation': operation
})
md = make_header(json_data)
md += make_contents(path_sections, json_data)
md += make_paths(path_sections, json_data)
md += make_definitions(json_data)
return md
def main():
parser = argparse.ArgumentParser()
parser.add_argument('SPECFILE', help="path to swagger.json file")
parser.add_argument('OUTFILE', help="path to output HTML file")
args = parser.parse_args()
marked_down = make_markdown(get_json(args.SPECFILE))
if args.OUTFILE:
write_file(args.OUTFILE, marked_down)
print " success: {}".format(args.OUTFILE)
else:
print marked_down
if __name__ == '__main__':
main()