#!/usr/bin/env python3

# This script is used to create the REST API documentation.  It executes
# example requests and then prints a markdown documentation to stdout.
#
# Start `content-testapp`, create and configure an API key for user `fred`, and
# run:
#
#   ./tools/bin/gen-apidoc-nog-content >apidoc.md
#
# Remove the testing repos with:
#
#   $ meteor mongo
#   > db.repos.remove({name: {$regex: '.*hello-world.*'}})
#

toc = [
    'createRepo',
    'getRef',
    'getRefs',
    'updateRef',
    'deleteRef',
    'getCommit_hrefs',
    'getCommit_hrefs.v0',
    'getCommit_hrefs.v1',
    'getCommit_minimal',
    'getCommit_minimal.v0',
    'getCommit_minimal.v1',
    'createCommit',
    'createCommit.v0',
    'getObject_hrefs',
    'getObject_minimal',
    'getObject_minimal.v0',
    'getObject_minimal.v1',
    'createObject',
    'createObject.v0',
    'getTree0',
    'getTree1',
    'getTree1_minimal',
    'createTree',
    'createTreeExpanded',
    'postBulk',
    'postStat'
]

doc = {}

doc['createRepo'] = '''

<!--
DO NOT EDIT.
This file has been automatically generated by gen-apidoc-nog-content.
-->

## Create a Repo

```
{method} {path}
```

**Request body**

 - `repoFullName` (`String` of format `<owner>/<name>`): The name of
   the repository

Example:

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getRef'] = '''

## Get a Reference

```
{method} {path}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getRefs'] = '''

## Get all References

```
{method} {path}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['updateRef'] = '''

## Update a Reference

```
{method} {path}
```

**Request body**

 - `new` (`String`): The new value of the reference (a hex sha1).
 - `old` (`String`): The old value of the reference (a hex sha1).

The old value must be specified as a safety measure against accidentally
overwriting a reference that has been modified by someone else.  `null` or
`0000000000000000000000000000000000000000` can be used to indicate that the
reference is unset.

Example:

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['deleteRef'] = '''

## Delete a Reference

```
{method} {path}
```

**Request body**

 - `old` (`String`): The old value of the reference (a hex sha1).

The old value must be specified as a safety measure against accidentally
deleting a reference that has been modified by someone else.

Example:

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

'''


doc['getCommit_hrefs'] = '''

## Get a Commit

```
{method} {path}
```

**Request query params**

 - `format=:format` (`minimal` or `hrefs` with optional suffix `.v0` of `.v1`;
   default: `hrefs`): Specifies whether the result contains a minimal
   representation or embedded hrefs.  The suffix specifies which representation
   version to return.  The default without suffix is to return the
   representation that matches `_idversion`.

**Response**

With hrefs:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getCommit_hrefs.v0'] = '''
With hrefs, representation v0 with UTC Z datetimes:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getCommit_hrefs.v1'] = '''
With hrefs, representation v1 with UTC timezone support:

Note that `_idversion` and format version may differ.  To compute the correct
id, the client code must convert the dates to the correct `_idversion`, which
is UTC Z for `_idversion: 0`.

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getCommit_minimal'] = '''
Minimal:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getCommit_minimal.v0'] = '''
Minimal, representation v0 with UTC Z datetimes:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getCommit_minimal.v1'] = '''
Minimal, representation v1 with UTC timezone support:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['createCommit'] = '''

## Create a Commit

```
{method} {path}
```

**Request query params**

 - `format=:format` (`minimal` or `hrefs` with optional suffix `.v0` or `.v1`;
   default: `hrefs`): Specifies whether the result is a minimal representation
   or contains embedded hrefs (see example at get).

**Request body**

The body contains a JSON representation of the commit with the following keys:

 - `subject` (`String`): The subject line of the commit.
 - `message` (`String`): The body of the commit message.
 - `tree` (`String`): The id of the tree as a hex sha1.
 - `parents` (`[String]`): The ids of the parent commits as hex sha1s.  The
   array may be empty.
 - `authors` (`[String]`, optional): An array of authors, by convention `John
   Q. Public <john@example.com>`.
 - `authorDate` (`String`, optional): An ISO string without fractional seconds
   (see below for `_idversion`).
 - `committer` (`String`, optional)
 - `commitDate` (`String`, optional): An ISO string without fractional seconds
   (see below for `_idversion`).
 - `meta` (`Object`, optional): Meta data that is stored with the commit.
 - `_idversion` (`0` or `1`, default `1`): Specify format to use for computing
   the sha1 id.

The date format differs between representation versions:

 - `_idversion 0`: Dates are UTC with Z timezone indicator.
 - `_idversion 1`: Dates use a timezone offset `+HH:MM` or `-HH:MM`.

Example:

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['createCommit.v0'] = '''
Commit `idversion 0`:

```
{method} {path}
```

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getObject_hrefs'] = '''

## Get an Object

```
{method} {path}
```

**Request query params**

 - `format=:format` (`minimal` or `hrefs` with optional suffix `.v0` or `.v1`;
   default: `hrefs`): Specifies whether the result contains a minimal
   representation or embedded hrefs.

**Response**

With hrefs:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getObject_minimal'] = '''
Minimal:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getObject_minimal.v0'] = '''
Minimal, explicit format version 0:

Note that `_idversion` and format version may differ.  To compute the correct
sha1, the client must convert the format to the `_idversion` that the server
reported.

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getObject_minimal.v1'] = '''
Minimal, explicit format version 1:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['createObject'] = '''

## Create an Object

```
{method} {path}
```

**Request query params**

 - `format=:format` (`minimal` or `hrefs`; default: `hrefs`): Specifies whether
   the result is a minimal representation or contains embedded hrefs (see
   example at get)

**Request body**

The body contains a JSON representation of the object with the following keys:

 - `blob` (`String` or `null`): The identifier of the associated blob (a hex
   sha1).
 - `text` (`String` or `null`; since format version 1): Text content of the
   object.  Text content is indexed for fulltext search, while blob content is
   opaque.  By convention either use `blob` or `text`, or none of them; but do
   not use both at the same time.
 - `name` (`String`): The name of the object.
 - `meta` (`Object`): Meta data that is stored with the object.
 - `_idversion` (`0` or `1`, default `1`): Specify the format to use for
   computing the sha1 id.

There are two different format versions:

 - `_idversion 0`: Absence of a blob is indicated by
   `0000000000000000000000000000000000000000`.  Fulltext is, by convention,
   stored in `meta.content`.
 - `_idversion 1`: Absence of a blob is indicated by `null`.  Fulltext is
   stored in `text` (may be `null`).

Example:

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['createObject.v0'] = '''
Object with idversion 0 layout:

```
{method} {path}
```

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getTree0'] = '''

## Get a Tree

```
{method} {path}
```

**Request query params**

 - `expand=:levels` (non-negative integer, optional): Specifies how many entry
   levels will be expanded recursively.  0 indicates no expansion.
 - `format=:format` (`minimal` or `hrefs` with optional suffix `.v0`; default:
   `hrefs`): Specifies whether the result contains a minimal representation or
   embedded hrefs.

The optional `format` version suffix may only be used with `expand=0`.
Children will always be expanded in the format that matches their `_idversion`.

**Response**

Unexpanded:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getTree1'] = '''
Expanded:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['getTree1_minimal'] = '''
Expanded, minimal:

```
GET {url}
```

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['createTree'] = '''

## Create a Tree

```
{method} {path}
```

**Request query params**

 - `format=:format` (`minimal` or `hrefs`; default: `hrefs`): Specifies whether
   the result is a minimal representation or contains embedded hrefs (see
   example at get)

**Request body**

The body contains a JSON representation of the tree with the following keys:

 - `tree.name` (`String`): The name of the object.
 - `tree.meta` (`Object`): Meta data that is stored with the object.
 - `tree.entries` (`Array` of entries): An entry can either be collapsed
   `{{"type": <type>, "sha1": <sha1>}}`, where `<type>` can be `'object'` or
   `'tree'` and `<sha1>` must be the id of a corresponding entry; or an entry
   can contain the full content for an object or tree.

There is only a single canonical representation (`_idversion: 0`) for trees.

Trees may recursively contain trees up to the total request limit.  Consider
using a series of bulk posts (see below) if the total tree size exceeds the
limit.

Example with collapsed entry:

```json
{reqbody}
```
'''


doc['createTreeExpanded'] = '''
Example with expanded entry:

```json
{reqbody}
```

**Response**

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['postBulk'] = '''

## Bulk Create Entries and Copy from Other Repos

```
{method} {path}
```

**Request body**

 - `entries` (Array of expanded entries or copy instructions):  Expanded
   entries can be objects, trees, and commits.  If entries depend on each
   other, the entries must be ordered such that entries that depend on other
   entries come after their dependencies.  A special kind of entry can be used
   to copy content from other repos: `{{"copy": {{"type": String, "sha1":
   String, "repoFullName": String}}}}` copies the entry with the `sha1` from
   the repo with name `repoFullName`.  `type` can be `object`, `tree`,
   `commit`, or `blob`.

Example:

```json
{reqbody}
```

**Response**

An array of collapsed `entries` of format `{{"type": String, "sha1": String}}`.

```
Status: {statusCode}
```

```json
{resbody}
```

'''


doc['postStat'] = '''

## Get Entry Status Information

```
{method} {path}
```

The method is `POST`, because it seems controversial whether request bodies
should be used with `GET`.

**Request body**

 - `entries` (Array of `{{"type": String, "sha1": String}}`): The entries for
   which status information is requested.  `type` can be `object`, `tree`,
   `commit`, or `blob`.  `sha1` is a hex sha1 id of the entry.

Example:

```json
{reqbody}
```

**Response**

The `entries` array that was posted is echoed back with an additional field
`status` that contains `exists` or `unknown`.

```
Status: {statusCode}
```

```json
{resbody}
```

'''


from copy import copy
import requests
import json
import random
import string
import hashlib
import sys
from subprocess import check_output
from textwrap import dedent
import re


baseUrl = 'http://localhost:3000/api'
apiPath = '/repos'
headers = {'Content-Type': 'application/json'}

fakeBlobId = '3f786850e387550fdab836ed7e6dc881de23001b'  # sha1_hex("a\n")
nullCommit = '0000000000000000000000000000000000000000'

userName = 'fred'
repoName = 'hello-world'

# `examples` is used to store the HTTP requests in `request()`.  The recorded
# requests are then used when formatting the documentation.
examples = {}


def main():
    repoSelector = unusedRepoSelector()
    createRepo(repoSelector)
    postBulk(repoSelector)
    postGetObject_v0(repoSelector)
    createInitialCommit(repoSelector)
    commitId = getRef(repoSelector, 'branches/master')
    createRef(repoSelector, 'branches/foo/bar', commitId)
    getRefs(repoSelector)
    deleteRef(repoSelector, 'branches/foo/bar', commitId)
    getCommit(repoSelector, commitId, format='minimal')
    getCommit(repoSelector, commitId, format='minimal.v1')
    getCommit(repoSelector, commitId, format='hrefs.v1')
    commit = getCommit(repoSelector, commitId, format='hrefs')
    # Get tree with and without expansion.
    getTree(repoSelector, commit['tree']['sha1'], levels='0')
    getTree(repoSelector, commit['tree']['sha1'], levels='1', format='minimal')
    tree = getTree(repoSelector, commit['tree']['sha1'], levels='1')
    obj = getObject(repoSelector, tree['entries'][0]['_id']['sha1'],
                    format='minimal')
    obj = getObject(repoSelector, tree['entries'][0]['_id']['sha1'],
                    format='hrefs')
    entries = [
        {'type': 'object', 'sha1': obj['_id']['sha1']},
        {'type': 'tree', 'sha1': tree['_id']['sha1']},
        {'type': 'commit', 'sha1': commit['_id']['sha1']},
        {'type': 'object', 'sha1': '0123012301230123012301230123012301230123'}
    ]
    stat = postStat(repoSelector, entries)
    printFullDoc()


def createRepo(sel):
    request(
        key='createRepo',
        method='POST',
        path=apiPath,
        reqbody={
            'repoFullName': sel['repoOwner'] + '/' + sel['repoName']
        },
        statusCode=201
    )


def createInitialCommit(repoSel):
    obj = {
        'name': 'Fake data',
        'meta': {
            'study': 'foo',
            'specimen': 'bar',
            'random': randomword(10)
        },
        'blob': fakeBlobId
    }
    obj_v1 = {
        '_idversion': 1,
        'name': 'index.md',
        'text': 'Lorem ipsum...',
        'meta': {
            'random': randomword(10)
        },
        'blob': None
    }
    tree = {
        'name': 'Workspace root',
        'meta': {
            'study': 'foo'
        },
        'entries': [obj, obj_v1]
    }
    treeId = createTree(repoSel, tree, key='createTreeExpanded')
    commit = {
        'subject': 'Initial commit',
        'message': dedent("""\
                Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
                do eiusmod tempor incididunt ut labore et dolore magna aliqua.
                Ut enim ad minim veniam, quis nostrud exercitation ullamco
                laboris nisi ut aliquip ex ea commodo consequat.
            """),
        'tree': treeId,
        'parents': []
    }
    commitId = createCommit(repoSel, commit)
    commit['parents'] = [commitId]
    commit['meta'] = {'importGitCommit': '1919191919191919191919191919191919191919'}
    commitId = createCommit(repoSel, commit)
    updateRef(repoSel, 'branches/master', commitId, nullCommit)


def postBulk(repoSel):
    obj = {
        'name': 'Fake data',
        'meta': {
            'study': 'foo',
            'specimen': 'bar',
            'random': randomword(10)
        },
        'blob': fakeBlobId
    }
    objId = createObject(repoSel, obj)
    tree = {
        'name': 'Workspace root',
        'meta': {
            'study': 'foo'
        },
        'entries': [
            {'type': 'object', 'sha1': objId}
        ]
    }
    treeId = createTree(repoSel, tree)
    commit = {
        'subject': 'Initial commit',
        'message': dedent("""\
                Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
                do eiusmod tempor incididunt ut labore et dolore magna aliqua.
                Ut enim ad minim veniam, quis nostrud exercitation ullamco
                laboris nisi ut aliquip ex ea commodo consequat.
            """),
        'tree': treeId,
        'parents': []
    }
    commitId = createCommit(repoSel, commit)
    commit_v0 = {
        '_idversion': 0,
        'subject': 'Initial commit',
        'message': dedent("""\
                Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed
                do eiusmod tempor incididunt ut labore et dolore magna aliqua.
                Ut enim ad minim veniam, quis nostrud exercitation ullamco
                laboris nisi ut aliquip ex ea commodo consequat.
            """),
        'tree': treeId,
        'authorDate': '2015-01-01T00:00:00Z',
        'commitDate': '2015-01-01T00:00:00Z',
        'parents': []
    }
    commitId_v0 = createCommit(repoSel, commit_v0, key='createCommit.v0')
    getCommit(repoSel, commitId_v0, format='minimal.v0')
    getCommit(repoSel, commitId_v0, format='hrefs.v0')
    commit['parents'] = [commitId]
    commit['meta'] = {'importGitCommit': '1919191919191919191919191919191919191919'}
    commitId = createCommit(repoSel, commit)
    repoFullName = repoSel['repoOwner'] + '/' + repoSel['repoName']
    entries = [obj, tree, commit]
    entries.append({
        'copy': {
                'type': 'object',
                'sha1': objId,
                'repoFullName': repoFullName
            }
        })
    entries.append({
        'copy': {
                'type': 'tree',
                'sha1': treeId,
                'repoFullName': repoFullName
            }
        })
    res = request(
        key='postBulk',
        method='POST',
        path=apiPath + '/{repoOwner}/{repoName}/db/bulk',
        params=repoSel,
        reqbody={'entries': entries},
        statusCode=201
    )
    return res.json()['data']['entries']


def postGetObject_v0(repoSel):
    obj_v0 = {
        '_idversion': 0,
        'name': 'fake-index.md',
        'meta': {
            'content': 'Lorem ipsum...',
            'random': randomword(10)
        },
        'blob': None
    }
    id = createObject(repoSel, obj_v0, key='createObject.v0')
    getObject(repoSel, id, format='minimal.v0')
    getObject(repoSel, id, format='minimal.v1')


def createObject(repoSel, obj, key=None):
    key = key or 'createObject'
    res = request(
        key=key,
        method='POST',
        path=apiPath + '/{repoOwner}/{repoName}/db/objects?format={format}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'format': 'hrefs'
        },
        reqbody=obj,
        statusCode=201
    )
    return res.json()["data"]["_id"]["sha1"]


def getObject(repoSel, objectId, format):
    res = request(
        key='getObject_' + format,
        method='GET',
        path=apiPath + '/{repoOwner}/{repoName}/db/objects/{sha1}?format={format}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'sha1': objectId,
            'format': format
        },
        statusCode=200
    )
    return res.json()["data"]


def createTree(repoSel, tree, key=None):
    key = key or 'createTree'
    res = request(
        key=key,
        method='POST',
        path=apiPath + '/{repoOwner}/{repoName}/db/trees?format={format}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'format': 'hrefs'
        },
        reqbody={'tree': tree},
        statusCode=201
    )
    return res.json()["data"]["_id"]["sha1"]


def getTree(repoSel, treeId, levels, format=None):
    if format:
        suffix = '_' + format
    else:
        suffix = ''
        format = 'hrefs'
    res = request(
        key='getTree' + levels + suffix,
        method='GET',
        path=apiPath + '/{repoOwner}/{repoName}/db/trees/{sha1}?expand={levels}&format={format}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'sha1': treeId,
            'levels': levels,
            'format': format
        },
        statusCode=200
    )
    return res.json()["data"]


def createCommit(repoSel, commit, key=None):
    res = request(
        key=(key or 'createCommit'),
        method='POST',
        path=apiPath + '/{repoOwner}/{repoName}/db/commits?format={format}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'format': 'hrefs'
        },
        reqbody=commit,
        statusCode=201
    )
    return res.json()["data"]["_id"]["sha1"]


def getCommit(repoSel, commitId, format):
    res = request(
        key='getCommit_' + format,
        method='GET',
        path=apiPath + '/{repoOwner}/{repoName}/db/commits/{sha1}?format={format}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'sha1': commitId,
            'format': format
        },
        statusCode=200
    )
    return res.json()["data"]


def postStat(params, entries):
    res = request(
        key='postStat',
        method='POST',
        path=apiPath + '/{repoOwner}/{repoName}/db/stat',
        params=params,
        reqbody={'entries': entries},
        statusCode=200
    )
    return res.json()['data']['entries']


def getRef(repoSel, refName):
    res = request(
        key='getRef',
        method='GET',
        path=apiPath + '/{repoOwner}/{repoName}/db/refs/{refName}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'refName': refName
        },
        statusCode=200
    )
    return res.json()["data"]["entry"]["sha1"]


def getRefs(repoSel):
    res = request(
        key='getRefs',
        method='GET',
        path=apiPath + '/{repoOwner}/{repoName}/db/refs',
        params=repoSel,
        statusCode=200
    )
    return res.json()["data"]


def createRef(repoSel, refName, commitId):
    res = request(
        key='createRef',
        method='POST',
        path=apiPath + '/{repoOwner}/{repoName}/db/refs',
        params=repoSel,
        reqbody={
            'refName': refName,
            'sha1': commitId
        },
        statusCode=201
    )
    return res.json()["data"]


def updateRef(repoSel, refName, newCommit, oldCommit):
    res = request(
        key='updateRef',
        method='PATCH',
        path=apiPath + '/{repoOwner}/{repoName}/db/refs/{refName}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'refName': refName
        },
        reqbody = {
            'new': newCommit,
            'old': oldCommit
        },
        statusCode=200
    )
    return res.json()["data"]


def deleteRef(repoSel, refName, oldCommit):
    request(
        key='deleteRef',
        method='DELETE',
        path=apiPath + '/{repoOwner}/{repoName}/db/refs/{refName}',
        params={
            'repoOwner': repoSel["repoOwner"],
            'repoName': repoSel["repoName"],
            'refName': refName
        },
        reqbody={
            'old': oldCommit
        },
        statusCode=204
    )


class ApiError(RuntimeError):
    def __init__(self, arg):
        self.arg = arg


def raise_res(res):
    try:
        data = res.json()
    except ValueError:
        data = 'no response JSON.'
    raise ApiError("Unexpected status code {0}: {1}".format(
        res.status_code, data))


def randomword(length):
    chars = (random.choice(string.ascii_lowercase) for i in range(length))
    return ''.join(chars)


# Process request and record it in examples.
def request(key, method, path, statusCode, reqbody=None, params=None):
    params = params or {}
    url = baseUrl + path.format(**params)
    unsignedUrl = url
    url = sign_req(method, url)
    if method == 'GET':
        res = requests.get(url, headers=headers)
    elif method == 'POST':
        res = requests.post(url, headers=headers, data=json.dumps(reqbody))
    elif method == 'PATCH':
        res = requests.patch(url, headers=headers, data=json.dumps(reqbody))
    elif method == 'DELETE':
        res = requests.delete(url, headers=headers, data=json.dumps(reqbody))
    else:
        raise NotImplementedError(
                "Method '{0}' not implemented.".format(method)
                )
    if res.status_code != statusCode:
        raise_res(res)
    if method == 'DELETE':
        resbody = None
        data = None
    else:
        resbody = res.json()
        data = resbody["data"]
    examples[key] = {
        'method': method,
        'path': path,
        'url': unsignedUrl,
        'reqbody': reqbody,
        'statusCode': res.status_code,
        'resbody': resbody,
        'data': data
    }
    return res


def printFullDoc():
    for k in toc:
        doc = fmtDoc(k)
        print(doc)


def fmtDoc(key):
    ex = copy(examples[key])
    ex["reqbody"] = stringify(ex["reqbody"])
    ex["resbody"] = stringify(ex["resbody"])
    ex['resbody'] = re.sub(r'http://localhost:3000', 'https://nog.zib.de',
                           ex['resbody'])
    ex["data"] = stringify(ex["data"])
    # Change Python format fields to Express-style params:
    # `/api/{foo}/db` -> `/api/:foo/db`.
    ex["path"] = re.sub(r'[{]([^}]+)[}]', r':\1', ex["path"])
    return dedent(doc[key]).format(**ex)


def stringify(data):
    return json.dumps(data, indent=2, sort_keys=True)


def unusedRepoSelector():
    sel = {
        'repoOwner': userName,
        'repoName': repoName
    }
    i = 0
    while True:
        try:
            getRef(sel, 'branches/master')
        except ApiError:
            return sel
        i = i + 1
        sel['repoName'] = repoName + '-' + str(i)


def sign_req(method, url):
    res = check_output(['../../tools/bin/sign-req', method, url])
    return res.decode('utf-8').rstrip()


main()
