#!/usr/bin/env python
# update_libs.py
#
# Retrieve the versions of packages from Arch Linux's repositories and update
# Dockerfile as needed.
#
# The code in documentation comments can also be used to test the functions by
# running "python -m doctest update_libs.py -v".

from __future__ import print_function

try:
    # Python 3
    import urllib.request as urllib
except ImportError:
    # Python 2
    import urllib

import json
import os
import re


def convert_openssl_version(version):
    """ Convert OpenSSL package versions to match upstream's format

    >>> convert_openssl_version('1.0.2.o')
    '1.0.2o'
    """

    return re.sub(r'(.+)\.([a-z])', r'\1\2', version)


def convert_sqlite_version(version):
    """Convert SQLite package versions to match upstream's format

    >>> convert_sqlite_version('3.24.0')
    '3240000'
    """

    matches = re.match(r'(\d+)\.(\d+)\.(\d+)', version)
    return '{:d}{:02d}{:02d}00'.format(int(matches.group(1)), int(matches.group(2)), int(matches.group(3)))


def pkgver(package):
    """Retrieve the current version of the package in Arch Linux repos

    API documentation: https://wiki.archlinux.org/index.php/Official_repositories_web_interface

    The "str" call is only needed to make the test pass on Python 2 and 3, you
    do not need to include it when using this function.

    >>> str(pkgver('reiserfsprogs'))
    '3.6.27'
    """

    # Though the URL contains "/search/", this only returns exact matches (see API documentation)
    url = 'https://www.archlinux.org/packages/search/json/?name={}'.format(package)
    req = urllib.urlopen(url)
    metadata = json.loads(req.read())
    req.close()
    try:
        return metadata['results'][0]['pkgver']
    except IndexError:
        raise NameError('Package not found: {}'.format(package))


if __name__ == '__main__':
    PACKAGES = {
        'CURL': pkgver('curl'),
        'PQ': pkgver('postgresql-old-upgrade'),
        'SQLITE': convert_sqlite_version(pkgver('sqlite')),
        'SSL': convert_openssl_version(pkgver('openssl-1.0')),
        'ZLIB': pkgver('zlib'),
    }

    # Show a list of packages with current versions
    for prefix in PACKAGES:
        print('{}_VER="{}"'.format(prefix, PACKAGES[prefix]))

    # Open a different file for the destination to update Dockerfile atomically
    src = open('Dockerfile', 'r')
    dst = open('Dockerfile.new', 'w')

    # Iterate over each line in Dockerfile, replacing any *_VER variables with the most recent version
    for line in src:
        for prefix in PACKAGES:
            version = PACKAGES[prefix]
            line = re.sub(r'({}_VER=)\S+'.format(prefix), r'\1"{}"'.format(version), line)
        dst.write(line)

    # Close original and new Dockerfile then overwrite the old with the new
    src.close()
    dst.close()
    os.rename('Dockerfile.new', 'Dockerfile')

Add script to update library versions in Dockerfile (#38)