├── __init__.py ├── test ├── __init__.py ├── testdataDefaults.py ├── testdataValidValues.py ├── testdataArgument.py ├── testdataBodyArgument.py ├── testdataQueryArgument.py ├── testdataArguments.py └── testdataMethod.py ├── dev ├── rerunner.sh ├── http_cors_server.py └── index.html ├── .gitignore ├── processDefaults.py ├── LICENSE ├── README.md ├── processQueryArgument.py ├── processBodyArgument.py ├── processValidValues.py ├── processArguments.py ├── generate_swagger_TrelloAPI.py ├── processMethod.py └── processModelName.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/rerunner.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # export the_file="processValidValues.py" 3 | # export the_file="processDefaults.py" 4 | # export the_file="processArgument.py" 5 | export the_file="processArguments.py" 6 | 7 | while true; do 8 | change=$(inotifywait -e close_write .) 9 | change=${change#./ * } 10 | if [ "$change" = ${the_file} ]; then ./${the_file}; fi 11 | done 12 | -------------------------------------------------------------------------------- /dev/http_cors_server.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | from SimpleHTTPServer import SimpleHTTPRequestHandler 3 | import BaseHTTPServer 4 | 5 | class CORSRequestHandler (SimpleHTTPRequestHandler): 6 | def end_headers (self): 7 | self.send_header('Access-Control-Allow-Origin', '*') 8 | SimpleHTTPRequestHandler.end_headers(self) 9 | 10 | if __name__ == '__main__': 11 | BaseHTTPServer.test(CORSRequestHandler, BaseHTTPServer.HTTPServer) 12 | 13 | -------------------------------------------------------------------------------- /test/testdataDefaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Constants 5 | swagger = { 6 | "name": "petId", 7 | "description": "Pet id to delete", 8 | "required": True, 9 | } 10 | 11 | test_values = [] 12 | test_values.append(""" 13 |
  • Default: false
  • 14 | """) 15 | test_values.append(""" 16 |
  • Default: name,closed,idOrganization,pinned
  • 17 | """) 18 | test_values.append(""" 19 |
  • Default: name,desc,descData,closed,idOrganization,pinned,url,shortUrl,prefs,labelNames
  • 20 | """) 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # backup files 9 | *~ 10 | *.*~ 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | -------------------------------------------------------------------------------- /processDefaults.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from bs4 import BeautifulSoup 3 | 4 | # Constants 5 | DEFAULT = "Default:" 6 | 7 | # Methods 8 | def processDefaults(soup, swagger): 9 | 10 | default = '' 11 | for string in soup.code.stripped_strings : 12 | default = ' and'.join(', '.join(string.split(',')).rsplit(',', 1)) 13 | 14 | swagger['default'] = default 15 | if "true" in default : swagger['default'] = True 16 | if "false" in default : swagger['default'] = False 17 | 18 | return 19 | 20 | 21 | # - - - - - - - - - - - 22 | # Main routine (for testing) 23 | def main(): 24 | from test.testdataDefaults import test_values 25 | from test.testdataDefaults import swagger 26 | 27 | idx = 0 28 | for frag in test_values : 29 | print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 30 | soup = BeautifulSoup(frag) 31 | # if idx == 1 : 32 | if idx != -1 : 33 | processDefaults(soup.body.li, swagger) 34 | print "swagger = {}".format(swagger) 35 | idx = idx + 1 36 | 37 | print(">>~~~~~~~~~~~~~~~~~~~~~~~~~<<") 38 | 39 | 40 | if __name__ == "__main__": main() 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Warehouseman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # trello-swagger-generator 2 | Reads Trello's crappy API documentation pages and generates a swagger file. 3 | 4 | 5 | ### to generate the swagger 6 | 7 | pip install beautifulsoup4 8 | ./generate_swagger_TrelloAPI.py 9 | 10 | ### to view the swagger file 11 | #### run the simple Python http server 12 | 13 | dev/http_cors_server.py 14 | 15 | Open your browser to [localhost:8000](http://localhost:8000) 16 | 17 | ![Imgur](http://i.imgur.com/cK1V7MU.png) 18 | 19 | 20 | #### obtain and run the swagger.io viewer 21 | 22 | cd ~/projects 23 | wget -O swagger.zip https://github.com/swagger-api/swagger-ui/archive/master.zip 24 | unzip swagger.zip 25 | mv swagger-ui-master swagger-ui 26 | mv swagger.zip swagger-ui 27 | cd swagger-ui 28 | 29 | edit `dist/index.html` line #32 replacing ... 30 | 31 | url = "http://petstore.swagger.io/v2/swagger.json"; 32 | 33 | ... with ... 34 | 35 | url = "http://localhost:8000/TrelloAPI.json"; 36 | 37 | Open a second tab in your browser to the local file address file : 38 | 39 | file:///home/yourself/projects/swagger-ui/dist/index.html 40 | 41 | 42 | 43 | ![Imgur](http://i.imgur.com/CEvWdyn.png) 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /processQueryArgument.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from bs4 import BeautifulSoup 3 | from processValidValues import processValidValues 4 | from processDefaults import processDefaults 5 | from processModelName import determineModelName 6 | import json 7 | 8 | # Constants 9 | DEFAULT = "Default:" 10 | REQUIRED = "required" 11 | NONE = -1 12 | 13 | def processQueryArgument(soup, swggr, cursor): 14 | 15 | swagger = swggr['paths'][cursor['path']][cursor['method']] 16 | 17 | ''' 18 | print "##### " 19 | print repr(soup.code.span.next_element.next_element.strip()) 20 | print "##### " 21 | ''' 22 | 23 | name = soup.code.span.text 24 | # print 'Name : {}\n\n'.format(name) 25 | 26 | required = False 27 | if REQUIRED in soup.code.span.next_element.next_element.strip() : 28 | required = True 29 | 30 | 31 | parameter = { 32 | "name" : name.strip() 33 | , "required" : required 34 | , "in" : "query" 35 | , "type" : "string" 36 | , "description" : "undefined" 37 | , "default" : "undefined" 38 | } 39 | vvals = soup.find_all(text='Valid Values:') 40 | 41 | ''' 42 | print "::::: " 43 | print ' {}'.format(vvals) 44 | print "::::: " 45 | ''' 46 | for vval in vvals : 47 | processValidValues(vval.parent.parent, parameter) 48 | 49 | defaults = soup.find_all(text='Default:') 50 | for default in defaults : 51 | processDefaults(default.parent.parent, parameter) 52 | 53 | swagger["parameters"].append(parameter) 54 | 55 | return 56 | 57 | 58 | 59 | # - - - - - - - - - - - 60 | # Main routine (for testing) 61 | def main(): 62 | 63 | from test.testdataQueryArgument import test_values 64 | from test.testdataQueryArgument import swagger 65 | from test.testdataQueryArgument import cursor 66 | 67 | idx = 0 68 | for frag in test_values : 69 | print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 70 | soup = BeautifulSoup(frag) 71 | # if idx == 0 : 72 | if idx != -1 : 73 | processQueryArgument(soup.body, swagger, cursor) 74 | idx = idx + 1 75 | 76 | print json.dumps(swagger) 77 | 78 | 79 | print("\n>>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<<\n") 80 | 81 | 82 | 83 | if __name__ == "__main__": main() 84 | 85 | -------------------------------------------------------------------------------- /test/testdataValidValues.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Constants 5 | swagger = { 6 | "name": "petId", 7 | "in": "path", 8 | "description": "Pet id to delete", 9 | "required": True, 10 | "type": "undefined", 11 | "format": "undefined" 12 | } 13 | 14 | test_values = [] 15 | test_values.append(""" 16 |
  • Valid Values: A valid label color or null
  • 17 | """) 18 | test_values.append(""" 19 |
  • Valid Values: An id
  • 20 | """) 21 | test_values.append(""" 22 |
  • Valid Values: all or a comma-separated list of: 23 | 27 |
  • 28 | """) 29 | test_values.append(""" 30 |
  • Valid Values: all or a comma-separated list of: 51 |
  • 52 | """) 53 | 54 | 55 | -------------------------------------------------------------------------------- /processBodyArgument.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from bs4 import BeautifulSoup 3 | from processValidValues import processValidValues 4 | from processDefaults import processDefaults 5 | from processModelName import determineModelName 6 | from pprint import pprint 7 | import json 8 | import re 9 | 10 | # Constants 11 | DEFAULT = "Default:" 12 | REQUIRED = "required" 13 | NONE = -1 14 | 15 | def processBodyArgument(soup, swggr, cursor): 16 | 17 | # if cursor['entity'] == "board" and cursor['method'] != "get" \ 18 | # and cursor['path'] in ["/boards","/cards","/checklists","/labels","/lists","/organizations","/sessions","/webhooks"] : 19 | # # ["/boards/{idBoard}/lists"] : 20 | 21 | if cursor['path'] in ["xxxxx/members/{idMember}/boardStars/{idBoardStar}/idBoard"] : 22 | debugPrint = True 23 | else : 24 | debugPrint = False 25 | 26 | 27 | name = soup.code.span.text 28 | if debugPrint : print 'Name : {}\n\n'.format(name) 29 | 30 | required = False 31 | if REQUIRED in soup.code.span.next_element.next_element.strip() : 32 | required = True 33 | 34 | swagger = swggr['paths'][cursor['path']][cursor['method']] 35 | 36 | 37 | ''' 38 | print "##### " 39 | print repr(soup.code.span.next_element.next_element.strip()) 40 | print "##### " 41 | ''' 42 | 43 | parameter = { 44 | "name" : name.strip() 45 | , "required" : required 46 | , "in" : "query" 47 | , "type" : "string" 48 | , "description" : "undefined" 49 | , "default" : "undefined" 50 | } 51 | vvals = soup.find_all(text='Valid Values:') 52 | 53 | ''' 54 | print "::::: " 55 | print ' {}'.format(vvals) 56 | print "::::: " 57 | ''' 58 | for vval in vvals : 59 | processValidValues(vval.parent.parent, parameter) 60 | 61 | defaults = soup.find_all(text='Default:') 62 | for default in defaults : 63 | processDefaults(default.parent.parent, parameter) 64 | 65 | model = determineModelName(cursor['path']) 66 | 67 | if model not in swggr['definitions']: 68 | swggr['definitions'][model] = { 69 | "type" : "object", 70 | "properties" : { 71 | }, 72 | "xml":{ 73 | "name":cursor['entity'] 74 | } 75 | } 76 | 77 | swggr['definitions'][model]['properties'][parameter['name']] = { 78 | 'type' : parameter['type'], 79 | 'description' : parameter['description'], 80 | } 81 | 82 | if debugPrint : 83 | print 'Swagger definitions : {}\n\n'.format(model) 84 | pprint(swggr['definitions'][model]) 85 | 86 | # swagger["parameters"].append(parameter) 87 | 88 | return model 89 | 90 | 91 | # - - - - - - - - - - - 92 | # Main routine (for testing) 93 | def main(): 94 | 95 | from test.testdataBodyArgument import test_values 96 | from test.testdataBodyArgument import swagger 97 | from test.testdataBodyArgument import cursor 98 | 99 | idx = 0 100 | for frag in test_values : 101 | print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 102 | soup = BeautifulSoup(frag) 103 | # if idx == 0 : 104 | if idx != -1 : 105 | processBodyArgument(soup.body, swagger, cursor) 106 | idx = idx + 1 107 | 108 | print json.dumps(swagger) 109 | 110 | 111 | print("\n>>~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~<<\n") 112 | 113 | 114 | 115 | if __name__ == "__main__": main() 116 | 117 | ''' 118 | "List":{ 119 | "type":"object", 120 | "properties":{ 121 | "name":{ 122 | "type":"string" 123 | }, 124 | "pos":{ 125 | "type":"string", 126 | "default":"top" 127 | } 128 | }, 129 | "xml":{ 130 | "name":"Pet" 131 | } 132 | } 133 | 134 | ''' 135 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 85 | 86 | 87 | 88 | 98 | 99 |
     
    100 |
    101 | 102 | 103 | -------------------------------------------------------------------------------- /processValidValues.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from bs4 import BeautifulSoup 4 | 5 | # Constants 6 | VALID_VALUES = "Valid Values:" 7 | 8 | # Methods 9 | def processValidValues(soup, swagger): 10 | 11 | if (soup.ul == None) : # no attached