├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── dev-requirements.txt ├── docs ├── Makefile ├── _static │ ├── ajax-loader.gif │ ├── basic.css │ ├── comment-bright.png │ ├── comment-close.png │ ├── comment.png │ ├── css │ │ ├── badge_only.css │ │ ├── custom.css │ │ └── rtdtheme.css │ ├── doctools.js │ ├── down-pressed.png │ ├── down.png │ ├── favicon.ico │ ├── file.png │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── fontawesome_webfont.ttf │ │ ├── fontawesome_webfont.woff │ │ └── fontawesome_webfont.woff2 │ ├── jquery.js │ ├── js │ │ └── theme.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore.js │ ├── up-pressed.png │ ├── up.png │ └── websupport.js ├── api.rst ├── changelog.rst ├── conf.py ├── config.rst ├── contributing.rst ├── index.rst ├── installation.rst ├── intro.rst ├── license.rst └── usage.rst ├── griffin ├── __init__.py ├── __main__.py ├── _filters.py ├── cli.py ├── config.py ├── core.py ├── logger.py ├── templates │ ├── base.html │ ├── collection_sidebar.html │ ├── custom.css │ ├── documentation.html │ ├── endpoint │ │ ├── body.html │ │ ├── example.html │ │ ├── form_params.html │ │ ├── general.html │ │ ├── headers.html │ │ ├── index.html │ │ ├── params.html │ │ ├── query_params.html │ │ ├── responses.html │ │ ├── secured.html │ │ └── uri_params.html │ ├── head.html │ ├── index.html │ ├── navbar.html │ └── scripts.html ├── themes │ └── default │ │ ├── css │ │ ├── bootstrap.css │ │ ├── bootstrap.min.css │ │ ├── default.css │ │ └── hljs.css │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ ├── highlight.pack.js │ │ └── jquery.linkify-1.0.js └── utils.py ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── base.py ├── cli_input │ ├── box.raml │ ├── config │ │ ├── github.ini │ │ ├── instagram.ini │ │ ├── instagram.yaml │ │ └── simple_config.yaml │ ├── example.raml │ ├── github.raml │ ├── includes │ │ └── spotify │ │ │ ├── album │ │ │ ├── full.example.json │ │ │ ├── full.schema.json │ │ │ ├── simple.example.json │ │ │ └── simple.schema.json │ │ │ ├── get-playlists.example.json │ │ │ ├── get-playlists.schema.json │ │ │ ├── put_playlist_tracks.example.json │ │ │ ├── put_playlist_tracks.schema.json │ │ │ ├── responses │ │ │ ├── albums.example.json │ │ │ ├── albums.schema.json │ │ │ ├── tracks.example.json │ │ │ └── tracks.schema.json │ │ │ ├── track │ │ │ ├── full.schema.json │ │ │ └── simplified.schema.json │ │ │ └── user │ │ │ └── playlist │ │ │ ├── full.example.json │ │ │ └── full.schema.json │ ├── index.md │ ├── instagram.raml │ ├── spotify.raml │ └── stripe.raml ├── test_cli.py ├── test_config.py ├── test_core.py ├── test_logger.py ├── test_main.py └── test_utils.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.py[cod] 3 | .Python 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | 23 | # pip 24 | pip-log.txt 25 | pip-delete-this-directory.txt 26 | 27 | # Unit test / coverage reports 28 | .coverage 29 | .tox 30 | nosetests.xml 31 | coverage.xml 32 | .cache 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Sphinx 38 | docs/_build 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 2.7 3 | env: 4 | - TOX_ENV=py26 5 | - TOX_ENV=py27 6 | - TOX_ENV=py33 7 | - TOX_ENV=py34 8 | - TOX_ENV=pypy 9 | - TOX_ENV=docs 10 | - TOX_ENV=flake8 11 | - TOX_ENV=manifest 12 | install: 13 | - pip install tox coveralls 14 | script: 15 | - tox --hashseed 0 -e $TOX_ENV 16 | notifications: 17 | email: false 18 | slack: 19 | secure: "G1bX3FdqzbQb/f5CNwatSdPTbxKBo4HT5/wodDNtFyoj9ePd+Xh/h+d97QGB+6Swn7Gnp3r9bqmJrkffogc+N8RzhOJiFfVfOtXWqUa1Y5r/rxO0xZz7Q8PVRAIW0N6YYnq8t4bp9R3yYOdQYtgfUOmKq2TlnEEtZA4MeH7jYwc=" 20 | deploy: 21 | provider: pypi 22 | user: roguelynn 23 | password: 24 | secure: XgUz7PiCHCOVM1yZeNfuy2j73aAGpW5HtTocmjlKNqG9wKXGkqV4IFWneznE1n8hqQj+Tqmm4MQ0AftJcomEh9WESVsglwMOqwMericRcsVkrEq+XHPynMfqsUc/MEr+zAB/Tj9SsbYMjP8zsk4TdJx/s+EbYFXsDfHrxcQY6q4= 25 | on: 26 | tags: true 27 | distributions: sdist bdist_wheel 28 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Lynn Root 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Contributing 3 | ============ 4 | 5 | Contributions are welcome, and they are greatly appreciated! Every 6 | little bit helps, and credit will always be given. 7 | 8 | You can contribute in many ways: 9 | 10 | Types of Contributions 11 | ---------------------- 12 | 13 | Report Bugs 14 | ~~~~~~~~~~~ 15 | 16 | Report bugs at https://github.com/spotify/griffin/issues. 17 | 18 | If you are reporting a bug, please include: 19 | 20 | * Your operating system name and version. 21 | * Any details about your local setup that might be helpful in troubleshooting. 22 | * Detailed steps to reproduce the bug. 23 | 24 | Fix Bugs 25 | ~~~~~~~~ 26 | 27 | Look through the GitHub issues for bugs. Anything tagged with "bug" 28 | is open to whoever wants to implement it. 29 | 30 | Implement Features 31 | ~~~~~~~~~~~~~~~~~~ 32 | 33 | Look through the GitHub issues for features. Anything tagged with "feature" 34 | is open to whoever wants to implement it. 35 | 36 | Write Documentation 37 | ~~~~~~~~~~~~~~~~~~~ 38 | 39 | griffin could always use more documentation, whether as part of the 40 | official griffin docs, in docstrings, or even on the web in blog posts, 41 | articles, and such. 42 | 43 | Submit Feedback 44 | ~~~~~~~~~~~~~~~ 45 | 46 | The best way to send feedback is to file an issue at https://github.com/spotify/griffin/issues. 47 | 48 | If you are proposing a feature: 49 | 50 | * Explain in detail how it would work. 51 | * Keep the scope as narrow as possible, to make it easier to implement. 52 | * Remember that this is a volunteer-driven project, and that contributions 53 | are welcome :) 54 | 55 | Get Started! 56 | ------------ 57 | 58 | Ready to contribute? Here's how to set up `griffin` for 59 | local development. 60 | 61 | 1. Fork_ the `griffin` repo on GitHub. 62 | 2. Clone your fork locally:: 63 | 64 | $ git clone git@github.com:your_name_here/griffin.git 65 | 66 | 3. Create a branch for local development:: 67 | 68 | $ git checkout -b name-of-your-bugfix-or-feature 69 | 70 | Now you can make your changes locally. 71 | 72 | 4. When you're done making changes, check that your changes pass style and unit 73 | tests, including testing other Python versions with tox:: 74 | 75 | $ tox 76 | 77 | To get tox, just pip install it. 78 | 79 | 5. Commit your changes and push your branch to GitHub:: 80 | 81 | $ git add . 82 | $ git commit -m "Your detailed description of your changes." 83 | $ git push origin name-of-your-bugfix-or-feature 84 | 85 | 6. Submit a pull request through the GitHub website. 86 | 87 | .. _Fork: https://github.com/Nekroze/griffin/fork 88 | 89 | Pull Request Guidelines 90 | ----------------------- 91 | 92 | Before you submit a pull request, check that it meets these guidelines: 93 | 94 | 1. The pull request should include tests. 95 | 2. If the pull request adds functionality, the docs should be updated. Put 96 | your new functionality into a function with a docstring, and add the 97 | feature to the list in README.rst. 98 | 3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. 99 | Check https://travis-ci.org/spotify/griffin 100 | under pull requests for active pull requests or run the ``tox`` command and 101 | make sure that the tests pass for all supported Python versions. 102 | 103 | 104 | Tips 105 | ---- 106 | 107 | To run a subset of tests:: 108 | 109 | $ py.test test/test_griffin.py 110 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Lynn Root, Spotify AB 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst *.txt docs/Makefile LICENSE .coveragerc .travis.yml *.ini 2 | recursive-include griffin *.py 3 | recursive-include griffin/templates * 4 | recursive-include griffin/themes * 5 | recursive-include docs *.py *.rst 6 | recursive-include docs/_static * 7 | prune docs/_build 8 | recursive-include tests *.py *.json *.md *.py *.raml *.yaml *.yml *.ini 9 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | griffin: RAML reference implementation in Python 2 | ====================================================== 3 | 4 | .. image:: https://img.shields.io/pypi/v/griffin.svg?style=flat-square 5 | :target: https://pypi.python.org/pypi/griffin/ 6 | :alt: Latest Version 7 | 8 | .. image:: https://img.shields.io/travis/spotify/griffin.svg?style=flat-square 9 | :target: https://travis-ci.org/spotify/griffin 10 | :alt: CI status 11 | 12 | .. image:: https://img.shields.io/pypi/status/griffin.svg?style=flat-square 13 | :target: https://pypi.python.org/pypi/griffin/ 14 | :alt: Development Status 15 | 16 | .. image:: https://img.shields.io/pypi/l/griffin.svg?style=flat-square 17 | :target: https://github.com/spotify/griffin/blob/master/LICENSE 18 | :alt: License 19 | 20 | .. image:: https://img.shields.io/coveralls/spotify/griffin/master.svg?style=flat-square 21 | :target: https://coveralls.io/r/spotify/griffin?branch=master 22 | :alt: Current coverage 23 | 24 | .. image:: https://img.shields.io/pypi/pyversions/griffin.svg?style=flat-square 25 | :target: https://pypi.python.org/pypi/griffin/ 26 | :alt: Supported Python versions 27 | 28 | .. begin 29 | 30 | .. warning:: 31 | 32 | This is an ALPHA! Be prepared for shit to break! 33 | 34 | Requirements and Installation 35 | ============================= 36 | 37 | User Setup 38 | ---------- 39 | 40 | The latest version (currently alpha only) can be found on PyPI_, and you can install via pip_:: 41 | 42 | $ pip install griffin --pre 43 | 44 | The ``--pre`` is needed to download since it's still in alpha. 45 | 46 | Continue onto `usage`_ to get started on using ``griffin``. 47 | 48 | Supported Python/Systems 49 | ^^^^^^^^^^^^^^^^^^^^^^^^ 50 | 51 | .. warning:: 52 | currently runs with Python 2.7 - but will get up to 3.3+ and PyPy 53 | 54 | Both Linux and OS X are supported. 55 | 56 | 57 | 58 | Developer Setup 59 | --------------- 60 | 61 | If you'd like to contribute or develop upon ``griffin``, be sure to read `How to Contribute`_ 62 | first. 63 | 64 | System requirements: 65 | ^^^^^^^^^^^^^^^^^^^^ 66 | 67 | - C Compiler (gcc/clang/etc.) 68 | - If on Linux - you'll need to install Python headers (e.g. ``apt-get install python-dev``) 69 | - Python 2.6, 2.7, 3.3+, or PyPy 70 | - virtualenv_ 71 | 72 | Here's how to set your machine up:: 73 | 74 | $ git clone git@github.com:spotify/griffin 75 | $ cd griffin 76 | $ virtualenv env 77 | $ source env/bin/activate 78 | (env) $ pip install -r dev-requirements.txt 79 | 80 | 81 | Run Tests 82 | ^^^^^^^^^ 83 | 84 | If you'd like to run tests for all supported Python versions, you must have all Python versions 85 | installed on your system. I suggest pyenv_ to help with that. 86 | 87 | To run all tests:: 88 | 89 | (env) $ tox 90 | 91 | To run a specific test setup (options include: ``py26``, ``py27``, ``py33``, ``py34``, ``pypy``, 92 | ``flake8``, ``verbose``, ``manifest``, ``docs``, ``setup``, ``setupcov``):: 93 | 94 | (env) $ tox -e py26 95 | 96 | To run tests without tox:: 97 | 98 | (env) $ py.test 99 | (env) $ py.test --cov griffin --cov-report term-missing 100 | 101 | 102 | Build Docs 103 | ^^^^^^^^^^ 104 | 105 | Documentation is build with Sphinx_, written in rST, uses the `Read the Docs`_ theme with 106 | a slightly customized CSS, and is hosted on `Read the Docs site`_. 107 | 108 | To rebuild docs locally, within the parent ``griffin`` directory:: 109 | 110 | (env) $ tox -e docs 111 | 112 | or:: 113 | 114 | (env) $ sphinx-build -b docs/ docs/_build 115 | 116 | 117 | or:: 118 | 119 | (env) $ cd docs 120 | (env) $ make html 121 | 122 | Then within ``griffin/docs/_build`` you can open the index.html page in your browser. 123 | 124 | 125 | Still have issues? 126 | ^^^^^^^^^^^^^^^^^^ 127 | 128 | Feel free to drop by ``#ramlfications`` on Freenode (`webchat`_) (no dedicated IRC channel - yet) \ 129 | or ping via `Twitter`_. "roguelynn" on IRC is the maintainer, a.k.a `econchick`_ on GitHub, \ 130 | and based in San Fran. 131 | 132 | 133 | .. _pip: https://pip.pypa.io/en/latest/installing.html#install-pip 134 | .. _PyPI: https://pypi.python.org/project/griffin/ 135 | .. _virtualenv: https://virtualenv.pypa.io/en/latest/ 136 | .. _pyenv: https://github.com/yyuu/pyenv 137 | .. _Sphinx: http://sphinx-doc.org/ 138 | .. _`Read the Docs`: https://github.com/snide/sphinx_rtd_theme 139 | .. _`Read the Docs site`: https://griffin.readthedocs.org 140 | .. _`usage`: http://griffin.readthedocs.org/en/latest/usage.html 141 | .. _`How to Contribute`: http://griffin.readthedocs.org/en/latest/contributing.html 142 | .. _`webchat`: http://webchat.freenode.net?channels=%23ramlfications&uio=ND10cnVlJjk9dHJ1ZQb4 143 | .. _`econchick`: https://github.com/econchick 144 | .. _`Twitter`: https://twitter.com/roguelynn 145 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | Sphinx==1.2.3 3 | check-manifest==0.21 4 | coverage==3.7.1 5 | flake8==2.2.3 6 | sphinx-rtd-theme==0.1.6 7 | tox==1.7.2 8 | pytest==2.6.4 9 | pytest-cov==1.8.1 10 | wheel>=0.22 11 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/complexity.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/complexity.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/complexity" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/complexity" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | } 56 | 57 | div.sphinxsidebar ul { 58 | list-style: none; 59 | } 60 | 61 | div.sphinxsidebar ul ul, 62 | div.sphinxsidebar ul.want-points { 63 | margin-left: 20px; 64 | list-style: square; 65 | } 66 | 67 | div.sphinxsidebar ul ul { 68 | margin-top: 0; 69 | margin-bottom: 0; 70 | } 71 | 72 | div.sphinxsidebar form { 73 | margin-top: 10px; 74 | } 75 | 76 | div.sphinxsidebar input { 77 | border: 1px solid #98dbcc; 78 | font-family: sans-serif; 79 | font-size: 1em; 80 | } 81 | 82 | div.sphinxsidebar #searchbox input[type="text"] { 83 | width: 170px; 84 | } 85 | 86 | div.sphinxsidebar #searchbox input[type="submit"] { 87 | width: 30px; 88 | } 89 | 90 | img { 91 | border: 0; 92 | max-width: 100%; 93 | } 94 | 95 | /* -- search page ----------------------------------------------------------- */ 96 | 97 | ul.search { 98 | margin: 10px 0 0 20px; 99 | padding: 0; 100 | } 101 | 102 | ul.search li { 103 | padding: 5px 0 5px 20px; 104 | background-image: url(file.png); 105 | background-repeat: no-repeat; 106 | background-position: 0 7px; 107 | } 108 | 109 | ul.search li a { 110 | font-weight: bold; 111 | } 112 | 113 | ul.search li div.context { 114 | color: #888; 115 | margin: 2px 0 0 30px; 116 | text-align: left; 117 | } 118 | 119 | ul.keywordmatches li.goodmatch a { 120 | font-weight: bold; 121 | } 122 | 123 | /* -- index page ------------------------------------------------------------ */ 124 | 125 | table.contentstable { 126 | width: 90%; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable dl, table.indextable dd { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | } 158 | 159 | table.indextable tr.pcap { 160 | height: 10px; 161 | } 162 | 163 | table.indextable tr.cap { 164 | margin-top: 10px; 165 | background-color: #f2f2f2; 166 | } 167 | 168 | img.toggler { 169 | margin-right: 3px; 170 | margin-top: 3px; 171 | cursor: pointer; 172 | } 173 | 174 | div.modindex-jumpbox { 175 | border-top: 1px solid #ddd; 176 | border-bottom: 1px solid #ddd; 177 | margin: 1em 0 1em 0; 178 | padding: 0.4em; 179 | } 180 | 181 | div.genindex-jumpbox { 182 | border-top: 1px solid #ddd; 183 | border-bottom: 1px solid #ddd; 184 | margin: 1em 0 1em 0; 185 | padding: 0.4em; 186 | } 187 | 188 | /* -- general body styles --------------------------------------------------- */ 189 | 190 | a.headerlink { 191 | visibility: hidden; 192 | } 193 | 194 | h1:hover > a.headerlink, 195 | h2:hover > a.headerlink, 196 | h3:hover > a.headerlink, 197 | h4:hover > a.headerlink, 198 | h5:hover > a.headerlink, 199 | h6:hover > a.headerlink, 200 | dt:hover > a.headerlink { 201 | visibility: visible; 202 | } 203 | 204 | div.body p.caption { 205 | text-align: inherit; 206 | } 207 | 208 | div.body td { 209 | text-align: left; 210 | } 211 | 212 | .field-list ul { 213 | padding-left: 1em; 214 | } 215 | 216 | .first { 217 | margin-top: 0 !important; 218 | } 219 | 220 | p.rubric { 221 | margin-top: 30px; 222 | font-weight: bold; 223 | } 224 | 225 | img.align-left, .figure.align-left, object.align-left { 226 | clear: left; 227 | float: left; 228 | margin-right: 1em; 229 | } 230 | 231 | img.align-right, .figure.align-right, object.align-right { 232 | clear: right; 233 | float: right; 234 | margin-left: 1em; 235 | } 236 | 237 | img.align-center, .figure.align-center, object.align-center { 238 | display: block; 239 | margin-left: auto; 240 | margin-right: auto; 241 | } 242 | 243 | .align-left { 244 | text-align: left; 245 | } 246 | 247 | .align-center { 248 | text-align: center; 249 | } 250 | 251 | .align-right { 252 | text-align: right; 253 | } 254 | 255 | /* -- sidebars -------------------------------------------------------------- */ 256 | 257 | div.sidebar { 258 | margin: 0 0 0.5em 1em; 259 | border: 1px solid #ddb; 260 | padding: 7px 7px 0 7px; 261 | background-color: #ffe; 262 | width: 40%; 263 | float: right; 264 | } 265 | 266 | p.sidebar-title { 267 | font-weight: bold; 268 | } 269 | 270 | /* -- topics ---------------------------------------------------------------- */ 271 | 272 | div.topic { 273 | border: 1px solid #ccc; 274 | padding: 7px 7px 0 7px; 275 | margin: 10px 0 10px 0; 276 | } 277 | 278 | p.topic-title { 279 | font-size: 1.1em; 280 | font-weight: bold; 281 | margin-top: 10px; 282 | } 283 | 284 | /* -- admonitions ----------------------------------------------------------- */ 285 | 286 | div.admonition { 287 | margin-top: 10px; 288 | margin-bottom: 10px; 289 | padding: 7px; 290 | } 291 | 292 | div.admonition dt { 293 | font-weight: bold; 294 | } 295 | 296 | div.admonition dl { 297 | margin-bottom: 0; 298 | } 299 | 300 | p.admonition-title { 301 | margin: 0px 10px 5px 0px; 302 | font-weight: bold; 303 | } 304 | 305 | div.body p.centered { 306 | text-align: center; 307 | margin-top: 25px; 308 | } 309 | 310 | /* -- tables ---------------------------------------------------------------- */ 311 | 312 | table.docutils { 313 | border: 0; 314 | border-collapse: collapse; 315 | } 316 | 317 | table.docutils td, table.docutils th { 318 | padding: 1px 8px 1px 5px; 319 | border-top: 0; 320 | border-left: 0; 321 | border-right: 0; 322 | border-bottom: 1px solid #aaa; 323 | } 324 | 325 | table.field-list td, table.field-list th { 326 | border: 0 !important; 327 | } 328 | 329 | table.footnote td, table.footnote th { 330 | border: 0 !important; 331 | } 332 | 333 | th { 334 | text-align: left; 335 | padding-right: 5px; 336 | } 337 | 338 | table.citation { 339 | border-left: solid 1px gray; 340 | margin-left: 1px; 341 | } 342 | 343 | table.citation td { 344 | border-bottom: none; 345 | } 346 | 347 | /* -- other body styles ----------------------------------------------------- */ 348 | 349 | ol.arabic { 350 | list-style: decimal; 351 | } 352 | 353 | ol.loweralpha { 354 | list-style: lower-alpha; 355 | } 356 | 357 | ol.upperalpha { 358 | list-style: upper-alpha; 359 | } 360 | 361 | ol.lowerroman { 362 | list-style: lower-roman; 363 | } 364 | 365 | ol.upperroman { 366 | list-style: upper-roman; 367 | } 368 | 369 | dl { 370 | margin-bottom: 15px; 371 | } 372 | 373 | dd p { 374 | margin-top: 0px; 375 | } 376 | 377 | dd ul, dd table { 378 | margin-bottom: 10px; 379 | } 380 | 381 | dd { 382 | margin-top: 3px; 383 | margin-bottom: 10px; 384 | margin-left: 30px; 385 | } 386 | 387 | dt:target, .highlighted { 388 | background-color: #fbe54e; 389 | } 390 | 391 | dl.glossary dt { 392 | font-weight: bold; 393 | font-size: 1.1em; 394 | } 395 | 396 | .field-list ul { 397 | margin: 0; 398 | padding-left: 1em; 399 | } 400 | 401 | .field-list p { 402 | margin: 0; 403 | } 404 | 405 | .optional { 406 | font-size: 1.3em; 407 | } 408 | 409 | .versionmodified { 410 | font-style: italic; 411 | } 412 | 413 | .system-message { 414 | background-color: #fda; 415 | padding: 5px; 416 | border: 3px solid red; 417 | } 418 | 419 | .footnote:target { 420 | background-color: #ffa; 421 | } 422 | 423 | .line-block { 424 | display: block; 425 | margin-top: 1em; 426 | margin-bottom: 1em; 427 | } 428 | 429 | .line-block .line-block { 430 | margin-top: 0; 431 | margin-bottom: 0; 432 | margin-left: 1.5em; 433 | } 434 | 435 | .guilabel, .menuselection { 436 | font-family: sans-serif; 437 | } 438 | 439 | .accelerator { 440 | text-decoration: underline; 441 | } 442 | 443 | .classifier { 444 | font-style: oblique; 445 | } 446 | 447 | abbr, acronym { 448 | border-bottom: dotted 1px; 449 | cursor: help; 450 | } 451 | 452 | /* -- code displays --------------------------------------------------------- */ 453 | 454 | pre { 455 | overflow: auto; 456 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 457 | } 458 | 459 | td.linenos pre { 460 | padding: 5px 0px; 461 | border: 0; 462 | background-color: transparent; 463 | color: #aaa; 464 | } 465 | 466 | table.highlighttable { 467 | margin-left: 0.5em; 468 | } 469 | 470 | table.highlighttable td { 471 | padding: 0 0.5em 0 0.5em; 472 | } 473 | 474 | tt.descname { 475 | background-color: transparent; 476 | font-weight: bold; 477 | font-size: 1.2em; 478 | } 479 | 480 | tt.descclassname { 481 | background-color: transparent; 482 | } 483 | 484 | tt.xref, a tt { 485 | background-color: transparent; 486 | font-weight: bold; 487 | } 488 | 489 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { 490 | background-color: transparent; 491 | } 492 | 493 | .viewcode-link { 494 | float: right; 495 | } 496 | 497 | .viewcode-back { 498 | float: right; 499 | font-family: sans-serif; 500 | } 501 | 502 | div.viewcode-block:target { 503 | margin: -1px -10px; 504 | padding: 0 10px; 505 | } 506 | 507 | /* -- math display ---------------------------------------------------------- */ 508 | 509 | img.math { 510 | vertical-align: middle; 511 | } 512 | 513 | div.body div.math p { 514 | text-align: center; 515 | } 516 | 517 | span.eqno { 518 | float: right; 519 | } 520 | 521 | /* -- printout stylesheet --------------------------------------------------- */ 522 | 523 | @media print { 524 | div.document, 525 | div.documentwrapper, 526 | div.bodywrapper { 527 | margin: 0 !important; 528 | width: 100%; 529 | } 530 | 531 | div.sphinxsidebar, 532 | div.related, 533 | div.footer, 534 | #top-link { 535 | display: none; 536 | } 537 | } -------------------------------------------------------------------------------- /docs/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/comment.png -------------------------------------------------------------------------------- /docs/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-weight:normal;font-style:normal;src:url("../font/fontawesome_webfont.eot");src:url("../font/fontawesome_webfont.eot?#iefix") format("embedded-opentype"),url("../font/fontawesome_webfont.woff") format("woff"),url("../font/fontawesome_webfont.ttf") format("truetype"),url("../font/fontawesome_webfont.svg#FontAwesome") format("svg")}.fa:before{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;text-decoration:inherit}a .fa{display:inline-block;text-decoration:inherit}li .fa{display:inline-block}li .fa-large:before,li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-0.8em}ul.fas li .fa{width:0.8em}ul.fas li .fa-large:before,ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before{content:"\f02d"}.icon-book:before{content:"\f02d"}.fa-caret-down:before{content:"\f0d7"}.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;border-top:solid 10px #343131;font-family:"Lato","proxima-nova","Helvetica Neue",Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:before,.rst-versions .rst-current-version:after{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book{float:left}.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:gray;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:solid 1px #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px}.rst-versions.rst-badge .icon-book{float:none}.rst-versions.rst-badge .fa-book{float:none}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book{float:left}.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge .rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width: 768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}img{width:100%;height:auto}} 2 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | @import url("rtdtheme.css"); 2 | @import url("badge_only.css"); 3 | 4 | .wy-side-nav-search { 5 | background-color: #1ED760; 6 | } 7 | 8 | .wy-side-nav-search input[type=text] { 9 | width: 100%; 10 | border-radius: 50px; 11 | padding: 6px 12px; 12 | border-color: #1ED760 13 | } 14 | 15 | .rst-content a { 16 | color: #1ED760; 17 | } 18 | 19 | .rst-content dl:not(.docutils) dt { 20 | padding: 2px; 21 | } 22 | 23 | dt#ramlfications.raml.RAMLRoot.resources { 24 | padding: 2px; 25 | } 26 | 27 | 28 | code, .rst-content tt { 29 | color: #1ED760; 30 | } 31 | 32 | .descclassname, .descname { 33 | background: none; 34 | border: none; 35 | padding: 0; 36 | font-size: 100% !important; 37 | color: #000; 38 | font-weight: bold; 39 | } 40 | 41 | .fa-home:before, 42 | .icon-home:before { 43 | content: "\f1bc" 44 | } 45 | 46 | .wy-nav-content { 47 | padding: 1.618em 3.236em; 48 | height: 100%; 49 | max-width: 900px; 50 | margin: auto 51 | } 52 | -------------------------------------------------------------------------------- /docs/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /** 95 | * Small JavaScript module for the documentation. 96 | */ 97 | var Documentation = { 98 | 99 | init : function() { 100 | this.fixFirefoxAnchorBug(); 101 | this.highlightSearchWords(); 102 | this.initIndexTable(); 103 | }, 104 | 105 | /** 106 | * i18n support 107 | */ 108 | TRANSLATIONS : {}, 109 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 110 | LOCALE : 'unknown', 111 | 112 | // gettext and ngettext don't access this so that the functions 113 | // can safely bound to a different name (_ = Documentation.gettext) 114 | gettext : function(string) { 115 | var translated = Documentation.TRANSLATIONS[string]; 116 | if (typeof translated == 'undefined') 117 | return string; 118 | return (typeof translated == 'string') ? translated : translated[0]; 119 | }, 120 | 121 | ngettext : function(singular, plural, n) { 122 | var translated = Documentation.TRANSLATIONS[singular]; 123 | if (typeof translated == 'undefined') 124 | return (n == 1) ? singular : plural; 125 | return translated[Documentation.PLURALEXPR(n)]; 126 | }, 127 | 128 | addTranslations : function(catalog) { 129 | for (var key in catalog.messages) 130 | this.TRANSLATIONS[key] = catalog.messages[key]; 131 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 132 | this.LOCALE = catalog.locale; 133 | }, 134 | 135 | /** 136 | * add context elements like header anchor links 137 | */ 138 | addContextElements : function() { 139 | $('div[id] > :header:first').each(function() { 140 | $('\u00B6'). 141 | attr('href', '#' + this.id). 142 | attr('title', _('Permalink to this headline')). 143 | appendTo(this); 144 | }); 145 | $('dt[id]').each(function() { 146 | $('\u00B6'). 147 | attr('href', '#' + this.id). 148 | attr('title', _('Permalink to this definition')). 149 | appendTo(this); 150 | }); 151 | }, 152 | 153 | /** 154 | * workaround a firefox stupidity 155 | */ 156 | fixFirefoxAnchorBug : function() { 157 | if (document.location.hash && $.browser.mozilla) 158 | window.setTimeout(function() { 159 | document.location.href += ''; 160 | }, 10); 161 | }, 162 | 163 | /** 164 | * highlight the search words provided in the url in the text 165 | */ 166 | highlightSearchWords : function() { 167 | var params = $.getQueryParameters(); 168 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 169 | if (terms.length) { 170 | var body = $('div.body'); 171 | if (!body.length) { 172 | body = $('body'); 173 | } 174 | window.setTimeout(function() { 175 | $.each(terms, function() { 176 | body.highlightText(this.toLowerCase(), 'highlighted'); 177 | }); 178 | }, 10); 179 | $('') 181 | .appendTo($('#searchbox')); 182 | } 183 | }, 184 | 185 | /** 186 | * init the domain index toggle buttons 187 | */ 188 | initIndexTable : function() { 189 | var togglers = $('img.toggler').click(function() { 190 | var src = $(this).attr('src'); 191 | var idnum = $(this).attr('id').substr(7); 192 | $('tr.cg-' + idnum).toggle(); 193 | if (src.substr(-9) == 'minus.png') 194 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 195 | else 196 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 197 | }).css('display', ''); 198 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 199 | togglers.click(); 200 | } 201 | }, 202 | 203 | /** 204 | * helper function to hide the search marks again 205 | */ 206 | hideSearchWords : function() { 207 | $('#searchbox .highlight-link').fadeOut(300); 208 | $('span.highlighted').removeClass('highlighted'); 209 | }, 210 | 211 | /** 212 | * make the url absolute 213 | */ 214 | makeURL : function(relativeURL) { 215 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 216 | }, 217 | 218 | /** 219 | * get the current relative url 220 | */ 221 | getCurrentURL : function() { 222 | var path = document.location.pathname; 223 | var parts = path.split(/\//); 224 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 225 | if (this == '..') 226 | parts.pop(); 227 | }); 228 | var url = parts.join('/'); 229 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 230 | } 231 | }; 232 | 233 | // quick alias for translations 234 | _ = Documentation.gettext; 235 | 236 | $(document).ready(function() { 237 | Documentation.init(); 238 | }); 239 | -------------------------------------------------------------------------------- /docs/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/down.png -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/file.png -------------------------------------------------------------------------------- /docs/_static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome_webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome_webfont.ttf -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome_webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome_webfont.woff -------------------------------------------------------------------------------- /docs/_static/fonts/fontawesome_webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/fonts/fontawesome_webfont.woff2 -------------------------------------------------------------------------------- /docs/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | $( document ).ready(function() { 2 | // Shift nav in mobile when clicking the menu. 3 | $(document).on('click', "[data-toggle='wy-nav-top']", function() { 4 | $("[data-toggle='wy-nav-shift']").toggleClass("shift"); 5 | $("[data-toggle='rst-versions']").toggleClass("shift"); 6 | }); 7 | // Close menu when you click a link. 8 | $(document).on('click', ".wy-menu-vertical .current ul li a", function() { 9 | $("[data-toggle='wy-nav-shift']").removeClass("shift"); 10 | $("[data-toggle='rst-versions']").toggleClass("shift"); 11 | }); 12 | $(document).on('click', "[data-toggle='rst-current-version']", function() { 13 | $("[data-toggle='rst-versions']").toggleClass("shift-up"); 14 | }); 15 | // Make tables responsive 16 | $("table.docutils:not(.field-list)").wrap("
"); 17 | }); 18 | 19 | window.SphinxRtdTheme = (function (jquery) { 20 | var stickyNav = (function () { 21 | var navBar, 22 | win, 23 | stickyNavCssClass = 'stickynav', 24 | applyStickNav = function () { 25 | if (navBar.height() <= win.height()) { 26 | navBar.addClass(stickyNavCssClass); 27 | } else { 28 | navBar.removeClass(stickyNavCssClass); 29 | } 30 | }, 31 | enable = function () { 32 | applyStickNav(); 33 | win.on('resize', applyStickNav); 34 | }, 35 | init = function () { 36 | navBar = jquery('nav.wy-nav-side:first'); 37 | win = jquery(window); 38 | }; 39 | jquery(init); 40 | return { 41 | enable : enable 42 | }; 43 | }()); 44 | return { 45 | StickyNav : stickyNav 46 | }; 47 | }($)); 48 | -------------------------------------------------------------------------------- /docs/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/minus.png -------------------------------------------------------------------------------- /docs/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/plus.png -------------------------------------------------------------------------------- /docs/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 8 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 12 | .highlight .ge { font-style: italic } /* Generic.Emph */ 13 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 16 | .highlight .go { color: #333333 } /* Generic.Output */ 17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 18 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 26 | .highlight .kt { color: #902000 } /* Keyword.Type */ 27 | .highlight .m { color: #208050 } /* Literal.Number */ 28 | .highlight .s { color: #4070a0 } /* Literal.String */ 29 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 30 | .highlight .nb { color: #007020 } /* Name.Builtin */ 31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 32 | .highlight .no { color: #60add5 } /* Name.Constant */ 33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 35 | .highlight .ne { color: #007020 } /* Name.Exception */ 36 | .highlight .nf { color: #06287e } /* Name.Function */ 37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/docs/_static/up.png -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Definition 2 | ============== 3 | 4 | .. automodule:: griffin 5 | 6 | CLI 7 | --- 8 | 9 | .. option:: build -r RAMLFILE 10 | 11 | Command-line function to build HTML documentation off of \ 12 | a given RAML file. 13 | 14 | .. program:: build 15 | .. option:: -o OUTPUT, --output OUTPUT 16 | 17 | Specify where to output the built HTML files. If not given, \ 18 | then ``output`` from the giving config file will be used. If not \ 19 | defined in output, then a directory named ``output`` will be \ 20 | created in the relative location that the command is ran. 21 | 22 | .. program:: build 23 | .. option:: -c CONFIGFILE, --config CONFIGFILE 24 | 25 | A ``yaml`` configuration file for the documentation \ 26 | generator. See :doc:`config` for more information. 27 | 28 | If no config file is provided, ``griffin`` will look for a file \ 29 | called ``griffin.yaml`` by default within the same directory that \ 30 | the ``build`` command is run. 31 | 32 | .. program:: build 33 | .. option:: -R RAMLCONFIGFILE, --ramlconfig RAMLCONFIGFILE 34 | 35 | A ``ini`` configuration file for parsing RAML. See the `ramlfications \ 36 | documentation \ 37 | `_ \ 38 | for more information. 39 | 40 | .. program:: build 41 | .. option:: -q, --quiet 42 | 43 | Suppress output. 44 | 45 | .. autofunction:: griffin.cli.build 46 | 47 | 48 | Core 49 | ---- 50 | 51 | .. py:class:: griffin.core.APIContext 52 | 53 | Context object of the parsed API. 54 | 55 | .. py:attribute:: api 56 | 57 | The API parsed by `ramlfications `_. 58 | 59 | .. py:attribute:: metadata 60 | 61 | Metadata about the API. 62 | 63 | .. py:attribute:: groupings 64 | 65 | A ``dict`` of groups of endpoints, defined by their parent nodes. For example,:: 66 | 67 | /foo 68 | displayName: Foo 69 | description: a foo endpoint 70 | get: 71 | /{id} 72 | displayName: A single Foo 73 | description: A particular Foo 74 | get: 75 | 76 | There would be a ``foo`` grouping, with the ``foo`` and the \ 77 | ``foo/{id}`` endpoints. 78 | 79 | 80 | .. py:attribute:: endpoints 81 | 82 | A ``list`` of all parsed endpoints. 83 | 84 | .. autofunction:: griffin.core.create_context 85 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.0.1a (2015-07-29) 5 | ------------------- 6 | 7 | Super alpha version! 8 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # griffin documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Sep 11 10:00:00 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import codecs 16 | import datetime 17 | import os 18 | import re 19 | import sys 20 | 21 | try: 22 | import sphinx_rtd_theme 23 | except ImportError: 24 | sphinx_rtd_theme = None 25 | 26 | 27 | def read(*parts): 28 | """ 29 | Build an absolute path from *parts* and and return the contents of the 30 | resulting file. Assume UTF-8 encoding. 31 | """ 32 | here = os.path.abspath(os.path.dirname(__file__)) 33 | with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: 34 | return f.read() 35 | 36 | 37 | def find_version(*file_paths): 38 | """ 39 | Build a path from *file_paths* and search for a ``__version__`` 40 | string inside. 41 | """ 42 | version_file = read(*file_paths) 43 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 44 | version_file, re.M) 45 | if version_match: 46 | return version_match.group(1) 47 | raise RuntimeError("Unable to find version string.") 48 | 49 | 50 | def find_latest_release_date(): 51 | """ 52 | Build a date string from the latest release logged in ``changelog.rst`` 53 | """ 54 | changelog = read("changelog.rst") 55 | # format of either YYYY-MM-DD or YYYY/MM/DD 56 | date_regex = re.compile("(\d{4}[-/]\d{2}[-/]\d{2})") 57 | date_match = date_regex.findall(changelog)[0] 58 | if date_match: 59 | release_date = datetime.datetime.strptime(date_match, "%Y-%m-%d") 60 | fmt_release_date = release_date.strftime("%b %-d, %Y") 61 | return fmt_release_date 62 | raise RuntimeError("Unable to find latest release date in changelog.rst") 63 | 64 | 65 | # If extensions (or modules to document with autodoc) are in another directory, 66 | # add these directories to sys.path here. If the directory is relative to the 67 | # documentation root, use os.path.abspath to make it absolute, like shown here. 68 | sys.path.append(os.path.abspath(os.path.pardir)) 69 | 70 | # -- General configuration ------------------------------------------------ 71 | 72 | # If your documentation needs a minimal Sphinx version, state it here. 73 | #needs_sphinx = '1.0' 74 | 75 | # Add any Sphinx extension module names here, as strings. They can be 76 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 77 | # ones. 78 | extensions = [ 79 | 'sphinx.ext.autodoc', 80 | 'sphinx.ext.doctest', 81 | 'sphinx.ext.todo', 82 | 'sphinx.ext.coverage', 83 | 'sphinx.ext.autosummary', 84 | ] 85 | autodoc_member_order = 'bysource' 86 | autoclass_content = 'class' 87 | 88 | # Add any paths that contain templates here, relative to this directory. 89 | templates_path = ['_templates'] 90 | 91 | # The suffix of source filenames. 92 | source_suffix = '.rst' 93 | 94 | # The encoding of source files. 95 | #source_encoding = 'utf-8-sig' 96 | 97 | # The master toctree document. 98 | master_doc = 'index' 99 | 100 | # General information about the project. 101 | project = u'griffin' 102 | year = datetime.date.today().year 103 | copyright = u'2015{0}, Lynn Root, Spotify AB'.format( 104 | u'-{0}'.format(year) if year != 2014 else u"" 105 | ) 106 | 107 | # The version info for the project you're documenting, acts as replacement for 108 | # |version| and |release|, also used in various other places throughout the 109 | # built documents. 110 | # 111 | # The full version, including alpha/beta/rc tags. 112 | release = find_version("../griffin/__init__.py") 113 | # The short X.Y version. 114 | version = release.rsplit(u".", 1)[0] 115 | 116 | # The language for content autogenerated by Sphinx. Refer to documentation 117 | # for a list of supported languages. 118 | #language = None 119 | 120 | # There are two options for replacing |today|: either, you set today to some 121 | # non-false value, then it is used: 122 | today = find_latest_release_date() 123 | # Else, today_fmt is used as the format for a strftime call. 124 | #today_fmt = '%B %d, %Y' 125 | 126 | # List of patterns, relative to source directory, that match files and 127 | # directories to ignore when looking for source files. 128 | exclude_patterns = ['_build'] 129 | 130 | # The reST default role (used for this markup: `text`) to use for all 131 | # documents. 132 | #default_role = None 133 | 134 | # If true, '()' will be appended to :func: etc. cross-reference text. 135 | #add_function_parentheses = True 136 | 137 | # If true, the current module name will be prepended to all description 138 | # unit titles (such as .. function::). 139 | #add_module_names = True 140 | 141 | # If true, sectionauthor and moduleauthor directives will be shown in the 142 | # output. They are ignored by default. 143 | #show_authors = False 144 | 145 | # The name of the Pygments (syntax highlighting) style to use. 146 | pygments_style = 'sphinx' 147 | 148 | # A list of ignored prefixes for module index sorting. 149 | #modindex_common_prefix = [] 150 | 151 | # If true, keep warnings as "system message" paragraphs in the built documents. 152 | #keep_warnings = False 153 | 154 | 155 | # -- Options for HTML output ---------------------------------------------- 156 | 157 | # The theme to use for HTML and HTML Help pages. See the documentation for 158 | # a list of builtin themes. 159 | if sphinx_rtd_theme: 160 | html_theme = "sphinx_rtd_theme" 161 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 162 | else: 163 | html_theme = "default" 164 | 165 | html_style = "css/custom.css" 166 | 167 | # # on_rtd is whether we are on readthedocs.org 168 | # on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 169 | 170 | # if not on_rtd: # only import and set the theme if we're building docs locally 171 | # import sphinx_rtd_theme 172 | # html_theme = 'sphinx_rtd_theme' 173 | # html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 174 | 175 | 176 | # Theme options are theme-specific and customize the look and feel of a theme 177 | # further. For a list of options available for each theme, see the 178 | # documentation. 179 | #html_theme_options = {} 180 | 181 | # Add any paths that contain custom themes here, relative to this directory. 182 | #html_theme_path = [] 183 | 184 | # The name for this set of Sphinx documents. If None, it defaults to 185 | # " v documentation". 186 | #html_title = None 187 | 188 | # A shorter title for the navigation bar. Default is the same as html_title. 189 | #html_short_title = None 190 | 191 | # The name of an image file (relative to this directory) to place at the top 192 | # of the sidebar. 193 | #html_logo = None 194 | 195 | # The name of an image file (within the static path) to use as favicon of the 196 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 197 | # pixels large. 198 | html_favicon = "_static/favicon.ico" 199 | 200 | # Add any paths that contain custom static files (such as style sheets) here, 201 | # relative to this directory. They are copied after the builtin static files, 202 | # so a file named "default.css" will overwrite the builtin "default.css". 203 | html_static_path = ['_static'] 204 | 205 | # Add any extra paths that contain custom files (such as robots.txt or 206 | # .htaccess) here, relative to this directory. These files are copied 207 | # directly to the root of the documentation. 208 | #html_extra_path = [] 209 | 210 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 211 | # using the given strftime format. 212 | #html_last_updated_fmt = '%b %d, %Y' 213 | 214 | # If true, SmartyPants will be used to convert quotes and dashes to 215 | # typographically correct entities. 216 | #html_use_smartypants = True 217 | 218 | # Custom sidebar templates, maps document names to template names. 219 | #html_sidebars = {} 220 | 221 | # Additional templates that should be rendered to pages, maps page names to 222 | # template names. 223 | #html_additional_pages = {} 224 | 225 | # If false, no module index is generated. 226 | #html_domain_indices = True 227 | 228 | # If false, no index is generated. 229 | #html_use_index = True 230 | 231 | # If true, the index is split into individual pages for each letter. 232 | #html_split_index = False 233 | 234 | # If true, links to the reST sources are added to the pages. 235 | #html_show_sourcelink = True 236 | 237 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 238 | #html_show_sphinx = True 239 | 240 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 241 | #html_show_copyright = True 242 | 243 | # If true, an OpenSearch description file will be output, and all pages will 244 | # contain a tag referring to it. The value of this option must be the 245 | # base URL from which the finished HTML is served. 246 | #html_use_opensearch = '' 247 | 248 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 249 | #html_file_suffix = None 250 | 251 | # Output file base name for HTML help builder. 252 | htmlhelp_basename = 'griffindoc' 253 | 254 | 255 | # -- Options for LaTeX output --------------------------------------------- 256 | 257 | latex_elements = { 258 | # The paper size ('letterpaper' or 'a4paper'). 259 | #'papersize': 'letterpaper', 260 | 261 | # The font size ('10pt', '11pt' or '12pt'). 262 | #'pointsize': '10pt', 263 | 264 | # Additional stuff for the LaTeX preamble. 265 | #'preamble': '', 266 | } 267 | 268 | # Grouping the document tree into LaTeX files. List of tuples 269 | # (source start file, target name, title, 270 | # author, documentclass [howto, manual, or own class]). 271 | latex_documents = [ 272 | ('index', 'griffin.tex', u'griffin Documentation', 273 | u'Lynn Root', 'manual'), 274 | ] 275 | 276 | # The name of an image file (relative to this directory) to place at the top of 277 | # the title page. 278 | #latex_logo = None 279 | 280 | # For "manual" documents, if this is true, then toplevel headings are parts, 281 | # not chapters. 282 | #latex_use_parts = False 283 | 284 | # If true, show page references after internal links. 285 | #latex_show_pagerefs = False 286 | 287 | # If true, show URL addresses after external links. 288 | #latex_show_urls = False 289 | 290 | # Documents to append as an appendix to all manuals. 291 | #latex_appendices = [] 292 | 293 | # If false, no module index is generated. 294 | #latex_domain_indices = True 295 | 296 | 297 | # -- Options for manual page output --------------------------------------- 298 | 299 | # One entry per manual page. List of tuples 300 | # (source start file, name, description, authors, manual section). 301 | man_pages = [ 302 | ('index', 'griffin', u'griffin Documentation', 303 | [u'Lynn Root'], 1) 304 | ] 305 | 306 | # If true, show URL addresses after external links. 307 | #man_show_urls = False 308 | 309 | 310 | # -- Options for Texinfo output ------------------------------------------- 311 | 312 | # Grouping the document tree into Texinfo files. List of tuples 313 | # (source start file, target name, title, author, 314 | # dir menu entry, description, category) 315 | texinfo_documents = [ 316 | ('index', 'griffin', u'griffin Documentation', 317 | u'Lynn Root', 'griffin', 'One line description of project.', 318 | 'Miscellaneous'), 319 | ] 320 | 321 | # Documents to append as an appendix to all manuals. 322 | #texinfo_appendices = [] 323 | 324 | # If false, no module index is generated. 325 | #texinfo_domain_indices = True 326 | 327 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 328 | #texinfo_show_urls = 'footnote' 329 | 330 | # If true, do not generate a @detailmenu in the "Top" node's menu. 331 | #texinfo_no_detailmenu = False 332 | -------------------------------------------------------------------------------- /docs/config.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | You may use a ``yaml`` file to help configure the generation of your \ 5 | documentation. You'll notice without certain fields within your config, 6 | you'll see some attributes titled ``MISSING``. So it may be a good idea \ 7 | to have a config file. 8 | 9 | When running the ``build`` command on the command line, ``griffin`` will \ 10 | automatically look for a ``griffin.yaml`` file within the directory that \ 11 | ``build`` is run. 12 | 13 | You may also provide the ``build`` command with a path to a different \ 14 | config file, via ``-c`` or ``--config``. See :doc:`api` for more details. 15 | 16 | HTML ```` & Metadata 17 | -------------------------- 18 | 19 | ================= =========================================== ======================= ========================== 20 | variable name what used for default 21 | ----------------- ------------------------------------------- ----------------------- -------------------------- 22 | ``title`` ``str`` Site title ``MISSING`` 23 | ``base_url`` ``str`` Relative URL for assets ``http://localhost:8080`` 24 | ``description`` ``str`` Site description ``MISSING`` 25 | ``author`` ``str`` Site Author name ``MISSING`` 26 | ``canonical_url`` URL that site will be deployed to Canonical URL ``MISSING`` 27 | ``favicon`` path to an image relative to ``static_dir`` Favicon default theme img 28 | ================= =========================================== ======================= ========================== 29 | 30 | 31 | Theming 32 | ------- 33 | 34 | =============== =========================================== ======================= ========================== 35 | variable name what used for default 36 | --------------- ------------------------------------------- ----------------------- -------------------------- 37 | ``brand_image`` path to an image relative to ``static_dir`` Navbar Brand image default theme img 38 | ``theme_path`` path to an image relative to ``static_dir`` Location of own theme built-in themes 39 | ``theme_name`` name of them Directory name of theme ``default`` 40 | =============== =========================================== ======================= ========================== 41 | 42 | 43 | Directory Locations 44 | ------------------- 45 | 46 | ================= ================= ====================== ============= 47 | variable name what used for default 48 | ----------------- ----------------- ---------------------- ------------- 49 | ``template_dir`` path to directory Your own templates ``templates`` 50 | ``static_dir`` path to directory Your own static assets ``assets`` 51 | ``output_dir`` path to directory Final HTML output ``output`` 52 | ================= ================= ====================== ============= 53 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to griffin's documentation! 2 | ========================================= 3 | 4 | Release v\ |release| (:doc:`What's new? `). 5 | 6 | 7 | .. include:: intro.rst 8 | :start-after: begin 9 | 10 | User's Guide 11 | ============ 12 | 13 | .. toctree:: 14 | 15 | installation 16 | usage 17 | api 18 | config 19 | 20 | 21 | Project Information 22 | =================== 23 | 24 | .. toctree:: 25 | :maxdepth: 1 26 | :titlesonly: 27 | 28 | contributing 29 | license 30 | changelog 31 | 32 | 33 | Indices and tables 34 | ================== 35 | 36 | * :ref:`genindex` 37 | * :ref:`modindex` 38 | * :ref:`search` 39 | 40 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | :start-after: begin 3 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | :orphan: 2 | 3 | Introduction 4 | ============ 5 | 6 | .. begin 7 | 8 | ``griffin`` is an Apache 2.0-licensed documentation generator written in Python 9 | leveraging the `RAMLfications`_ library to create API docs from `RAML`_. 10 | 11 | If you’ve never heard of `RAML`_, you’re missing out: 12 | 13 | RESTful API Modeling Language (RAML) is a simple and succinct way of describing practically-RESTful APIs. 14 | It encourages reuse, enables discovery and pattern-sharing, and aims for merit-based emergence of best practices. 15 | The goal is to help our current API ecosystem by solving immediate problems and then encourage ever-better API patterns. 16 | RAML is built on broadly-used standards such as YAML and JSON and is a non-proprietary, vendor-neutral open spec. 17 | 18 | 19 | About 20 | ----- 21 | ``griffin``\ ’s documentation lives at `Read the Docs`_, the code on GitHub_. 22 | It’s tested on Python 2.6, 2.7, 3.3+, and PyPy. Both Linux and OS X are supported. 23 | 24 | 25 | .. _`Read the Docs`: https://griffin.readthedocs.org/ 26 | .. _`GitHub`: https://github.com/spotify/griffin/ 27 | .. _`RAML`: http://raml.org 28 | .. _`RAMLfications`: https://ramlfications.readthedocs.org 29 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | License and Hall of Fame 2 | ======================== 3 | 4 | ``griffin`` is licensed under the `Apache 2.0 `_ license. 5 | The full license text can be also found in the `source code repository `_. 6 | 7 | .. _authors: 8 | 9 | .. include:: ../AUTHORS.rst 10 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Usage 3 | ======== 4 | 5 | To use ``griffin``: 6 | 7 | .. code-block:: bash 8 | 9 | $ griffin build -r path/to/api.raml 10 | 11 | With the above example, new directories & HTML files should now live in \ 12 | ``output``. 13 | 14 | To view them, you can simply run the following command within ``output``: 15 | 16 | .. code-block:: bash 17 | 18 | $ python -m SimpleHTTPServer 8080 19 | 20 | 21 | then navigate your browser to ``localhost:8080``. 22 | 23 | 24 | .. note:: 25 | 26 | Make sure the port in the above command matches your config file (if \ 27 | you have one). The default will be ``8080``. 28 | 29 | 30 | For more information about the ``griffin build`` command, continue to :doc:`api`. 31 | -------------------------------------------------------------------------------- /griffin/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | __author__ = 'Lynn Root' 7 | __email__ = 'lynn@spotify.com' 8 | __version__ = '0.0.1.dev1' 9 | 10 | 11 | __email__ = "lynn@spotify.com" 12 | __uri__ = "https://griffin.readthedocs.org" 13 | __description__ = "A RAML documentation generator in Python" 14 | 15 | __license__ = "Apache 2.0" 16 | -------------------------------------------------------------------------------- /griffin/__main__.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright (c) 2015 Spotify AB 4 | 5 | from __future__ import absolute_import, division, print_function 6 | 7 | import click 8 | 9 | from .cli import build as b 10 | from .config import setup_config 11 | from .logger import configure_log 12 | 13 | 14 | @click.group() 15 | def main(): 16 | """The main interface""" 17 | 18 | 19 | @main.command(help="Build documentation given a RAML file.") 20 | @click.option("-r", "--ramlfile", 21 | type=click.Path(dir_okay=False, 22 | exists=True, 23 | resolve_path=True, 24 | readable=True), 25 | default=".") 26 | @click.option("-o", "--output", 27 | type=click.Path(file_okay=False, 28 | resolve_path=True, 29 | writable=True, 30 | exists=False)) 31 | @click.option("-c", "--config", 32 | type=click.Path(exists=True, 33 | dir_okay=False, 34 | readable=True, 35 | resolve_path=True), 36 | default="griffin.yaml") 37 | @click.option("-R", "--ramlconfig", 38 | type=click.Path(exists=True, 39 | dir_okay=False, 40 | readable=True, 41 | resolve_path=True)) 42 | @click.option("-q", "--quiet", is_flag=True) 43 | def build(ramlfile, output, config, ramlconfig, quiet): 44 | """Builds documentation off of a given RAML file.""" 45 | conf = setup_config(config) 46 | configure_log(quiet) 47 | b(ramlfile, conf, output, ramlconfig) 48 | -------------------------------------------------------------------------------- /griffin/_filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import markdown2 7 | 8 | 9 | def dictionary(item): 10 | if isinstance(item, dict): 11 | return True 12 | return False 13 | 14 | 15 | def a_list(item): 16 | if isinstance(item, list): 17 | return True 18 | return False 19 | 20 | 21 | def map_schemes(item): 22 | try: 23 | return { 24 | "oauth_2_0": "OAuth 2.0", 25 | "oauth_1_0": "OAuth 1.0", 26 | "http_basic": "Basic Authentication", 27 | "basic": "Basic Authentication", 28 | "basic_auth": "Basic Authentication", 29 | "basicAuth": "Basic Authentication", 30 | "basicAuthentication": "Basic Authentication", 31 | "http_digest": "Digest Authentication", 32 | "digest": "Digest Authentication", 33 | "digest_auth": "Digest Authentication", 34 | "digestAuth": "Digest Authentication", 35 | "digestAuthentication": "Digest Authentication" 36 | }[item] 37 | except KeyError: 38 | return item 39 | 40 | 41 | def markdown(text): 42 | if text: 43 | return markdown2.markdown(text)[3:-5] 44 | return text 45 | 46 | 47 | def attrs(param): 48 | if param.default and param.default is not None: 49 | return True 50 | if param.minimum and param.minimum is not None: 51 | return True 52 | if param.maximum and param.maximum is not None: 53 | return True 54 | if param.max_length and param.max_length is not None: 55 | return True 56 | if param.min_length and param.min_length is not None: 57 | return True 58 | return False 59 | -------------------------------------------------------------------------------- /griffin/cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import sys 7 | if sys.version_info[0] == 2: 8 | from io import open 9 | 10 | import os 11 | 12 | import jinja2 13 | import markdown2 14 | 15 | from ._filters import dictionary, a_list, map_schemes, markdown, attrs 16 | from .core import create_context 17 | from .utils import _copy_media_files, _pretty_compact_json 18 | 19 | 20 | ##### 21 | # Little helper functions 22 | ##### 23 | 24 | def _create_path_here(fileobj): 25 | here = os.path.dirname(__file__) 26 | return os.path.join(here, fileobj) 27 | 28 | 29 | def _theme_path(rel_directory, config): 30 | if config.get("theme_path"): 31 | _theme_dir = config.get("theme_path") 32 | _theme_name = config.get("theme_name") 33 | _theme_path = os.path.join(_theme_dir, _theme_name) 34 | return os.path.join(rel_directory, _theme_path) 35 | _theme_dir = _create_path_here("themes") 36 | _theme_name = config.get("theme_name") 37 | return os.path.join(_theme_dir, _theme_name) 38 | 39 | 40 | def _template_path(config): 41 | _temp_dir = config.get("template_dir") 42 | return _create_path_here(_temp_dir) 43 | 44 | 45 | def _static_path(rel_directory, config): 46 | _static_dir = config.get("static_dir") 47 | return os.path.join(rel_directory, _static_dir) 48 | 49 | 50 | def _save_template(template, output): 51 | if not os.path.exists(output): 52 | os.makedirs(output) 53 | 54 | save_as = os.path.join(output, "index.html") 55 | with open(save_as, "w", encoding="utf-8") as f: 56 | f.write(template) 57 | 58 | 59 | ##### 60 | # Jinja templating-related 61 | ##### 62 | def _set_jinja_env(template_path): 63 | loader = jinja2.FileSystemLoader(template_path) 64 | env = jinja2.Environment(loader=loader) 65 | env.tests["a_list"] = a_list 66 | env.tests["dictionary"] = dictionary 67 | env.filters["jsonify"] = _pretty_compact_json 68 | env.filters["schemes"] = map_schemes 69 | env.filters["markdown"] = markdown 70 | env.filters["attrs"] = attrs 71 | return env 72 | 73 | 74 | def _create_template(context, env, tmpl_file): 75 | template = env.get_template(tmpl_file) 76 | return template.render(context) 77 | 78 | 79 | def _create_landing_page(index_page, context, env): 80 | with open(index_page, "r") as f: 81 | context["index"] = markdown2.markdown(f.read()) 82 | 83 | return _create_template(context, env, "index.html") 84 | 85 | 86 | def _custom_css(config, template_path, output): 87 | custom_css = config.get("custom_css", {}) 88 | rest_colors = custom_css.get("rest_colors", {}) 89 | navbar_color = custom_css.get("navbar_color", {}) 90 | fonts = custom_css.get("fonts", {}) 91 | context = dict(rest_colors=rest_colors, 92 | navbar_color=navbar_color, 93 | fonts=fonts) 94 | loader = jinja2.FileSystemLoader(template_path) 95 | env = jinja2.Environment(loader=loader) 96 | 97 | _template = env.get_template("custom.css") 98 | template = _template.render(context) 99 | 100 | css_dir = os.path.join(output, "css") 101 | if not os.path.exists(css_dir): 102 | os.makedirs(css_dir) 103 | save_as = os.path.join(css_dir, "custom.css") 104 | 105 | with open(save_as, "w", encoding="utf-8") as f: 106 | f.write(template) 107 | 108 | 109 | # Main build function for CLI 110 | def build(ramlfile, config, output=None, ramlconfig=None): 111 | """ 112 | Builds HTML templates from a RAML file and saves to desired output. 113 | 114 | :param str ramlfile: path to raml file 115 | :param dict config: documentation config 116 | :param str output: output of HTML files 117 | :param str ramlconfig: configuration for RAML parsing. See \ 118 | `ramlfications documentation \ 119 | `_ \ 120 | for more information. 121 | """ 122 | # parse out RAML file & create context for templates 123 | context = create_context(ramlfile, ramlconfig) 124 | site_context = dict(site=config, 125 | meta=context.metadata, 126 | endpoints=context.groupings) 127 | 128 | if not output: 129 | output = config.get("output_dir") 130 | 131 | # set up Jinja templates 132 | template_path = _template_path(config) 133 | env = _set_jinja_env(template_path) 134 | 135 | # create main site template 136 | site_template = _create_template(site_context, env, "base.html") 137 | _save_template(site_template, output) 138 | 139 | # create endpoint templates 140 | for endpoint in context.endpoints: 141 | end_context = dict(site=config, meta=context.metadata, 142 | endpoints=context.groupings, endpoint=endpoint) 143 | endp_template = _create_template(end_context, env, 144 | "endpoint/index.html") 145 | directory = endpoint.get("anchor") 146 | endpoint_out = os.path.join(output, directory) 147 | _save_template(endp_template, endpoint_out) 148 | 149 | # create landing page 150 | rel_directory = os.path.dirname(ramlfile) 151 | index_md = os.path.join(rel_directory, "index.md") 152 | landing_page = _create_landing_page(index_md, site_context, env) 153 | _save_template(landing_page, output) 154 | 155 | # save static/assets 156 | theme_path = _theme_path(rel_directory, config) 157 | static_path = _static_path(rel_directory, config) 158 | assets_path = os.path.join(output, "assets") 159 | 160 | if config.get("custom_css"): 161 | _custom_css(config, template_path, assets_path) 162 | _copy_media_files(theme_path, assets_path) 163 | _copy_media_files(static_path, assets_path) 164 | -------------------------------------------------------------------------------- /griffin/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | from six import iteritems, iterkeys 7 | import yaml 8 | 9 | 10 | class DocConfigError(Exception): 11 | pass 12 | 13 | 14 | DEFAULT_CONFIG = { 15 | "theme_path": None, 16 | "template_dir": "templates", 17 | "static_dir": "assets", 18 | "endpoint_order": "preserve", 19 | "base_url": "http://localhost:8080", 20 | "theme_name": "default", 21 | "title": "MISSING", 22 | "description": "MISSING", 23 | "author": "MISSING", 24 | "canonical_url": "MISSING", 25 | "favicon": "images/default.png", 26 | "brand_image": "images/default.png", 27 | "output_dir": "output" 28 | } 29 | 30 | 31 | def _set_defaults(config): 32 | """Add defaults where custom isn't set""" 33 | for k, v in iteritems(DEFAULT_CONFIG): 34 | if k not in iterkeys(config): 35 | config[k] = v 36 | if config[k] is None: 37 | config[k] = v 38 | return config 39 | 40 | 41 | def setup_config(config_file): 42 | with open(config_file) as c: 43 | config = yaml.load(c) 44 | 45 | config = _set_defaults(config) 46 | return config 47 | -------------------------------------------------------------------------------- /griffin/core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | 7 | import ramlfications 8 | 9 | 10 | from .utils import OrderedDefaultDict as defaultdict # NOQA 11 | 12 | 13 | class APIContext(object): 14 | """ 15 | Context object of the parsed API. 16 | 17 | :param api: The API parsed by \ 18 | `ramlfications `_. 19 | """ 20 | def __init__(self, api): 21 | self.api = api 22 | self.metadata = self._set_metadata() 23 | self.groupings, self.endpoints = self._set_endpoints() 24 | 25 | @staticmethod 26 | def _create_anchor(node): 27 | """ 28 | Cleans up the endpoint.path attribute to create an ``href`` anchor. 29 | 30 | :param obj node: resource node object 31 | :rtype: str 32 | """ 33 | anchor = node.path.lstrip("/") 34 | anchor = anchor.replace("/", "-") 35 | anchor = anchor.replace("_", "-") 36 | anchor = anchor.replace("{", "") 37 | anchor = anchor.replace("}", "") 38 | 39 | if node.method: 40 | anchor = "-".join([node.method, anchor]) 41 | return anchor 42 | 43 | @staticmethod 44 | def _get_parent_node(node): 45 | """ 46 | Returns the parent node of an endpoint, and strips the trailing 47 | ``/``. Default grouping order. 48 | 49 | :param obj node: resource node object 50 | :ret: parent name of the node 51 | :rtype: str 52 | """ 53 | if node.parent: 54 | return APIContext._get_parent_node(node.parent) 55 | return node.name.lstrip("/") 56 | 57 | @staticmethod 58 | def _create_curl_example(node): 59 | """ 60 | Returns complete cURL string using the resource node examples. 61 | :param obj node: resource node object 62 | :ret: example cURL command 63 | :rtype: str 64 | """ 65 | curl_url = node.absolute_uri 66 | q_string = "" 67 | f_string = "" 68 | h_string = "" 69 | if node.query_params: 70 | query_p = [(q.name, q.example) for q in node.query_params] 71 | for q in query_p: 72 | q_string += "{0}={1}".format(q[0], q[1]) 73 | if node.form_params: 74 | form_p = [(f.name, f.example)for f in node.form_params] 75 | for f in form_p: 76 | f_string += " -d {0}={1}".format(f[0], f[1]) 77 | if node.uri_params: 78 | uri_p = [(u.name, u.example) for u in node.uri_params if u.example] 79 | for u in uri_p: 80 | curl_url = curl_url.replace("{" + u[0] + "}", str(u[1])) 81 | if node.headers: 82 | headers = [(h.name, h.example) for h in node.headers] 83 | for h in headers: 84 | h_string += ' -H "{0}: {1}"'.format(h[0], h[1]) 85 | 86 | if q_string: 87 | curl_url = curl_url + "?" + q_string 88 | 89 | if node.secured_by: 90 | secured = ' -H "Authorization: Bearer A-v@1id-o@uth-t0k3n"' 91 | curl_url = '"{0}"'.format(curl_url) + secured 92 | 93 | if h_string: 94 | curl_url = curl_url + h_string 95 | 96 | if f_string: 97 | curl_url = curl_url + f_string 98 | 99 | # what if there are multiple bodies? 100 | if node.body: 101 | body = node.body[0].example 102 | body_mime = node.body[0].mime_type 103 | b_header = ' -H "Content-Type: {0}"'.format(body_mime) 104 | b_data = ' --data "{0}"'.format(body) 105 | curl_url = curl_url + b_header + b_data 106 | 107 | return curl_url 108 | 109 | @staticmethod 110 | def _sort_resp_codes(responses): 111 | """ 112 | Returns response codes in numeric order. 113 | 114 | :param list responses: list of resource-supported responses 115 | :ret: ordered ``list`` of resource param objects 116 | """ 117 | try: 118 | return sorted(responses, key=lambda k: k) 119 | except TypeError: 120 | return responses 121 | 122 | @staticmethod 123 | def _sort_required(params): 124 | """ 125 | Returns the parameters by with required on top, followed by 126 | order within the RAML file. 127 | 128 | :param list params: list of resource param objects 129 | :ret: ordered ``list`` of resource param objects 130 | """ 131 | if params is not None: 132 | ordered_params = [] 133 | for p in params: 134 | if p.required: 135 | ordered_params.append(p) 136 | for p in params: 137 | if p not in ordered_params: 138 | ordered_params.append(p) 139 | return ordered_params 140 | return params 141 | 142 | def _set_metadata(self): 143 | """ 144 | Returns metadata about the API 145 | 146 | :param obj api: parsed RAML API 147 | :rtype: ``dict`` 148 | """ 149 | return dict(title=self.api.title, 150 | version=self.api.version, 151 | protocols=self.api.protocols, 152 | docs=self.api.documentation, 153 | uri=self.api.base_uri, 154 | b_params=self.api.base_uri_params, 155 | u_params=self.api.uri_params, 156 | traits=self.api.traits, 157 | types=self.api.resource_types, 158 | secured=self.api.secured_by, 159 | sec_schemes=self.api.security_schemes, 160 | media_type=self.api.media_type) 161 | 162 | def _set_endpoints(self): 163 | """ 164 | Parses API object into dictionaries for each endpoint and their 165 | respective groupings. 166 | 167 | :param obj api: parsed RAML API 168 | """ 169 | res = self.api.resources 170 | 171 | groupings = defaultdict(list) 172 | endpoints = [] 173 | for r in res: 174 | anchor = self._create_anchor(r) 175 | parent = self._get_parent_node(r) 176 | curl_url = self._create_curl_example(r) 177 | if r.description.raw: 178 | desc = r.description.html 179 | else: 180 | desc = '' 181 | endpoint = dict(anchor=anchor, 182 | curl=curl_url, 183 | display_name=r.display_name, 184 | name=r.name, 185 | path=r.path, 186 | method=r.method, 187 | media_type=r.media_type, 188 | description=desc, 189 | headers=r.headers, 190 | body=r.body, 191 | resp=self._sort_resp_codes(r.responses), 192 | protocols=r.protocols, 193 | q_params=self._sort_required(r.query_params), 194 | f_params=self._sort_required(r.form_params), 195 | u_params=self._sort_required(r.uri_params), 196 | secured=r.secured_by) 197 | endpoints.append(endpoint) 198 | groupings[parent].append(endpoint) 199 | 200 | return groupings, endpoints 201 | 202 | def __repr__(self): 203 | return self.api.title 204 | 205 | 206 | def create_context(ramlfile, ramlconfig=None): 207 | """ 208 | Returns context to populate templates. 209 | 210 | :param str ramlfile: RAML file to parse 211 | :param str ramlconfig: config file for RAML (see ``ramlfications`` \ 212 | `docs `_) 213 | :rtype: :py:class:`.APIContext` object 214 | """ 215 | api = ramlfications.parse(ramlfile, ramlconfig) 216 | 217 | return APIContext(api) 218 | -------------------------------------------------------------------------------- /griffin/logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | 7 | import logging 8 | 9 | 10 | # TODO: colorize logs 11 | class ColorLogFormat(logging.Formatter): 12 | pass 13 | 14 | 15 | def configure_log(quiet): 16 | """ 17 | If `-q | --quiet` is passed into the command line, set logger 18 | to only return warnings and higher. Otherwise, set logger level to 19 | info and higher. 20 | """ 21 | logger = logging.getLogger('griffin') 22 | sh = logging.StreamHandler() 23 | formatter = logging.Formatter('%(levelname)s - %(message)s') 24 | sh.setFormatter(formatter) 25 | logger.addHandler(sh) 26 | if quiet: 27 | logger.setLevel(logging.WARNING) 28 | else: 29 | logger.setLevel(logging.INFO) 30 | -------------------------------------------------------------------------------- /griffin/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% if site.title %}{{ site.title }} - {% endif %}{{ meta.title }} 9 | 10 | 11 | 12 | 13 | {% if site.description %}{% endif %} 14 | {% if site.author %}{% endif %} 15 | {% if site.canonical_url %}{% endif %} 16 | {% if site.favicon %} 17 | {% else %}{% endif %} 18 | 19 | 20 | 21 | 22 | {% include "navbar.html" %} 23 |
24 |
25 | 26 |
{% block documentation %}{% endblock %}
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 43 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /griffin/templates/collection_sidebar.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /griffin/templates/custom.css: -------------------------------------------------------------------------------- 1 | /******************* 2 | REST specific 3 | ********************/ 4 | 5 | 6 | #method-get { 7 | background-color: {{ rest_colors["get"] }}; 8 | } 9 | 10 | #method-post { 11 | background-color: {{ rest_colors.post }} ; 12 | } 13 | 14 | #method-delete { 15 | background-color: {{ rest_colors.delete }}; 16 | } 17 | 18 | #method-put { 19 | background-color: {{ rest_colors.put }}; 20 | } 21 | 22 | #method-head { 23 | background-color: {{ rest_colors.head }}; 24 | } 25 | 26 | .#method-patch { 27 | background-color: {{ rest_colors.patch }}; 28 | } 29 | 30 | #method-options { 31 | background-color: {{ rest_colors.options }}; 32 | } 33 | 34 | #method-trace { 35 | background-color: {{ rest_colors.trace }}; 36 | } 37 | 38 | #method-connect { 39 | background-color: {{ rest_colors.connect }}; 40 | } 41 | 42 | 43 | /************************** 44 | Simple theme-y stuff 45 | **************************/ 46 | 47 | @font-face { 48 | font-family: {{ fonts.body.name }}; 49 | src: url("{{ fonts.body.path }}") format("{{ fonts.body.type }}"); 50 | } 51 | 52 | @font-face { 53 | font-family: {{ fonts.headers.name }}; 54 | src: url("{{ fonts.headers.path}}") format("{{ fonts.headers.type }}"); 55 | } 56 | 57 | #grif-navbar { 58 | background-color: {{ navbar_color }}; 59 | color: #fff; 60 | } 61 | 62 | body { 63 | font-family: {{ fonts.body.name }}; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | font-family: {{ fonts.headers.name }}; 68 | } 69 | -------------------------------------------------------------------------------- /griffin/templates/documentation.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" } 2 | 3 | {% include "endpoint/index.html" with context %} 4 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/body.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
request body
4 | {% for b in endpoint.body %} 5 |
6 |
mime type
7 |
{{ b.mime_type }}
8 | {% if b.schema is not none %} 9 |
schema
10 |
11 |
12 | 15 |
16 |
{{ b.schema|jsonify|e }}
17 |
18 | {% endif %} 19 | 20 | {% endfor %} 21 |
22 |
23 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/example.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
example
4 |
5 |
request
6 | 7 | 8 |
curl -X {{ endpoint.method|upper }} {{ endpoint.curl }}
9 | 10 | {% if endpoint.resp %} 11 | {% if endpoint.resp[0].body %} 12 | {% if endpoint.resp[0].body[0].example is not none %} 13 |
response
14 |
15 |
16 | 19 |
20 |
{{ endpoint.resp[0].body[0].example|jsonify|e }}
21 | {% endif %} 22 | {% endif %} 23 | {% endif %} 24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/form_params.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
form parameters
4 | {% set params = endpoint.f_params %} 5 | {% include "endpoint/params.html" %} 6 |
7 |
8 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/general.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{ endpoint.display_name }}

4 |
5 |
6 |
7 |
8 |
method
9 |
{{ endpoint.method|upper }}
10 |
11 |
12 |
13 |
14 |
path
15 |
{{ endpoint.path }}
16 |
17 |
18 |
19 |
20 |
description
21 |
{{ endpoint.description }}
22 |
23 |
24 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/headers.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
request headers
4 |
5 | {% for h in endpoint.headers %} 6 |
{{ h.name }}
7 |
{{ h.description.raw|markdown }}
8 | {% endfor %} 9 |
10 |
11 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block documentation %} 4 | {% include "endpoint/general.html" %} 5 | 6 | {# authentication #} 7 | {% if endpoint.secured %} 8 | {% include "endpoint/secured.html" %} 9 | {% endif %} 10 | 11 | {# parameters #} 12 | {% if endpoint.u_params %} 13 | {% include "endpoint/uri_params.html" %} 14 | {% endif %} 15 | {% if endpoint.q_params %} 16 | {% include "endpoint/query_params.html" %} 17 | {% endif %} 18 | {% if endpoint.f_params %} 19 | {% include "endpoint/form_params.html" %} 20 | {% endif %} 21 | {% if endpoint.headers %} 22 | {% include "endpoint/headers.html" %} 23 | {% endif %} 24 | {% if endpoint.body %} 25 | {% include "endpoint/body.html" %} 26 | {% endif %} 27 | {% if endpoint.resp %} 28 | {% include "endpoint/responses.html" %} 29 | {% endif %} 30 |
31 | {% include "endpoint/example.html" %} 32 | {% endblock %} 33 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/params.html: -------------------------------------------------------------------------------- 1 | {% for p in params %} 2 |
3 | {% if p.required %} 4 |
5 | {{ p.name }} 6 |
7 | {% else %} 8 |
{{ p.name }}
9 | {% endif %} 10 | {% if p.description.raw is not none %} 11 |
{{ p.description.raw|markdown }}
12 | {% else %} 13 |
{{ p.display_name }}
14 | {% endif %} 15 | {% if p|attrs %} 16 |
17 | 18 | {% if p.default and p.default is not none %}{% endif %} 19 | {% if p.minimum and p.minimum is not none %}{% endif %} 20 | {% if p.maximum and p.maximum is not none %}{% endif %} 21 | {% if p.minimum_length and p.minimum_length is not none %}{% endif %} 22 | {% if p.maximum_length and p.maximum_length is not none %}{% endif %} 23 |
default{{ p.default }}
minimum{{ p.minimum }}
maximum{{ p.maximum }}
min length{{ p.minimum_length }}
max length{{ p.maximum_length }}
24 |
25 | {% endif %} 26 | 27 | {% endfor %} 28 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/query_params.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
query parameters
4 | {% set params = endpoint.q_params %} 5 | {% include "endpoint/params.html" %} 6 |
7 |
8 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/responses.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
responses
4 | {% for r in endpoint.resp %} 5 |
6 |
http code
7 |
{{ r.code }}
8 | {% if r.description.raw is not none %} 9 |
description
10 |
{{ r.description.raw|markdown }}
11 | {% endif %} 12 | {% if r.body %} 13 |
mime type
14 |
{{ r.body[0].mime_type }}
15 | {% if r.body[0].schema is not none %} 16 |
schema
17 |
18 |
19 | 22 |
23 |
{{ r.body[0].schema|jsonify|e }}
24 | {% endif %} 25 | {% endif %} 26 | 27 | {% if not loop.last and loop.length > 1 %} 28 |
29 |

30 | {% endif %} 31 | {% endfor %} 32 |
33 |
34 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/secured.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
secured by
4 | {% for scheme in endpoint.secured %} 5 | {% if scheme is none %} 6 | None 7 | {% elif scheme is string %} 8 | {{ scheme }} 9 | {% elif scheme is dictionary %} 10 | {% for key, value in scheme.iteritems() %} 11 | 12 | {{ key|schemes }} {% if value is not dictionary %}{{ value }}{% endif %} 13 | {% if value is dictionary %} 14 | {% for k, v in value.iteritems() %} 15 | {% if k == 'scopes' and v %} 16 |
scopes
17 | {% for scope in v %}
{{ scope }}
{% endfor %} 18 | {% endif %} 19 | {% endfor %} 20 | {% endif %} 21 | 22 | {% endfor %} 23 | {% elif scheme is a_list %} 24 | {% for item in endpoint.secured %} 25 |
{{ item }}
26 | {% endfor %} 27 | {% endif %} 28 | {% endfor %} 29 |
30 |
31 | -------------------------------------------------------------------------------- /griffin/templates/endpoint/uri_params.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
uri parameters
4 | {% set params = endpoint.u_params %} 5 | {% include "endpoint/params.html" %} 6 |
7 |
8 | -------------------------------------------------------------------------------- /griffin/templates/head.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/griffin/templates/head.html -------------------------------------------------------------------------------- /griffin/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block documentation %} 4 | {{ index }} 5 | {% endblock %} 6 | -------------------------------------------------------------------------------- /griffin/templates/navbar.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /griffin/templates/scripts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 22 | -------------------------------------------------------------------------------- /griffin/themes/default/css/default.css: -------------------------------------------------------------------------------- 1 | /********************************** 2 | Default Griffin Theme 3 | **********************************/ 4 | 5 | /* custom colors and shiz */ 6 | @import url("custom.css"); 7 | 8 | /*************** 9 | General 10 | ****************/ 11 | 12 | .site-content { 13 | flex: 1; 14 | padding: 40px; 15 | padding-bottom: 100px; 16 | } 17 | 18 | .main { 19 | padding: 20px; 20 | padding-top: 50px; 21 | } 22 | @media (min-width: 768px) { 23 | .main { 24 | padding-right: 40px; 25 | padding-left: 40px; 26 | } 27 | } 28 | 29 | 30 | /*************** 31 | Navbar 32 | ****************/ 33 | 34 | .navbar { 35 | margin-bottom: 0; 36 | border: 0; 37 | } 38 | 39 | .navbar-logo > img { 40 | height: 30px; 41 | padding-left: 5px; 42 | } 43 | 44 | .navbar-brand { 45 | height: inherit; 46 | } 47 | 48 | .navbar a { 49 | color: #fff; 50 | } 51 | 52 | /*************** 53 | Sidebar 54 | ****************/ 55 | /* Hide for mobile, show later */ 56 | .sidebar { 57 | display: none; 58 | } 59 | @media (min-width: 768px) { 60 | .sidebar { 61 | position: fixed; 62 | top: 51px; 63 | bottom: 0; 64 | left: 0; 65 | z-index: 1000; 66 | display: inline-block; 67 | padding: 20px; 68 | overflow-x: hidden; 69 | overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */ 70 | background-color: #fff; 71 | border: 0; 72 | } 73 | } 74 | 75 | /* Sidebar navigation */ 76 | .nav-sidebar { 77 | margin-right: -21px; /* 20px padding + 1px border */ 78 | margin-bottom: 20px; 79 | } 80 | .nav-sidebar > h5 { 81 | padding-left: 5px; 82 | } 83 | .nav-sidebar > li > a { 84 | padding-right: 20px; 85 | padding-left: 20px; 86 | padding-bottom: 0; 87 | padding-top: 0; 88 | } 89 | .nav-sidebar > .active > a, 90 | .nav-sidebar > .active > a:hover, 91 | .nav-sidebar > .active > a:focus { 92 | color: #000; 93 | } 94 | 95 | .sidebar-header { 96 | font-variant: small-caps; 97 | } 98 | 99 | .nav > li { 100 | padding-left: 20px; 101 | } 102 | 103 | .nav > li > a { 104 | display: inline-flex; 105 | padding-left: 5px; 106 | } 107 | 108 | .label { 109 | min-width: 30px; 110 | padding-bottom: .2em; 111 | } 112 | 113 | 114 | 115 | /*************** 116 | Misc 117 | ****************/ 118 | 119 | .param { 120 | margin-top: 0; 121 | margin-bottom: 5px; 122 | } 123 | 124 | .horiz-padding { 125 | margin-bottom: 5px; 126 | } 127 | 128 | .param-desc { 129 | margin-left: 3%; 130 | padding-bottom: 5px; 131 | } 132 | #table-param-attr { 133 | margin-top: 4px; 134 | margin-left: 10px; 135 | } 136 | 137 | #griffin-body { 138 | margin-top: 55px; 139 | } 140 | 141 | #griffin-required { 142 | font-weight: 300; 143 | color: #FF6437; 144 | } 145 | 146 | 147 | /*Hacky way to make sure anchors are pushed down because of the sticky navbar*/ 148 | h1[id], h2[id], h3[id], h4[id], h5[id] { 149 | padding-top: 100px; 150 | margin-top: -100px; 151 | display: inline-block; /* required for webkit browsers */ 152 | } 153 | 154 | 155 | .pre-x-scrollable { 156 | overflow: auto; 157 | word-wrap: normal; 158 | white-space: pre; 159 | padding-right: 10px; 160 | } 161 | 162 | .pre-x-scrollable code { 163 | white-space: pre; 164 | } 165 | 166 | .dl-horizontal > dt { 167 | font-variant: small-caps; 168 | font-size: 18px; 169 | } 170 | 171 | .vert-padding { 172 | padding-top: 5px; 173 | } 174 | 175 | 176 | /* curl request example */ 177 | .kbd { 178 | color: #eaeaea; 179 | background-color: #5a5a5a; 180 | border-color: #3b3b3b; 181 | font-size: 13px; 182 | white-space: normal; 183 | } 184 | 185 | 186 | .kbd > .hljs { 187 | color: #fff; 188 | } 189 | 190 | pre.kbd > code > span.hljs-string { 191 | color: #19E68C; 192 | } 193 | 194 | 195 | .example { 196 | overflow-y: auto; 197 | } 198 | 199 | 200 | dt.second-level { 201 | text-transform: uppercase; 202 | font-weight: 200; 203 | font-size: 85%; 204 | margin-top: 2px; 205 | } 206 | 207 | dd.second-level { 208 | margin-bottom: 15px; 209 | } 210 | 211 | code { 212 | border: 1px solid #ccc; 213 | } 214 | 215 | pre code { 216 | border: 0; 217 | } 218 | 219 | 220 | dt.third-level { 221 | margin-left: 100px; 222 | text-transform: uppercase; 223 | font-weight: 500; 224 | font-size: 12px; 225 | } 226 | 227 | dd.third-level { 228 | padding-left: 90px; 229 | font-size: 12px; 230 | font-weight: 200; 231 | } 232 | 233 | 234 | /* Param attrs */ 235 | 236 | td.param-name { 237 | text-align: right; 238 | padding-right: 8px; 239 | line-height: 1.8; 240 | font-variant: small-caps; 241 | font-size: 110%; 242 | font-weight: 400; 243 | } 244 | 245 | td.param-value { 246 | padding-left: 8px; 247 | padding-top: 4px; 248 | padding-bottom: 4px; 249 | } 250 | 251 | 252 | .collapse-code { 253 | display: block; 254 | position: relative; 255 | color: #000; 256 | background-color: #eee; 257 | z-index: 10; 258 | left: -68px; 259 | padding: 5px 6px 4px 7px; 260 | margin: 0; 261 | cursor: pointer; 262 | border-radius: 0 4px 0 4px; 263 | border: 1px solid #bbb; 264 | font-size: 11px; 265 | text-transform: uppercase; 266 | letter-spacing: 1.5px; 267 | } 268 | 269 | 270 | .collapse { 271 | display: block; 272 | } 273 | 274 | .collapse.in { 275 | display: block; 276 | } 277 | 278 | .toggle-code-wrapper { 279 | display: block; 280 | position: relative; 281 | } 282 | 283 | .toggle-code { 284 | position: absolute; 285 | right: 0; 286 | top: 0; 287 | color: #000; 288 | background-color: #eee; 289 | z-index: 10; 290 | padding: 5px 6px 4px 7px; 291 | margin: 0; 292 | cursor: pointer; 293 | border-radius: 0 4px 0 4px; 294 | border: 1px solid #bbb; 295 | font-size: 11px; 296 | text-transform: uppercase; 297 | letter-spacing: 1.5px; 298 | } 299 | -------------------------------------------------------------------------------- /griffin/themes/default/css/hljs.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | } 10 | 11 | .hljs, 12 | .hljs-subst, 13 | .hljs-tag .hljs-title, 14 | .lisp .hljs-title, 15 | .clojure .hljs-built_in, 16 | .nginx .hljs-title { 17 | color: #2E2F33; /* sp-bootstrap: @gray-darkest, @text-color */ 18 | } 19 | 20 | .hljs-string, 21 | .hljs-title, 22 | .hljs-constant, 23 | .hljs-parent, 24 | .hljs-tag .hljs-value, 25 | .hljs-rules .hljs-value, 26 | .hljs-rules .hljs-value .hljs-number, 27 | .hljs-preprocessor, 28 | .hljs-pragma, 29 | .haml .hljs-symbol, 30 | .ruby .hljs-symbol, 31 | .ruby .hljs-symbol .hljs-string, 32 | .hljs-aggregate, 33 | .hljs-template_tag, 34 | .django .hljs-variable, 35 | .smalltalk .hljs-class, 36 | .hljs-addition, 37 | .hljs-flow, 38 | .hljs-stream, 39 | .bash .hljs-variable, 40 | .apache .hljs-tag, 41 | .apache .hljs-cbracket, 42 | .tex .hljs-command, 43 | .tex .hljs-special, 44 | .erlang_repl .hljs-function_or_atom, 45 | .asciidoc .hljs-header, 46 | .markdown .hljs-header, 47 | .coffeescript .hljs-attribute { 48 | color: #509BF5; 49 | } 50 | 51 | .smartquote, 52 | .hljs-comment, 53 | .hljs-annotation, 54 | .hljs-template_comment, 55 | .diff .hljs-header, 56 | .hljs-chunk, 57 | .asciidoc .hljs-blockquote, 58 | .markdown .hljs-blockquote { 59 | color: #888; 60 | } 61 | 62 | .hljs-number, 63 | .hljs-date, 64 | .hljs-regexp, 65 | .hljs-literal, 66 | .hljs-hexcolor, 67 | .smalltalk .hljs-symbol, 68 | .smalltalk .hljs-char, 69 | .go .hljs-constant, 70 | .hljs-change, 71 | .lasso .hljs-variable, 72 | .makefile .hljs-variable, 73 | .asciidoc .hljs-bullet, 74 | .markdown .hljs-bullet, 75 | .asciidoc .hljs-link_url, 76 | .markdown .hljs-link_url { 77 | color: #BC3200; /* sp-bootstrap: @brand-quaternary */ 78 | } 79 | 80 | .hljs-label, 81 | .hljs-javadoc, 82 | .ruby .hljs-string, 83 | .hljs-decorator, 84 | .hljs-filter .hljs-argument, 85 | .hljs-localvars, 86 | .hljs-array, 87 | .hljs-attr_selector, 88 | .hljs-important, 89 | .hljs-pseudo, 90 | .hljs-pi, 91 | .haml .hljs-bullet, 92 | .hljs-doctype, 93 | .hljs-deletion, 94 | .hljs-envvar, 95 | .hljs-shebang, 96 | .apache .hljs-sqbracket, 97 | .nginx .hljs-built_in, 98 | .tex .hljs-formula, 99 | .erlang_repl .hljs-reserved, 100 | .hljs-prompt, 101 | .asciidoc .hljs-link_label, 102 | .markdown .hljs-link_label, 103 | .vhdl .hljs-attribute, 104 | .clojure .hljs-attribute, 105 | .asciidoc .hljs-attribute, 106 | .lasso .hljs-attribute, 107 | .coffeescript .hljs-property, 108 | .hljs-phony { 109 | color: #00778f; /* sp-bootstrap: @brand-secondary */ 110 | } 111 | 112 | .hljs-keyword, 113 | .hljs-id, 114 | .hljs-title, 115 | .hljs-built_in, 116 | .hljs-aggregate, 117 | .css .hljs-tag, 118 | .hljs-javadoctag, 119 | .hljs-phpdoc, 120 | .hljs-yardoctag, 121 | .smalltalk .hljs-class, 122 | .hljs-winutils, 123 | .bash .hljs-variable, 124 | .apache .hljs-tag, 125 | .go .hljs-typename, 126 | .tex .hljs-command, 127 | .asciidoc .hljs-strong, 128 | .markdown .hljs-strong, 129 | .hljs-request, 130 | .hljs-status { 131 | font-weight: bold; 132 | } 133 | 134 | .asciidoc .hljs-emphasis, 135 | .markdown .hljs-emphasis { 136 | font-style: italic; 137 | } 138 | 139 | .nginx .hljs-built_in { 140 | font-weight: normal; 141 | } 142 | 143 | .coffeescript .javascript, 144 | .javascript .xml, 145 | .lasso .markup, 146 | .tex .hljs-formula, 147 | .xml .javascript, 148 | .xml .vbscript, 149 | .xml .css, 150 | .xml .hljs-cdata { 151 | opacity: 0.5; 152 | } 153 | -------------------------------------------------------------------------------- /griffin/themes/default/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/griffin/themes/default/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /griffin/themes/default/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/griffin/themes/default/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /griffin/themes/default/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/griffin/themes/default/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /griffin/themes/default/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/griffin/themes/default/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /griffin/themes/default/js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | var hljs=new function(){function k(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function i(w,x){var v=w&&w.exec(x);return v&&v.index==0}function d(v){return Array.prototype.map.call(v.childNodes,function(w){if(w.nodeType==3){return b.useBR?w.nodeValue.replace(/\n/g,""):w.nodeValue}if(t(w)=="br"){return"\n"}return d(w)}).join("")}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^language-/,"")});return v.filter(function(x){return j(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=k(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+k(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};function E(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})}if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b=D.bK.split(" ").join("|")}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?\\b("+F.b+")\\b\\.?":F.b}).concat([D.tE]).concat([D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T";U+=Z+'">';return U+X+Y}function N(){var U=k(C);if(!I.k){return U}var T="";var X=0;I.lR.lastIndex=0;var V=I.lR.exec(U);while(V){T+=U.substr(X,V.index-X);var W=E(I,V);if(W){H+=W[1];T+=w(W[0],V[0])}else{T+=V[0]}X=I.lR.lastIndex;V=I.lR.exec(U)}return T+U.substr(X)}function F(){if(I.sL&&!f[I.sL]){return k(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):g(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=k(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=k(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"")+'"')}C+=X;return X.length||1}var M=j(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:k(L)}}else{throw O}}}function g(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:k(y)};var w=v;x.forEach(function(z){if(!j(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function h(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"
")}return v}function p(z){var y=d(z);var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):g(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=h(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function e(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function j(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=g;this.fixMarkup=h;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=e;this.getLanguage=j;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.CLCM={cN:"comment",b:"//",e:"$"};this.CBLCLM={cN:"comment",b:"/\\*",e:"\\*/"};this.HCM={cN:"comment",b:"#",e:"$"};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.REGEXP_MODE={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("xml",function(a){var c="[A-Za-z0-9\\._:-]+";var d={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/]+/}]}]}]};return{aliases:["html"],cI:true,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true,}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("http",function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}}); 2 | -------------------------------------------------------------------------------- /griffin/themes/default/js/jquery.linkify-1.0.js: -------------------------------------------------------------------------------- 1 | /* encoding: utf-8 2 | 3 | **** linkify plugin for jQuery - automatically finds and changes URLs in text content into proper hyperlinks **** 4 | 5 | Version: 1.0 6 | 7 | Copyright (c) 2009 8 | Már Örlygsson (http://mar.anomy.net/) & 9 | Hugsmiðjan ehf. (http://www.hugsmidjan.is) 10 | 11 | Dual licensed under a MIT licence (http://en.wikipedia.org/wiki/MIT_License) 12 | and GPL 2.0 or above (http://www.gnu.org/licenses/old-licenses/gpl-2.0.html). 13 | 14 | ----------------------------------------------------------------------------- 15 | 16 | Demo and Qunit-tests: 17 | * <./jquery.linkify-1.0-demo.html> 18 | * <./jquery.linkify-1.0-test.html> 19 | 20 | Documentation: 21 | * ... 22 | 23 | Get updates from: 24 | * 25 | * 26 | 27 | ----------------------------------------------------------------------------- 28 | 29 | Requires: 30 | * jQuery (1.2.6 or later) 31 | 32 | Usage: 33 | 34 | jQuery('.articlebody').linkify(); 35 | 36 | // adding plugins: 37 | jQuery.extend( jQuery.fn.linkify.plugins, { 38 | name1: { 39 | re: RegExp 40 | tmpl: String/Function 41 | }, 42 | name2: function(html){ return html; } 43 | }); 44 | 45 | // Uses all plugins by default: 46 | jQuery('.articlebody').linkify(); 47 | 48 | // Use only certain plugins: 49 | jQuery('.articlebody').linkify( 'name1,name2' ); 50 | jQuery('.articlebody').linkify({ use: 'name1,name2' }); 51 | jQuery('.articlebody').linkify({ use: ['name1','name2'] }); 52 | 53 | // Explicitly use all plugins: 54 | jQuery('.articlebody').linkify('*'); 55 | jQuery('.articlebody').linkify({ use: '*' }); 56 | jQuery('.articlebody').linkify({ use: ['*'] }); 57 | 58 | // Use no plugins: 59 | jQuery('.articlebody').linkify(''); 60 | jQuery('.articlebody').linkify({ use: '' }); 61 | jQuery('.articlebody').linkify({ use: [] }); 62 | jQuery('.articlebody').linkify({ use: [''] }); 63 | 64 | // Perfmorm actions on all newly created links: 65 | jQuery('.articlebody').linkify( function (links){ links.addClass('linkified'); } ); 66 | jQuery('.articlebody').linkify({ handleLinks: function (links){ links.addClass('linkified'); } }); 67 | 68 | */ 69 | 70 | (function($){ 71 | 72 | var noProtocolUrl = /(^|["'(\s]|<)(www\..+?\..+?)((?:[:?]|\.+)?(?:\s|$)|>|[)"',])/g, 73 | httpOrMailtoUrl = /(^|["'(\s]|<)((?:(?:https?|ftp):\/\/|mailto:).+?)((?:[:?]|\.+)?(?:\s|$)|>|[)"',])/g, 74 | linkifier = function ( html ) { 75 | return html 76 | .replace( noProtocolUrl, '$1$2$3' ) // NOTE: we escape `"http` as `"<``>` to make sure `httpOrMailtoUrl` below doesn't find it as a false-positive 77 | .replace( httpOrMailtoUrl, '$1$2$3' ) 78 | .replace( /"<``>/g, '"http' ); // reinsert `"http` 79 | }, 80 | 81 | 82 | linkify = $.fn.linkify = function ( cfg ) { 83 | if ( !$.isPlainObject( cfg ) ) 84 | { 85 | cfg = { 86 | use: (typeof cfg == 'string') ? cfg : undefined, 87 | handleLinks: $.isFunction(cfg) ? cfg : arguments[1] 88 | }; 89 | } 90 | var use = cfg.use, 91 | allPlugins = linkify.plugins || {}, 92 | plugins = [linkifier], 93 | tmpCont, 94 | newLinks = [], 95 | callback = cfg.handleLinks; 96 | if ( use == undefined || use == '*' ) // use === undefined || use === null 97 | { 98 | for ( var name in allPlugins ) 99 | { 100 | plugins.push( allPlugins[name] ); 101 | } 102 | } 103 | else 104 | { 105 | use = $.isArray( use ) ? use : $.trim(use).split( / *, */ ); 106 | var plugin, 107 | name; 108 | for ( var i=0, l=use.length; i1 && /\S/.test(html) ) 129 | { 130 | var htmlChanged, 131 | preHtml; 132 | tmpCont = tmpCont || $('
')[0]; 133 | tmpCont.innerHTML = ''; 134 | tmpCont.appendChild( n.cloneNode(false) ); 135 | var tmpContNodes = tmpCont.childNodes; 136 | 137 | for (var j=0, plugin; (plugin = plugins[j]); j++) 138 | { 139 | var k = tmpContNodes.length, 140 | tmpNode; 141 | while ( k-- ) 142 | { 143 | tmpNode = tmpContNodes[k]; 144 | if ( tmpNode.nodeType == 3 ) 145 | { 146 | html = tmpNode.nodeValue; 147 | if ( html.length>1 && /\S/.test(html) ) 148 | { 149 | preHtml = html; 150 | html = html 151 | .replace( /&/g, '&' ) 152 | .replace( //g, '>' ); 154 | html = $.isFunction( plugin ) ? 155 | plugin( html ): 156 | html.replace( plugin.re, plugin.tmpl ); 157 | htmlChanged = htmlChanged || preHtml!=html; 158 | preHtml!=html && $(tmpNode).after(html).remove(); 159 | } 160 | } 161 | } 162 | } 163 | html = tmpCont.innerHTML; 164 | if ( callback ) 165 | { 166 | html = $('
').html(html); 167 | //newLinks.push.apply( newLinks, html.find('a').toArray() ); 168 | newLinks = newLinks.concat( html.find('a').toArray().reverse() ); 169 | html = html.contents(); 170 | } 171 | htmlChanged && $(n).after(html).remove(); 172 | } 173 | } 174 | else if ( n.nodeType == 1 && !/^(a|button|textarea)$/i.test(n.tagName) ) 175 | { 176 | arguments.callee.call( n ); 177 | } 178 | }; 179 | }); 180 | callback && callback( $(newLinks.reverse()) ); 181 | return this; 182 | }; 183 | 184 | linkify.plugins = { 185 | // default mailto: plugin 186 | mailto: { 187 | re: /(^|["'(\s]|<)([^"'(\s&]+?@.+\.[a-z]{2,7})(([:?]|\.+)?(\s|$)|>|[)"',])/gi, 188 | tmpl: '$1$2$3' 189 | } 190 | }; 191 | 192 | })(jQuery); 193 | -------------------------------------------------------------------------------- /griffin/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | from collections import OrderedDict 7 | import os 8 | import shutil 9 | 10 | 11 | class OrderedDefaultDict(OrderedDict): 12 | """ 13 | Creates an ordered ``defaultdict``. 14 | """ 15 | def __init__(self, *args, **kwargs): 16 | if not args: 17 | self.default_factory = None 18 | else: 19 | if not (args[0] is not None or callable(args[0])): 20 | raise TypeError("First argument must be callable or None") 21 | self.default_factory = args[0] 22 | args = args[1:] 23 | super(OrderedDefaultDict, self).__init__(*args, **kwargs) 24 | 25 | def __missing__(self, key): 26 | if self.default_factory is None: 27 | raise KeyError(key) 28 | self[key] = default = self.default_factory() 29 | return default 30 | 31 | 32 | def _pretty_compact_json(obj, indent=2, max_strlen=20): 33 | """ 34 | Converts objects to stringified JSON with compact arrays. 35 | Will flatten array items onto the same line if: 36 | - items are all integers, or 37 | - items are all floats, or 38 | - items are all strings less than max_strlen characters. 39 | """ 40 | 41 | space = " " 42 | newline = "\n" 43 | 44 | def to_json(o, level=0): 45 | ret = "" 46 | if isinstance(o, list): 47 | # Compact lists 48 | if all(isinstance(item, int) for item in o): 49 | ret += "[" + ", ".join(map(str, o)) + "]" 50 | elif all(isinstance(item, float) for item in o): 51 | ret += "[" + ", ".join(map(lambda x: '%.7g' % x, o)) + "]" 52 | elif all((isinstance(item, basestring) and 53 | len(item) < max_strlen) for item in o): 54 | ret += "[" + ", ".join(map(lambda x: '"%s"' % x, o)) + "]" 55 | # Expanded lists 56 | else: 57 | ret += "[" + newline 58 | comma = "" 59 | for v in o: 60 | ret += comma 61 | comma = ",\n" 62 | ret += space * indent * (level + 1) 63 | ret += to_json(v, level + 1) 64 | ret += newline + space * indent * level + "]" 65 | # The rest is conventional JSON pretty-print 66 | elif isinstance(o, dict): 67 | ret += "{" + newline 68 | comma = "" 69 | for k, v in o.iteritems(): 70 | ret += comma 71 | comma = ",\n" 72 | ret += space * indent * (level + 1) 73 | ret += '"' + str(k) + '":' + space 74 | ret += to_json(v, level + 1) 75 | ret += newline + space * indent * level + "}" 76 | elif isinstance(o, basestring): 77 | ret += '"' + o + '"' 78 | elif isinstance(o, bool): 79 | ret += "true" if o else "false" 80 | elif isinstance(o, int): 81 | ret += str(o) 82 | elif isinstance(o, float): 83 | ret += '%.7g' % o 84 | elif o is None: 85 | ret += "null" 86 | else: 87 | msg = "Unknown type '{0}' for json serialization".format(type(o)) 88 | raise TypeError(msg) 89 | return ret 90 | 91 | return to_json(obj) 92 | 93 | 94 | def _is_markdown_file(path): 95 | """ 96 | Return ``True`` if the given file is a Markdown file. 97 | """ 98 | ext = os.path.splitext(path)[1].lower() 99 | return ext in [ 100 | '.markdown', 101 | '.mdown', 102 | '.mkdn', 103 | '.mkd', 104 | '.md', 105 | ] 106 | 107 | 108 | def _is_html_file(path): 109 | """ 110 | Return ``True`` if the given file is an HTML file. 111 | """ 112 | ext = os.path.splitext(path)[1].lower() 113 | return ext in [ 114 | '.html', 115 | '.htm', 116 | ] 117 | 118 | 119 | def _copy_file(source_path, output_path): 120 | """ 121 | Copy ``source_path`` to ``output_path``, preserving parent 122 | directories. 123 | """ 124 | output_dir = os.path.dirname(output_path) 125 | if not os.path.exists(output_dir): 126 | os.makedirs(output_dir) 127 | shutil.copy(source_path, output_path) 128 | 129 | 130 | def _copy_media_files(from_dir, to_dir): 131 | """ 132 | Copy media files from ``from_dir`` to ``to_dir``. 133 | """ 134 | for (source_dir, dirnames, filenames) in os.walk(from_dir): 135 | relative_path = os.path.relpath(source_dir, from_dir) 136 | output_dir = os.path.normpath(os.path.join(to_dir, relative_path)) 137 | 138 | # Ignore filenames starting with a '.' 139 | filenames = [f for f in filenames if not f.startswith('.')] 140 | 141 | # Ignore dirnames that start with a '.' 142 | dirnames[:] = [d for d in dirnames if not d.startswith('.')] 143 | 144 | for filename in filenames: 145 | if not _is_markdown_file(filename) and not _is_html_file(filename): 146 | source_path = os.path.join(source_dir, filename) 147 | output_path = os.path.join(output_dir, filename) 148 | _copy_file(source_path, output_path) 149 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ramlfications==0.1.6 2 | Jinja2==2.7.3 3 | six==1.9.0 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # Copyright (c) 2015 Spotify AB 4 | import io 5 | import os 6 | import re 7 | import sys 8 | from setuptools import setup, find_packages 9 | from setuptools.command.test import test as TestCommand # NOQA 10 | 11 | 12 | NAME = "griffin" 13 | META_PATH = os.path.join("griffin", "__init__.py") 14 | 15 | 16 | HERE = os.path.abspath(os.path.dirname(__file__)) 17 | 18 | 19 | def read(*filenames, **kwargs): 20 | encoding = kwargs.get('encoding', 'utf-8') 21 | sep = kwargs.get('sep', '\n') 22 | buf = [] 23 | for fl in filenames: 24 | with io.open(fl, encoding=encoding) as f: 25 | buf.append(f.read()) 26 | return sep.join(buf) 27 | 28 | 29 | META_FILE = read(META_PATH) 30 | 31 | 32 | def find_meta(meta): 33 | """ 34 | Extract __*meta*__ from META_FILE. 35 | """ 36 | meta_match = re.search( 37 | r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), 38 | META_FILE, re.M 39 | ) 40 | if meta_match: 41 | return meta_match.group(1) 42 | raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta)) 43 | 44 | 45 | def install_requires(): 46 | install_requires = ["ramlfications", "jinja2"] 47 | if sys.version_info[:2] == (2, 6): 48 | install_requires.append("ordereddict") 49 | return install_requires 50 | 51 | 52 | long_description = read("README.rst", "docs/changelog.rst") 53 | 54 | 55 | class PyTest(TestCommand): 56 | user_options = [("pytest-args=", "a", "Arguments to pass to py.test")] 57 | 58 | def initialize_options(self): 59 | TestCommand.initialize_options(self) 60 | self.pytest_args = None 61 | 62 | def finalize_options(self): 63 | TestCommand.finalize_options(self) 64 | self.test_args = [] 65 | self.test_suite = True 66 | 67 | def run_tests(self): 68 | import pytest 69 | errno = pytest.main(self.pytest_args or [] + ["tests"]) 70 | sys.exit(errno) 71 | 72 | setup( 73 | name=NAME, 74 | version=find_meta("version"), 75 | description=find_meta("description"), 76 | long_description=long_description, 77 | url=find_meta("uri"), 78 | license=find_meta("license"), 79 | author=find_meta("author"), 80 | author_email=find_meta("email"), 81 | keywords=["raml", "rest"], 82 | packages=find_packages(exclude=["tests*"]), 83 | entry_points={ 84 | 'console_scripts': [ 85 | 'griffin = griffin.__main__:main' 86 | ] 87 | }, 88 | classifiers=[ 89 | "Development Status :: 3 - Alpha", 90 | "Environment :: Console", 91 | "Intended Audience :: Developers", 92 | "Natural Language :: English", 93 | "License :: OSI Approved :: Apache Software License", 94 | "Operating System :: OS Independent", 95 | "Programming Language :: Python", 96 | "Programming Language :: Python :: 2", 97 | "Programming Language :: Python :: 2.6", 98 | "Programming Language :: Python :: 2.7", 99 | "Programming Language :: Python :: 3", 100 | "Programming Language :: Python :: 3.3", 101 | "Programming Language :: Python :: 3.4", 102 | "Programming Language :: Python :: Implementation :: CPython", 103 | "Programming Language :: Python :: Implementation :: PyPy", 104 | "Topic :: Documentation", 105 | "Topic :: Software Development :: Documentation", 106 | "Topic :: Software Development :: Libraries :: Python Modules", 107 | ], 108 | install_requires=install_requires(), 109 | tests_require=[ 110 | "pytest" 111 | ], 112 | cmdclass={ 113 | "test": PyTest, 114 | } 115 | ) 116 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/tests/__init__.py -------------------------------------------------------------------------------- /tests/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | from __future__ import absolute_import, division, print_function 4 | 5 | import os 6 | 7 | PAR_DIR = os.path.abspath(os.path.dirname(__file__)) 8 | SPOTIFY = os.path.join(PAR_DIR + '/cli_input/spotify/') 9 | CONFIG = os.path.join(PAR_DIR + '/cli_input/config/') 10 | EXAMPLES = os.path.join(PAR_DIR + '/cli_input/') 11 | -------------------------------------------------------------------------------- /tests/cli_input/config/github.ini: -------------------------------------------------------------------------------- 1 | [main] 2 | validate = False 3 | production = True 4 | 5 | [custom] 6 | append = True 7 | resp_codes = 420, 421, 422 8 | auth_schemes = oauth_3_0, oauth_4_0 9 | media_types = application/vnd.github.v3, foo/bar 10 | protocols = FTP 11 | raml_versions = 0.8 12 | -------------------------------------------------------------------------------- /tests/cli_input/config/instagram.ini: -------------------------------------------------------------------------------- 1 | [main] 2 | validate = False 3 | production = True 4 | 5 | [custom] 6 | append = True 7 | resp_codes = 420, 421, 422 8 | raml_versions = 0.8 9 | -------------------------------------------------------------------------------- /tests/cli_input/config/instagram.yaml: -------------------------------------------------------------------------------- 1 | # All things in templates that are "site.{{ FOO }}" 2 | title: Instagram API 3 | base_url: "https://localhost:5050" 4 | 5 | description: Auto-generated documentation from Griffin 6 | author: Super Awesome Developer 7 | canonical_url: https://api.example.com/documentation 8 | 9 | # relative path to base_url 10 | favicon: assets/images/favicon.ico 11 | 12 | # brand for navbar 13 | brand_image: assets/images/brand.png 14 | 15 | # "preserve" order on RAML, "abc" for alphabetical, 16 | # or "zyx" for reverse alphabetical 17 | endpoint_order: preserve 18 | 19 | 20 | theme_path: "themes" 21 | 22 | # options: default, ... 23 | theme_name: "default" 24 | 25 | template_dir: templates 26 | static_dir: static 27 | -------------------------------------------------------------------------------- /tests/cli_input/config/simple_config.yaml: -------------------------------------------------------------------------------- 1 | # All things in templates that are "site.{{ FOO }}" 2 | title: Spotify API 3 | base_url: "https://localhost:5050" 4 | 5 | description: Auto-generated documentation from Griffin 6 | author: Super Awesome Developer 7 | canonical_url: https://api.example.com/documentation 8 | 9 | # relative path to base_url 10 | favicon: assets/images/favicon.ico 11 | 12 | # brand for navbar 13 | brand_image: assets/images/brand.png 14 | 15 | # "preserve" order on RAML, "abc" for alphabetical, 16 | # or "zyx" for reverse alphabetical 17 | endpoint_order: preserve 18 | 19 | 20 | theme_path: "themes" 21 | 22 | # options: default, ... 23 | theme_name: "default" 24 | 25 | template_dir: templates 26 | static_dir: static 27 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/album/full.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "required": true, 5 | "properties": { 6 | "album_type": { 7 | "type": "string", 8 | "description": "The type of the album: one of 'album', 'single', or 'compilation'." 9 | }, 10 | "artists": { 11 | "type": "array", 12 | "description": "The artists of the album. Each artist object includes a link in `href` to more detailed information about the artist.", 13 | "items": { 14 | "$ref": "artist.json" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/album/simple.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "required": true, 5 | "properties": { 6 | "album_type": { 7 | "type": "string", 8 | "description": "The type of the album: one of 'album', 'single', or 'compilation'." 9 | }, 10 | "artists": { 11 | "type": "array", 12 | "description": "The artists of the album. Each artist object includes a link in `href` to more detailed information about the artist.", 13 | "items": { 14 | "$ref": "artist.json" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/get-playlists.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "properties": { 5 | "href": { 6 | "type": "string", 7 | "description": "A link to the Web API endpoint returning the full result of the request." 8 | }, 9 | "items": { 10 | "type": "array", 11 | "description": "List of track objects in JSON format.", 12 | "items": { 13 | "$ref": "user/playlist/full.schema.json" 14 | } 15 | }, 16 | "limit": { 17 | "type": "integer", 18 | "description": "The maximum number of items in the response (as set in the query or by default)." 19 | }, 20 | "next": { 21 | "type": "string", 22 | "description": "URL to the next page of items. (`null` if none) " 23 | }, 24 | "offset": { 25 | "type": "integer", 26 | "description": "The offset of the items returned (as set in the query or by default)." 27 | }, 28 | "previous": { 29 | "type": "string", 30 | "description": "URL to the previous page of items. (null if none) " 31 | }, 32 | "total": { 33 | "type": "integer", 34 | "description": "The total number of items available to return. " 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/put_playlist_tracks.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "range_start": 1, 3 | "range_length": 2, 4 | "insert_before": 3 5 | } 6 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/put_playlist_tracks.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "properties": { 5 | "range_start": { 6 | "type": "integer", 7 | "description": "The position of the first track to be reordered.", 8 | "required": true 9 | }, 10 | "range_length": { 11 | "type": "integer", 12 | "description": "Optional. The amount of tracks to be reordered. The range of tracks to be reordered begins from the range_start position, and includes the range_length subsequent tracks.", 13 | "required": false, 14 | "default": 1 15 | }, 16 | "insert_before": { 17 | "type": "integer", 18 | "description": "The position where the tracks should be inserted. To reorder the tracks to the end of the playlist, simply set insert_before to the position after the last track.", 19 | "required": true 20 | }, 21 | "snapshot_id": { 22 | "type": "string", 23 | "required": false, 24 | "description": "The playlist's snapshot ID against which you want to make the changes." 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/responses/albums.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "albums" : [ { 3 | "album_type" : "album", 4 | "artists" : [ { 5 | "external_urls" : { 6 | "spotify" : "https://open.spotify.com/artist/53A0W3U0s8diEn9RhXQhVz" 7 | }, 8 | "href" : "https://api.spotify.com/v1/artists/53A0W3U0s8diEn9RhXQhVz", 9 | "id" : "53A0W3U0s8diEn9RhXQhVz", 10 | "name" : "Keane", 11 | "type" : "artist", 12 | "uri" : "spotify:artist:53A0W3U0s8diEn9RhXQhVz" 13 | } ], 14 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ], 15 | "copyrights" : [ { 16 | "text" : "(C) 2013 Universal Island Records, a division of Universal Music Operations Limited", 17 | "type" : "C" 18 | }, { 19 | "text" : "(P) 2013 Universal Island Records, a division of Universal Music Operations Limited", 20 | "type" : "P" 21 | } ], 22 | "external_ids" : { 23 | "upc" : "00602537518357" 24 | }, 25 | "external_urls" : { 26 | "spotify" : "https://open.spotify.com/album/41MnTivkwTO3UUJ8DrqEJJ" 27 | }, 28 | "genres" : [ ], 29 | "href" : "https://api.spotify.com/v1/albums/41MnTivkwTO3UUJ8DrqEJJ", 30 | "id" : "41MnTivkwTO3UUJ8DrqEJJ", 31 | "images" : [ { 32 | "height" : 640, 33 | "url" : "https://i.scdn.co/image/89b92c6b59131776c0cd8e5df46301ffcf36ed69", 34 | "width" : 640 35 | }, { 36 | "height" : 300, 37 | "url" : "https://i.scdn.co/image/eb6f0b2594d81f8d9dced193f3e9a3bc4318aedc", 38 | "width" : 300 39 | }, { 40 | "height" : 64, 41 | "url" : "https://i.scdn.co/image/21e1ebcd7ebd3b679d9d5084bba1e163638b103a", 42 | "width" : 64 43 | } ], 44 | "name" : "The Best Of Keane (Deluxe Edition)", 45 | "popularity" : 65, 46 | "release_date" : "2013-11-08", 47 | "release_date_precision" : "day", 48 | "tracks" : { 49 | "href" : "https://api.spotify.com/v1/albums/41MnTivkwTO3UUJ8DrqEJJ/tracks?offset=0&limit=50", 50 | "items" : [ { 51 | "artists" : [ { 52 | "external_urls" : { 53 | "spotify" : "https://open.spotify.com/artist/53A0W3U0s8diEn9RhXQhVz" 54 | }, 55 | "href" : "https://api.spotify.com/v1/artists/53A0W3U0s8diEn9RhXQhVz", 56 | "id" : "53A0W3U0s8diEn9RhXQhVz", 57 | "name" : "Keane", 58 | "type" : "artist", 59 | "uri" : "spotify:artist:53A0W3U0s8diEn9RhXQhVz" 60 | } ], 61 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ], 62 | "disc_number" : 1, 63 | "duration_ms" : 215986, 64 | "explicit" : false, 65 | "external_urls" : { 66 | "spotify" : "https://open.spotify.com/track/4r9PmSmbAOOWqaGWLf6M9Q" 67 | }, 68 | "href" : "https://api.spotify.com/v1/tracks/4r9PmSmbAOOWqaGWLf6M9Q", 69 | "id" : "4r9PmSmbAOOWqaGWLf6M9Q", 70 | "name" : "Everybody's Changing", 71 | "preview_url" : "https://p.scdn.co/mp3-preview/641fd877ee0f42f3713d1649e20a9734cc64b8f9", 72 | "track_number" : 1, 73 | "type" : "track", 74 | "uri" : "spotify:track:4r9PmSmbAOOWqaGWLf6M9Q" 75 | }, { 76 | "artists" : [ { 77 | "external_urls" : { 78 | "spotify" : "https://open.spotify.com/artist/53A0W3U0s8diEn9RhXQhVz" 79 | }, 80 | "href" : "https://api.spotify.com/v1/artists/53A0W3U0s8diEn9RhXQhVz", 81 | "id" : "53A0W3U0s8diEn9RhXQhVz", 82 | "name" : "Keane", 83 | "type" : "artist", 84 | "uri" : "spotify:artist:53A0W3U0s8diEn9RhXQhVz" 85 | } ], 86 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ], 87 | "disc_number" : 1, 88 | "duration_ms" : 235880, 89 | "explicit" : false, 90 | "external_urls" : { 91 | "spotify" : "https://open.spotify.com/track/0HJQD8uqX2Bq5HVdLnd3ep" 92 | }, 93 | "href" : "https://api.spotify.com/v1/tracks/0HJQD8uqX2Bq5HVdLnd3ep", 94 | "id" : "0HJQD8uqX2Bq5HVdLnd3ep", 95 | "name" : "Somewhere Only We Know", 96 | "preview_url" : "https://p.scdn.co/mp3-preview/e001676375ea2b4807cee2f98b51f2f3fe0d109b", 97 | "track_number" : 2, 98 | "type" : "track", 99 | "uri" : "spotify:track:0HJQD8uqX2Bq5HVdLnd3ep" 100 | }], 101 | "limit" : 50, 102 | "next" : null, 103 | "offset" : 0, 104 | "previous" : null, 105 | "total" : 9 106 | }, 107 | "type" : "album", 108 | "uri" : "spotify:album:6UXCm6bOO4gFlDQZV5yL37" 109 | } ] 110 | } 111 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/responses/albums.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "requred": true, 5 | "properties": { 6 | "albums": { 7 | "type": "array", 8 | "description": "List of album objects in JSON format.", 9 | "items": { 10 | "$ref": "album/full.schema.json" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/responses/tracks.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "href": "https://api.spotify.com/v1/albums/6akEvsycLGftJxYudPjmqK/tracks?offset=0&limit=2", 3 | "items": [ { 4 | "artists": [ { 5 | "external_urls": { 6 | "spotify": "https://open.spotify.com/artist/08td7MxkoHQkXnWAYD8d6Q" 7 | }, 8 | "href": "https://api.spotify.com/v1/artists/08td7MxkoHQkXnWAYD8d6Q", 9 | "id": "08td7MxkoHQkXnWAYD8d6Q", 10 | "name": "Tania Bowra", 11 | "type": "artist", 12 | "uri": "spotify:artist:08td7MxkoHQkXnWAYD8d6Q" 13 | } ], 14 | "available_markets": [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ], 15 | "disc_number": 1, 16 | "duration_ms": 276773, 17 | "explicit": false, 18 | "external_urls": { 19 | "spotify": "https://open.spotify.com/track/2TpxZ7JUBn3uw46aR7qd6V" 20 | }, 21 | "href": "https://api.spotify.com/v1/tracks/2TpxZ7JUBn3uw46aR7qd6V", 22 | "id": "2TpxZ7JUBn3uw46aR7qd6V", 23 | "name": "All I Want", 24 | "preview_url": "https://p.scdn.co/mp3-preview/6d00206e32194d15df329d4770e4fa1f2ced3f57", 25 | "track_number": 1, 26 | "type": "track", 27 | "uri": "spotify:track:2TpxZ7JUBn3uw46aR7qd6V" 28 | }, { 29 | "artists": [ { 30 | "external_urls": { 31 | "spotify": "https://open.spotify.com/artist/08td7MxkoHQkXnWAYD8d6Q" 32 | }, 33 | "href": "https://api.spotify.com/v1/artists/08td7MxkoHQkXnWAYD8d6Q", 34 | "id": "08td7MxkoHQkXnWAYD8d6Q", 35 | "name": "Tania Bowra", 36 | "type": "artist", 37 | "uri": "spotify:artist:08td7MxkoHQkXnWAYD8d6Q" 38 | } ], 39 | "available_markets": [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ], 40 | "disc_number": 1, 41 | "duration_ms": 247680, 42 | "explicit": false, 43 | "external_urls": { 44 | "spotify": "https://open.spotify.com/track/4PjcfyZZVE10TFd9EKA72r" 45 | }, 46 | "href": "https://api.spotify.com/v1/tracks/4PjcfyZZVE10TFd9EKA72r", 47 | "id": "4PjcfyZZVE10TFd9EKA72r", 48 | "name": "Someday", 49 | "preview_url": "https://p.scdn.co/mp3-preview/2b15de922bf4f4b8cfe09c8448079b8ff7a45a5f", 50 | "track_number": 2, 51 | "type": "track", 52 | "uri": "spotify:track:4PjcfyZZVE10TFd9EKA72r" 53 | } ], 54 | "limit": 2, 55 | "next": "https://api.spotify.com/v1/albums/6akEvsycLGftJxYudPjmqK/tracks?offset=2&limit=2", 56 | "offset": 0, 57 | "previous": null, 58 | "total": 11 59 | } 60 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/responses/tracks.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "properties": { 5 | "href": { 6 | "type": "string", 7 | "description": "A link to the Web API endpoint returning the full result of the request." 8 | }, 9 | "items": { 10 | "type": "array", 11 | "description": "List of track objects in JSON format.", 12 | "items": { 13 | "$ref": "track/full.schema.json" 14 | } 15 | }, 16 | "limit": { 17 | "type": "integer", 18 | "description": "The maximum number of items in the response (as set in the query or by default)." 19 | }, 20 | "next": { 21 | "type": "string", 22 | "description": "URL to the next page of items. (`null` if none) " 23 | }, 24 | "offset": { 25 | "type": "integer", 26 | "description": "The offset of the items returned (as set in the query or by default)." 27 | }, 28 | "previous": { 29 | "type": "string", 30 | "description": "URL to the previous page of items. (null if none) " 31 | }, 32 | "total": { 33 | "type": "integer", 34 | "description": "The total number of items available to return. " 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/track/full.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "required": true, 5 | "properties": { 6 | "album": { 7 | "type": "object", 8 | "description": "The album on which the track appears. The album object includes a link in `href` to full information about the album.", 9 | "$ref": "album.simplified.schema.json" 10 | }, 11 | "artist": { 12 | "type": "array", 13 | "description": "The artists who performed the track. Each artist object includes a link in href to more detailed information about the artist. ", 14 | "items": { 15 | "$ref": "artist.simplified.schema.json" 16 | } 17 | }, 18 | "available_markets": { 19 | "type": "array", 20 | "description": "A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code. ", 21 | "items": { 22 | "type": "string", 23 | "description": "ISO 3166-1 alpha-2 code." 24 | } 25 | }, 26 | "disc_number": { 27 | "type": "integer", 28 | "description": "The disc number (usually `1` unless the album consists of more than one disc). " 29 | }, 30 | "duration_ms": { 31 | "type": "integer", 32 | "description": "The track length in milliseconds. " 33 | }, 34 | "explicit": { 35 | "type": "boolean", 36 | "description": "Whether or not the track has explicit lyrics (`true` = yes it does; `false` = no it does not OR unknown). " 37 | }, 38 | "external_ids": { 39 | "type": "object", 40 | "description": "Known external IDs for the track.", 41 | "properties": { 42 | "$ref": "external_ids.schema.json" 43 | } 44 | }, 45 | "external_urls": { 46 | "type": "object", 47 | "description": "Known external URLs for this track.", 48 | "properties": { 49 | "ref": "external_urls.schema.json" 50 | } 51 | }, 52 | "href": { 53 | "type": "string", 54 | "description": "A link to the Web API endpoint providing full details of the track." 55 | }, 56 | "id": { 57 | "type": "string", 58 | "description": "The Spotify ID for the track. " 59 | }, 60 | "is_playable": { 61 | "type": "boolean", 62 | "description": "Part of the response when Track Relinking is applied. If `true`, the track is playable in the given market. Otherwise `false`." 63 | }, 64 | "linked_from": { 65 | "type": "object", 66 | "description": "Part of the response when Track Relinking is applied, and the requested track has been replaced with different track. The track in the linked_from object contains information about the originally requested track.", 67 | "properties": { 68 | "$ref": "linked_track.schema.json" 69 | } 70 | }, 71 | "name": { 72 | "type": "string", 73 | "description": "The name of the track." 74 | }, 75 | "popularity": { 76 | "type": "integer", 77 | "description": "The popularity of the track. The value will be between 0 and 100, with 100 being the most popular. The popularity of a track is a value between 0 and 100, with 100 being the most popular. The popularity is calculated by algorithm and is based, in the most part, on the total number of plays the track has had and how recent those plays are. Generally speaking, songs that are being played a lot now will have a higher popularity than songs that were played a lot in the past. Duplicate tracks (e.g. the same track from a single and an album) are rated independently. Artist and album popularity is derived mathematically from track popularity. Note that the popularity value may lag actual popularity by a few days: the value is not updated in real time." 78 | }, 79 | "preview_url": { 80 | "type": "string", 81 | "description": "A link to a 30 second preview (MP3 format) of the track." 82 | }, 83 | "track_number": { 84 | "type": "integer", 85 | "description": "The number of the track. If an album has several discs, the track number is the number on the specified disc." 86 | }, 87 | "type": { 88 | "type": "string", 89 | "description": "The object type: \"track\"." 90 | }, 91 | "uri": { 92 | "type": "string", 93 | "description": "The Spotify URI for the track." 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/track/simplified.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "required": true, 5 | "properties": { 6 | "artist": { 7 | "type": "array", 8 | "description": "The artists who performed the track. Each artist object includes a link in href to more detailed information about the artist. ", 9 | "items": { 10 | "$ref": "artist.simplified.schema.json" 11 | } 12 | }, 13 | "available_markets": { 14 | "type": "array", 15 | "description": "A list of the countries in which the track can be played, identified by their ISO 3166-1 alpha-2 code. ", 16 | "items": { 17 | "type": "string", 18 | "description": "ISO 3166-1 alpha-2 code." 19 | } 20 | }, 21 | "disc_number": { 22 | "type": "integer", 23 | "description": "The disc number (usually `1` unless the album consists of more than one disc). " 24 | }, 25 | "duration_ms": { 26 | "type": "integer", 27 | "description": "The track length in milliseconds. " 28 | }, 29 | "explicit": { 30 | "type": "boolean", 31 | "description": "Whether or not the track has explicit lyrics (`true` = yes it does; `false` = no it does not OR unknown). " 32 | }, 33 | "external_urls": { 34 | "type": "object", 35 | "description": "Known external URLs for this track.", 36 | "properties": { 37 | "ref": "external_urls.schema.json" 38 | } 39 | }, 40 | "href": { 41 | "type": "string", 42 | "description": "A link to the Web API endpoint providing full details of the track." 43 | }, 44 | "id": { 45 | "type": "string", 46 | "description": "The Spotify ID for the track. " 47 | }, 48 | "is_playable": { 49 | "type": "boolean", 50 | "description": "Part of the response when Track Relinking is applied. If `true`, the track is playable in the given market. Otherwise `false`." 51 | }, 52 | "linked_from": { 53 | "type": "object", 54 | "description": "Part of the response when Track Relinking is applied, and the requested track has been replaced with different track. The track in the linked_from object contains information about the originally requested track.", 55 | "properties": { 56 | "$ref": "linked_track.schema.json" 57 | } 58 | }, 59 | "name": { 60 | "type": "string", 61 | "description": "The name of the track." 62 | }, 63 | "preview_url": { 64 | "type": "string", 65 | "description": "A link to a 30 second preview (MP3 format) of the track." 66 | }, 67 | "track_number": { 68 | "type": "integer", 69 | "description": "The number of the track. If an album has several discs, the track number is the number on the specified disc." 70 | }, 71 | "type": { 72 | "type": "string", 73 | "description": "The object type: \"track\"." 74 | }, 75 | "uri": { 76 | "type": "string", 77 | "description": "The Spotify URI for the track." 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/user/playlist/full.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "collaborative" : false, 3 | "description" : "A playlist for testing pourposes", 4 | "external_urls" : { 5 | "spotify" : "http://open.spotify.com/user/jmperezperez/playlist/3cEYpjA9oz9GiPac4AsH4n" 6 | }, 7 | "followers" : { 8 | "href" : null, 9 | "total" : 6 10 | }, 11 | "href" : "https://api.spotify.com/v1/users/jmperezperez/playlists/3cEYpjA9oz9GiPac4AsH4n", 12 | "id" : "3cEYpjA9oz9GiPac4AsH4n", 13 | "images" : [ { 14 | "height" : null, 15 | "url" : "https://u.scdn.co/images/pl/default/15e1e401aca06139b92bb116834a8324d03d4fd1", 16 | "width" : null 17 | } ], 18 | "name" : "Spotify Web API Testing playlist", 19 | "owner" : { 20 | "external_urls" : { 21 | "spotify" : "http://open.spotify.com/user/jmperezperez" 22 | }, 23 | "href" : "https://api.spotify.com/v1/users/jmperezperez", 24 | "id" : "jmperezperez", 25 | "type" : "user", 26 | "uri" : "spotify:user:jmperezperez" 27 | }, 28 | "public" : true, 29 | "snapshot_id" : "OO6GcckxJ416i8dVUSXH0xscQyX6CwEP14rp5JH+nM/Yd6YA9HTuT7F39uC6Y6MJ", 30 | "tracks" : { 31 | "href" : "https://api.spotify.com/v1/users/jmperezperez/playlists/3cEYpjA9oz9GiPac4AsH4n/tracks?offset=0&limit=100&market=ES", 32 | "items" : [ { 33 | "added_at" : "2015-01-15T12:39:22Z", 34 | "added_by" : { 35 | "external_urls" : { 36 | "spotify" : "http://open.spotify.com/user/jmperezperez" 37 | }, 38 | "href" : "https://api.spotify.com/v1/users/jmperezperez", 39 | "id" : "jmperezperez", 40 | "type" : "user", 41 | "uri" : "spotify:user:jmperezperez" 42 | }, 43 | "is_local" : false, 44 | "track" : { 45 | "album" : { 46 | "album_type" : "album", 47 | "external_urls" : { 48 | "spotify" : "https://open.spotify.com/album/2pANdqPvxInB0YvcDiw4ko" 49 | }, 50 | "href" : "https://api.spotify.com/v1/albums/2pANdqPvxInB0YvcDiw4ko", 51 | "id" : "2pANdqPvxInB0YvcDiw4ko", 52 | "images" : [ { 53 | "height" : 640, 54 | "url" : "https://i.scdn.co/image/599c4ead3874e1e66368e8cf3721b9f79116b328", 55 | "width" : 640 56 | }, { 57 | "height" : 300, 58 | "url" : "https://i.scdn.co/image/6f31225bf642a58f29a80ca51769c8c588cccdee", 59 | "width" : 300 60 | }, { 61 | "height" : 64, 62 | "url" : "https://i.scdn.co/image/1a8532495d73b657d592196bfdd9cb980f61443f", 63 | "width" : 64 64 | } ], 65 | "name" : "Progressive Psy Trance Picks Vol.8", 66 | "type" : "album", 67 | "uri" : "spotify:album:2pANdqPvxInB0YvcDiw4ko" 68 | }, 69 | "artists" : [ { 70 | "external_urls" : { 71 | "spotify" : "https://open.spotify.com/artist/6eSdhw46riw2OUHgMwR8B5" 72 | }, 73 | "href" : "https://api.spotify.com/v1/artists/6eSdhw46riw2OUHgMwR8B5", 74 | "id" : "6eSdhw46riw2OUHgMwR8B5", 75 | "name" : "Odiseo", 76 | "type" : "artist", 77 | "uri" : "spotify:artist:6eSdhw46riw2OUHgMwR8B5" 78 | } ], 79 | "disc_number" : 1, 80 | "duration_ms" : 376000, 81 | "explicit" : false, 82 | "external_ids" : { 83 | "isrc" : "DEKC41200989" 84 | }, 85 | "external_urls" : { 86 | "spotify" : "https://open.spotify.com/track/4rzfv0JLZfVhOhbSQ8o5jZ" 87 | }, 88 | "href" : "https://api.spotify.com/v1/tracks/4rzfv0JLZfVhOhbSQ8o5jZ", 89 | "id" : "4rzfv0JLZfVhOhbSQ8o5jZ", 90 | "is_playable" : true, 91 | "name" : "Api", 92 | "popularity" : 6, 93 | "preview_url" : "https://p.scdn.co/mp3-preview/9a149a9366c5bcb3e8b947b00f26e74be7b8aca6", 94 | "track_number" : 10, 95 | "type" : "track", 96 | "uri" : "spotify:track:4rzfv0JLZfVhOhbSQ8o5jZ" 97 | } 98 | }, { 99 | "added_at" : "2015-01-15T12:40:03Z", 100 | "added_by" : { 101 | "external_urls" : { 102 | "spotify" : "http://open.spotify.com/user/jmperezperez" 103 | }, 104 | "href" : "https://api.spotify.com/v1/users/jmperezperez", 105 | "id" : "jmperezperez", 106 | "type" : "user", 107 | "uri" : "spotify:user:jmperezperez" 108 | }, 109 | "is_local" : false, 110 | "track" : { 111 | "album" : { 112 | "album_type" : "compilation", 113 | "external_urls" : { 114 | "spotify" : "https://open.spotify.com/album/6nlfkk5GoXRL1nktlATNsy" 115 | }, 116 | "href" : "https://api.spotify.com/v1/albums/6nlfkk5GoXRL1nktlATNsy", 117 | "id" : "6nlfkk5GoXRL1nktlATNsy", 118 | "images" : [ { 119 | "height" : 640, 120 | "url" : "https://i.scdn.co/image/385bd8f5109c00d9b43842ba5b99e9e82f7156f8", 121 | "width" : 640 122 | }, { 123 | "height" : 300, 124 | "url" : "https://i.scdn.co/image/4a06e9a60a90529dea773326219ae6d4c7c422c2", 125 | "width" : 300 126 | }, { 127 | "height" : 64, 128 | "url" : "https://i.scdn.co/image/d076d09bea0978819ade083856f80d73e2438adc", 129 | "width" : 64 130 | } ], 131 | "name" : "Wellness & Dreaming Source", 132 | "type" : "album", 133 | "uri" : "spotify:album:6nlfkk5GoXRL1nktlATNsy" 134 | }, 135 | "artists" : [ { 136 | "external_urls" : { 137 | "spotify" : "https://open.spotify.com/artist/5VQE4WOzPu9h3HnGLuBoA6" 138 | }, 139 | "href" : "https://api.spotify.com/v1/artists/5VQE4WOzPu9h3HnGLuBoA6", 140 | "id" : "5VQE4WOzPu9h3HnGLuBoA6", 141 | "name" : "Vlasta Marek", 142 | "type" : "artist", 143 | "uri" : "spotify:artist:5VQE4WOzPu9h3HnGLuBoA6" 144 | } ], 145 | "disc_number" : 1, 146 | "duration_ms" : 730066, 147 | "explicit" : false, 148 | "external_ids" : { 149 | "isrc" : "FR2X41475057" 150 | }, 151 | "external_urls" : { 152 | "spotify" : "https://open.spotify.com/track/5o3jMYOSbaVz3tkgwhELSV" 153 | }, 154 | "href" : "https://api.spotify.com/v1/tracks/5o3jMYOSbaVz3tkgwhELSV", 155 | "id" : "5o3jMYOSbaVz3tkgwhELSV", 156 | "is_playable" : true, 157 | "name" : "Is", 158 | "popularity" : 0, 159 | "preview_url" : "https://p.scdn.co/mp3-preview/eee08812f2d21e00a802e1f0c9b1950a8acc6cf3", 160 | "track_number" : 21, 161 | "type" : "track", 162 | "uri" : "spotify:track:5o3jMYOSbaVz3tkgwhELSV" 163 | } 164 | }, { 165 | "added_at" : "2015-01-15T12:22:30Z", 166 | "added_by" : { 167 | "external_urls" : { 168 | "spotify" : "http://open.spotify.com/user/jmperezperez" 169 | }, 170 | "href" : "https://api.spotify.com/v1/users/jmperezperez", 171 | "id" : "jmperezperez", 172 | "type" : "user", 173 | "uri" : "spotify:user:jmperezperez" 174 | }, 175 | "is_local" : false, 176 | "track" : { 177 | "album" : { 178 | "album_type" : "album", 179 | "external_urls" : { 180 | "spotify" : "https://open.spotify.com/album/4hnqM0JK4CM1phwfq1Ldyz" 181 | }, 182 | "href" : "https://api.spotify.com/v1/albums/4hnqM0JK4CM1phwfq1Ldyz", 183 | "id" : "4hnqM0JK4CM1phwfq1Ldyz", 184 | "images" : [ { 185 | "height" : 640, 186 | "url" : "https://i.scdn.co/image/bb554141168b348d207c7a010cdafc7d2a4e88f8", 187 | "width" : 640 188 | }, { 189 | "height" : 300, 190 | "url" : "https://i.scdn.co/image/95010e3a7b14f31842dd13ff170a6b761848b67c", 191 | "width" : 300 192 | }, { 193 | "height" : 64, 194 | "url" : "https://i.scdn.co/image/4e9d34c73fd40bbb99c9095eb8ffa6ca1b4b3f08", 195 | "width" : 64 196 | } ], 197 | "name" : "This Is Happening", 198 | "type" : "album", 199 | "uri" : "spotify:album:4hnqM0JK4CM1phwfq1Ldyz" 200 | }, 201 | "artists" : [ { 202 | "external_urls" : { 203 | "spotify" : "https://open.spotify.com/artist/066X20Nz7iquqkkCW6Jxy6" 204 | }, 205 | "href" : "https://api.spotify.com/v1/artists/066X20Nz7iquqkkCW6Jxy6", 206 | "id" : "066X20Nz7iquqkkCW6Jxy6", 207 | "name" : "LCD Soundsystem", 208 | "type" : "artist", 209 | "uri" : "spotify:artist:066X20Nz7iquqkkCW6Jxy6" 210 | } ], 211 | "disc_number" : 1, 212 | "duration_ms" : 401440, 213 | "explicit" : false, 214 | "external_ids" : { 215 | "isrc" : "US4GE1000022" 216 | }, 217 | "external_urls" : { 218 | "spotify" : "https://open.spotify.com/track/4Cy0NHJ8Gh0xMdwyM9RkQm" 219 | }, 220 | "href" : "https://api.spotify.com/v1/tracks/4Cy0NHJ8Gh0xMdwyM9RkQm", 221 | "id" : "4Cy0NHJ8Gh0xMdwyM9RkQm", 222 | "is_playable" : true, 223 | "name" : "All I Want", 224 | "popularity" : 41, 225 | "preview_url" : "https://p.scdn.co/mp3-preview/86fceada0dbee180eb28162cd1365cb4e0dcf19a", 226 | "track_number" : 4, 227 | "type" : "track", 228 | "uri" : "spotify:track:4Cy0NHJ8Gh0xMdwyM9RkQm" 229 | } 230 | }, { 231 | "added_at" : "2015-01-15T12:40:35Z", 232 | "added_by" : { 233 | "external_urls" : { 234 | "spotify" : "http://open.spotify.com/user/jmperezperez" 235 | }, 236 | "href" : "https://api.spotify.com/v1/users/jmperezperez", 237 | "id" : "jmperezperez", 238 | "type" : "user", 239 | "uri" : "spotify:user:jmperezperez" 240 | }, 241 | "is_local" : false, 242 | "track" : { 243 | "album" : { 244 | "album_type" : "album", 245 | "external_urls" : { 246 | "spotify" : "https://open.spotify.com/album/2usKFntxa98WHMcyW6xJBz" 247 | }, 248 | "href" : "https://api.spotify.com/v1/albums/2usKFntxa98WHMcyW6xJBz", 249 | "id" : "2usKFntxa98WHMcyW6xJBz", 250 | "images" : [ { 251 | "height" : 636, 252 | "url" : "https://i.scdn.co/image/8d044068c7140d1d39dc6951eff37ac6150b4e8b", 253 | "width" : 640 254 | }, { 255 | "height" : 298, 256 | "url" : "https://i.scdn.co/image/911ed9ef8d2e513b2cf90472394193b0ece315f7", 257 | "width" : 300 258 | }, { 259 | "height" : 64, 260 | "url" : "https://i.scdn.co/image/51e7b672cfc8f58c97f39704cb9687bdf2e89797", 261 | "width" : 64 262 | } ], 263 | "name" : "Glenn Horiuchi Trio / Gelenn Horiuchi Quartet: Mercy / Jump Start / Endpoints / Curl Out / Earthworks / Mind Probe / Null Set / Another Space (A)", 264 | "type" : "album", 265 | "uri" : "spotify:album:2usKFntxa98WHMcyW6xJBz" 266 | }, 267 | "artists" : [ { 268 | "external_urls" : { 269 | "spotify" : "https://open.spotify.com/artist/272ArH9SUAlslQqsSgPJA2" 270 | }, 271 | "href" : "https://api.spotify.com/v1/artists/272ArH9SUAlslQqsSgPJA2", 272 | "id" : "272ArH9SUAlslQqsSgPJA2", 273 | "name" : "Glenn Horiuchi Trio", 274 | "type" : "artist", 275 | "uri" : "spotify:artist:272ArH9SUAlslQqsSgPJA2" 276 | } ], 277 | "disc_number" : 1, 278 | "duration_ms" : 358760, 279 | "explicit" : false, 280 | "external_ids" : { 281 | "isrc" : "USB8U1025969" 282 | }, 283 | "external_urls" : { 284 | "spotify" : "https://open.spotify.com/track/6hvFrZNocdt2FcKGCSY5NI" 285 | }, 286 | "href" : "https://api.spotify.com/v1/tracks/6hvFrZNocdt2FcKGCSY5NI", 287 | "id" : "6hvFrZNocdt2FcKGCSY5NI", 288 | "is_playable" : true, 289 | "name" : "Endpoints", 290 | "popularity" : 0, 291 | "preview_url" : "https://p.scdn.co/mp3-preview/04f45b1295cf1baf372484fa1ede9f8e00444ee2", 292 | "track_number" : 2, 293 | "type" : "track", 294 | "uri" : "spotify:track:6hvFrZNocdt2FcKGCSY5NI" 295 | } 296 | }, { 297 | "added_at" : "2015-01-15T12:41:10Z", 298 | "added_by" : { 299 | "external_urls" : { 300 | "spotify" : "http://open.spotify.com/user/jmperezperez" 301 | }, 302 | "href" : "https://api.spotify.com/v1/users/jmperezperez", 303 | "id" : "jmperezperez", 304 | "type" : "user", 305 | "uri" : "spotify:user:jmperezperez" 306 | }, 307 | "is_local" : false, 308 | "track" : { 309 | "album" : { 310 | "album_type" : "album", 311 | "external_urls" : { 312 | "spotify" : "https://open.spotify.com/album/0ivM6kSawaug0j3tZVusG2" 313 | }, 314 | "href" : "https://api.spotify.com/v1/albums/0ivM6kSawaug0j3tZVusG2", 315 | "id" : "0ivM6kSawaug0j3tZVusG2", 316 | "images" : [ { 317 | "height" : 634, 318 | "url" : "https://i.scdn.co/image/fa826e26b0d7683327317d9f4a4909f64cab281b", 319 | "width" : 640 320 | }, { 321 | "height" : 297, 322 | "url" : "https://i.scdn.co/image/5421e1bb2497eee2ba437db930eb831d0669a66b", 323 | "width" : 300 324 | }, { 325 | "height" : 63, 326 | "url" : "https://i.scdn.co/image/e3c51dadaa16361d247bb38b6f9e505f8d5ae6b1", 327 | "width" : 64 328 | } ], 329 | "name" : "All The Best", 330 | "type" : "album", 331 | "uri" : "spotify:album:0ivM6kSawaug0j3tZVusG2" 332 | }, 333 | "artists" : [ { 334 | "external_urls" : { 335 | "spotify" : "https://open.spotify.com/artist/2KftmGt9sk1yLjsAoloC3M" 336 | }, 337 | "href" : "https://api.spotify.com/v1/artists/2KftmGt9sk1yLjsAoloC3M", 338 | "id" : "2KftmGt9sk1yLjsAoloC3M", 339 | "name" : "Zucchero", 340 | "type" : "artist", 341 | "uri" : "spotify:artist:2KftmGt9sk1yLjsAoloC3M" 342 | } ], 343 | "disc_number" : 1, 344 | "duration_ms" : 176093, 345 | "explicit" : false, 346 | "external_ids" : { 347 | "isrc" : "ITUM70701043" 348 | }, 349 | "external_urls" : { 350 | "spotify" : "https://open.spotify.com/track/2E2znCPaS8anQe21GLxcvJ" 351 | }, 352 | "href" : "https://api.spotify.com/v1/tracks/2E2znCPaS8anQe21GLxcvJ", 353 | "id" : "2E2znCPaS8anQe21GLxcvJ", 354 | "is_playable" : true, 355 | "name" : "You Are So Beautiful", 356 | "popularity" : 24, 357 | "preview_url" : "https://p.scdn.co/mp3-preview/abaf2d95d7da2f7740690ea0b4b02fb817396a9d", 358 | "track_number" : 18, 359 | "type" : "track", 360 | "uri" : "spotify:track:2E2znCPaS8anQe21GLxcvJ" 361 | } 362 | } ], 363 | "limit" : 100, 364 | "next" : null, 365 | "offset" : 0, 366 | "previous" : null, 367 | "total" : 5 368 | }, 369 | "type" : "playlist", 370 | "uri" : "spotify:user:jmperezperez:playlist:3cEYpjA9oz9GiPac4AsH4n" 371 | } 372 | -------------------------------------------------------------------------------- /tests/cli_input/includes/spotify/user/playlist/full.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-03/schema", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string", 7 | "description": "Name of the playlist." 8 | }, 9 | "id": { 10 | "type": "string", 11 | "description": "ID of the playlist." 12 | }, 13 | "uri": { 14 | "type": "string", 15 | "description": "Spotify URI of the entity." 16 | }, 17 | "link": { 18 | "type": "string", 19 | "description": "HTTP link of the entity." 20 | }, 21 | "api_link": { 22 | "type": "string", 23 | "description": "API resource address of the entity." 24 | }, 25 | "followers_count": { 26 | "type": "number", 27 | "description": "The number of users following the playlist." 28 | }, 29 | "collaborative": { 30 | "type": "boolean", 31 | "description": "True if the owner allows other users to modify the playlist." 32 | }, 33 | "published": { 34 | "type": "boolean", 35 | "description": "Indicates whether the playlist is publicly discoverable. This does not restrict access for users who already know the playlist's URI." 36 | }, 37 | "description": { 38 | "type": "string", 39 | "description": "A description of the playlist." 40 | }, 41 | "image": { 42 | "type": "string", 43 | "description": "URL of a picture associated with the playlist." 44 | }, 45 | "owner": { 46 | "$ref": "User.json", 47 | "description": "User who owns the playlist." 48 | }, 49 | "items": { 50 | "type": "array", 51 | "description": "Contents of the playlist (an array of Track objects).", 52 | "items": { 53 | "$ref": "Track.json" 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/cli_input/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Spotify API! 2 | 3 |

Our Web API lets your applications fetch data from the Spotify music catalog and manage user's playlists and saved music.

4 | 5 | Based on simple REST principles, our Web API endpoints return metadata in JSON format about artists, albums, and tracks directly from the Spotify catalogue. The API also provides access to user-related data such as playlists and music saved in a "Your Music" library, subject to user's authorization. 6 | 7 | -------------------------------------------------------------------------------- /tests/test_cli.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import os 7 | 8 | import pytest 9 | 10 | from griffin.cli import build 11 | from griffin.config import setup_config 12 | from .base import EXAMPLES, CONFIG 13 | 14 | 15 | @pytest.fixture(scope="session") 16 | def raml(): 17 | return os.path.join(EXAMPLES, "spotify.raml") 18 | 19 | 20 | @pytest.fixture(scope="session") 21 | def config(): 22 | return setup_config(os.path.join(CONFIG, "simple_config.yaml")) 23 | 24 | 25 | def test_build(raml, config, tmpdir): 26 | tmp_output = tmpdir.mkdir("output") 27 | 28 | build(raml, config, str(tmp_output)) 29 | 30 | # TODO: test the existance of assets & asset dirs 31 | 32 | # TODO: test the existance of genderated HTML & parent dirs 33 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/tests/test_config.py -------------------------------------------------------------------------------- /tests/test_core.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import os 7 | 8 | import pytest 9 | from ramlfications import parameters, raml 10 | from six import iterkeys 11 | 12 | from griffin.core import create_context 13 | 14 | from .base import EXAMPLES 15 | 16 | 17 | @pytest.fixture(scope="session") 18 | def ramlfile(): 19 | return os.path.join(EXAMPLES, "spotify.raml") 20 | 21 | 22 | def test_create_context(ramlfile): 23 | context = create_context(ramlfile) 24 | meta = context.metadata 25 | collections = context.groupings 26 | 27 | assert repr(context) == "Spotify Web API" 28 | 29 | _meta_context(meta) 30 | _collections_context(collections) 31 | 32 | 33 | def _meta_context(meta): 34 | assert meta.get("title") == "Spotify Web API" 35 | assert meta.get("version") == "v1" 36 | assert meta.get("protocols") == ["HTTPS"] 37 | 38 | assert len(meta.get("docs")) == 1 39 | assert meta.get("docs")[0].title.raw == "Spotify Web API Docs" 40 | assert meta.get("docs")[0].content.raw == ( 41 | "Welcome to the _Spotify Web API_ specification. For more information " 42 | "about\nhow to use the API, check out [developer site]" 43 | "(https://developer.spotify.com/web-api/).\n" 44 | ) 45 | assert isinstance(meta.get("docs")[0], parameters.Documentation) 46 | 47 | assert meta.get("uri") == "https://api.spotify.com/v1" 48 | assert meta.get("b_params") is None 49 | assert meta.get("u_params") is None 50 | 51 | assert len(meta.get("traits")) == 2 52 | filterable = meta.get("traits")[0] 53 | paged = meta.get("traits")[1] 54 | 55 | assert isinstance(filterable, raml.TraitNode) 56 | assert filterable.name == "filterable" 57 | 58 | assert isinstance(paged, raml.TraitNode) 59 | assert paged.name == "paged" 60 | 61 | assert len(meta.get("types")) == 4 62 | base_get = meta.get("types")[0] 63 | base_post = meta.get("types")[1] 64 | item = meta.get("types")[2] 65 | collection = meta.get("types")[3] 66 | 67 | assert base_get.name == "base" 68 | assert base_get.method == "get" 69 | assert base_post.name == "base" 70 | assert base_post.method == "post" 71 | assert item.name == "item" 72 | assert collection.name == "collection" 73 | 74 | assert isinstance(base_get, raml.ResourceTypeNode) 75 | assert isinstance(base_post, raml.ResourceTypeNode) 76 | assert isinstance(item, raml.ResourceTypeNode) 77 | assert isinstance(collection, raml.ResourceTypeNode) 78 | 79 | assert meta.get("secured") is None 80 | 81 | assert len(meta.get("sec_schemes")) == 1 82 | assert meta.get("sec_schemes")[0].name == "oauth_2_0" 83 | 84 | assert meta.get("media_type") == "application/json" 85 | 86 | 87 | def _collections_context(collections): 88 | exp_parents = [ 89 | "albums", 90 | "artists", 91 | "tracks", 92 | "search", 93 | "me", 94 | "users/{user_id}", 95 | "browse" 96 | ] 97 | assert exp_parents == list(iterkeys(collections)) 98 | -------------------------------------------------------------------------------- /tests/test_logger.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/tests/test_logger.py -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Spotify AB 3 | 4 | from __future__ import absolute_import, division, print_function 5 | 6 | import os 7 | 8 | from click.testing import CliRunner 9 | import pytest 10 | 11 | from griffin import __main__ as main 12 | 13 | from .base import CONFIG, EXAMPLES 14 | 15 | 16 | @pytest.fixture() 17 | def runner(): 18 | return CliRunner() 19 | 20 | 21 | def check_result(exp_code, exp_msg, result): 22 | assert result.exit_code == exp_code 23 | if exp_msg: 24 | assert result.output == exp_msg 25 | 26 | 27 | def test_build(runner, tmpdir): 28 | raml_file = os.path.join(EXAMPLES, "spotify.raml") 29 | config_file = os.path.join(CONFIG, "simple_config.yaml") 30 | output_dir = str(tmpdir.mkdir("output")) 31 | exp_code = 0 32 | exp_msg = None 33 | 34 | result = runner.invoke(main.build, [ 35 | "--ramlfile={0}".format(raml_file), 36 | "--config={0}".format(config_file), 37 | "--output={0}".format(output_dir)]) 38 | check_result(exp_code, exp_msg, result) 39 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spotify/griffin/14ab9cde223e134179dbcac92c3b32bfd17ac62e/tests/test_utils.py -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py33, py34, pypy, flake8, manifest, docs 3 | 4 | [testenv] 5 | setenv = 6 | PYTHONHASHSEED = 0 7 | deps = 8 | pytest-cov 9 | commands = 10 | python setup.py test -a "-v --cov griffin --cov-report term-missing" 11 | 12 | [testenv:py26] 13 | basepython = python2.6 14 | setenv = 15 | PYTHONHASHSEED = 0 16 | deps = 17 | pytest-cov 18 | argparse 19 | commands = 20 | python setup.py test -a "-v --cov griffin --cov-report term-missing" 21 | 22 | ; pypy runs faster on Travis without coverage 23 | [testenv:pypy] 24 | basepython = pypy 25 | commands = 26 | python setup.py test 27 | 28 | [testenv:flake8] 29 | basepython = python2.7 30 | deps = 31 | flake8 32 | commands = 33 | flake8 griffin tests --exclude=docs/ --ignore=E221 34 | 35 | [testenv:manifest] 36 | basepython = python2.7 37 | deps = 38 | check-manifest 39 | commands = 40 | check-manifest 41 | 42 | [testenv:docs] 43 | basepython = python2.7 44 | setenv = 45 | PYTHONHASHSEED = 0 46 | deps = 47 | sphinx 48 | commands = 49 | sphinx-build -W -b html -d {envtmpdir}/doctrees docs docs/_build 50 | --------------------------------------------------------------------------------