Rename to stahg-gopher.py - stahg-gopher - Static Mercurial page generator for gopher
 (HTM) hg clone https://bitbucket.org/iamleot/stahg-gopher
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) changeset 4e8d26dffa31a5ef7e5012f8060910e53452dee8
 (DIR) parent 952e00b3a83e846a78c6f8669747d8fa6c957f82
 (HTM) Author: Leonardo Taccari <iamleot@gmail.com>
       Date:   Sun, 12 May 2019 22:21:34 
       
       Rename to stahg-gopher.py
       
       Diffstat:
        stahg-gopher.py |  337 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        stahg.py        |  337 --------------------------------------------------------
        2 files changed, 337 insertions(+), 337 deletions(-)
       ---
       diff -r 952e00b3a83e -r 4e8d26dffa31 stahg-gopher.py
       --- /dev/null   Thu Jan 01 00:00:00 1970 +0000
       +++ b/stahg-gopher.py   Sun May 12 22:21:34 2019 +0200
       @@ -0,0 +1,337 @@
       +#!/usr/bin/env python3.7
       +
       +#
       +# Copyright (c) 2019 Leonardo Taccari
       +# All rights reserved.
       +# 
       +# Redistribution and use in source and binary forms, with or without
       +# modification, are permitted provided that the following conditions
       +# are met:
       +# 
       +# 1. Redistributions of source code must retain the above copyright
       +#    notice, this list of conditions and the following disclaimer.
       +# 2. Redistributions in binary form must reproduce the above copyright
       +#    notice, this list of conditions and the following disclaimer in the
       +#    documentation and/or other materials provided with the distribution.
       +# 
       +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
       +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
       +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
       +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
       +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
       +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
       +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
       +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
       +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
       +# POSSIBILITY OF SUCH DAMAGE.
       +#
       +
       +
       +import datetime
       +import os
       +import shutil
       +import stat
       +
       +import hglib
       +
       +
       +LICENSE_FILES = [ 'LICENSE', 'LICENSE.md', 'COPYING' ]
       +README_FILES = [ 'README', 'README.md' ]
       +
       +
       +def gph_escape_entry(text):
       +    """Render text entry `[...]' by escaping/translating characters"""
       +    escaped_text = text.expandtabs().replace('|', '\|')
       +
       +    return escaped_text
       +
       +
       +def gph_escape_text(text):
       +    """Render text to .gph by escaping/translating characters"""
       +    escaped_text = []
       +
       +    for line in text.expandtabs().splitlines():
       +        # add leading 't' if needed
       +        if len(line) > 0 and line[0] == 't':
       +            line = 't' + line
       +
       +        escaped_text.append(line)
       +
       +    return '\n'.join(escaped_text)
       +
       +
       +def shorten(text, n=80):
       +    """Shorten text to the first `n' character of first line"""
       +    s, _, _ = text.partition('\n')
       +
       +    if len(s) > n:
       +        s = s[:n - 1] + '…'
       +
       +    return s
       +
       +
       +def rshorten(text, n=80):
       +    """Shorten text to the last `n' character of first line"""
       +    s, _, _ = text.partition('\n')
       +
       +    if len(s) > n:
       +        s = '…' + s[- (n - 1):] 
       +
       +    return s
       +
       +
       +def author_name(author):
       +    """Given an author `Name <email>' extract their name"""
       +    name, _, _ = author.rpartition(' <')
       +
       +    return name
       +
       +
       +def author_email(author):
       +    """Given an author `Name <email>' extract their email"""
       +    _, _, email = author.rpartition(' <')
       +    email = email.rstrip('>')
       +
       +    return email
       +
       +
       +class Stahg:
       +    def __init__(self, base_prefix='', limit=None):
       +        self.base_prefix = base_prefix
       +        self.client = None
       +        self.description = ''
       +        self.license = None
       +        self.limit = limit
       +        self.readme = None
       +        self.repodir = ''
       +        self.repository = ''
       +        self.url = ''
       +
       +
       +    def open(self, repodir):
       +        """Open repository in repodir"""
       +        self.repodir = os.path.normpath(repodir)
       +        self.client = hglib.open(self.repodir)
       +        self.base_prefix = base_prefix
       +        self.repository = os.path.basename(self.repodir)
       +
       +        try:
       +            for _, k, value in self.client.config([b'web']):
       +                if k == 'description':
       +                    self.description = value
       +                    break
       +        except:
       +            self.description = \
       +                "Unnamed repository, adjust .hg/hgrc `[web]' section, `description' key"
       +
       +        # XXX: For repository with a lot of files this is suboptimal...
       +        # XXX: Is there a simpler way to check for that?
       +        for e in self.client.manifest(rev=b'tip'):
       +            fpath = e[4].decode()
       +
       +            # file paths are sorted, break as soon as possible
       +            if fpath > max(LICENSE_FILES) and fpath > max(README_FILES):
       +                break
       +
       +            if fpath in LICENSE_FILES:
       +                self.license = fpath
       +            if fpath in README_FILES:
       +                self.readme = fpath
       +
       +
       +    def close(self):
       +        """Close repository"""
       +        self.client.close()
       +
       +
       +    def menu(self):
       +        """Generate menu for .gph files"""
       +        bp = gph_escape_entry(self.base_prefix)
       +
       +        m = '[1|Log|' + bp + '/log.gph|server|port]\n' + \
       +            '[1|Files|' + bp + '/files.gph|server|port]'
       +
       +        if self.readme:
       +            m += '\n[1|README|' + bp + '/file/{file}.gph|server|port]'.format(
       +                file=self.readme)
       +
       +        if self.license:
       +            m += '\n[1|LICENSE|' + bp + '/file/{file}.gph|server|port]'.format(
       +                file=self.license)
       +
       +        return m
       +
       +
       +    def title(self, text):
       +        """Generate title for .gph files"""
       +        return gph_escape_text(
       +            ' - '.join([text, self.repository, self.description]))
       +
       +
       +    def log(self):
       +        """Generate log.gph with latest commits"""
       +        bp = gph_escape_entry(self.base_prefix)
       +        fname = 'log.gph'
       +
       +        with open(fname, 'w') as f:
       +            print(self.title('Log'), file=f)
       +            print(self.menu(), file=f)
       +            print('---', file=f)
       +
       +            print('{:16}  {:40}  {}'.format('Date', 'Commit message', 'Author'),
       +                  file=f)
       +            for i, e in enumerate(self.client.log()):
       +                if self.limit and i > self.limit:
       +                    print('                  More commits remaining [...]',
       +                          file=f)
       +                    break
       +                print('[1|{desc}|{path}|server|port]'.format(
       +                    desc='{date:16}  {commit_message:40}  {author}'.format(
       +                        date=e.date.strftime('%Y-%m-%d %H:%M'),
       +                        commit_message=gph_escape_entry(shorten(e.desc.decode(), 40)),
       +                        author=author_name(e.author.decode())),
       +                    path='{base_path}/commit/{changeset}.gph'.format(
       +                        base_path=bp,
       +                        changeset=e.node.decode())), file=f)
       +
       +
       +    def files(self):
       +        """Generate files.gph with links to all files in `tip'"""
       +        bp = gph_escape_entry(self.base_prefix)
       +        fname = 'files.gph'
       +
       +        with open(fname, 'w') as f:
       +            print(self.title('Files'), file=f)
       +            print(self.menu(), file=f)
       +            print('---', file=f)
       +
       +            print('{:10}  {:68}'.format('Mode', 'Name'), file=f)
       +
       +            for e in self.client.manifest(rev=b'tip'):
       +                print('[1|{desc}|{path}|server|port]'.format(
       +                    desc='{mode:10}  {name:68}'.format(
       +                        mode=stat.filemode(int(e[1].decode(), base=8)),
       +                        name=gph_escape_entry(e[4].decode())),
       +                     path='{base_path}/file/{file}.gph'.format(
       +                        base_path=bp,
       +                        file=gph_escape_entry(e[4].decode()))), file=f)
       +
       +
       +    def refs(self):
       +        """Generate refs.gph listing all branches and tags"""
       +        pass # TODO
       +
       +
       +    def commit(self, changeset):
       +        """Generate commit/<changeset>.gph with commit message and diff"""
       +        bp = gph_escape_entry(self.base_prefix)
       +        c = self.client[changeset]
       +        fname = 'commit/{changeset}.gph'.format(changeset=c.node().decode())
       +
       +        with open(fname, 'w') as f:
       +            print(self.title(shorten(c.description().decode(), 80)), file=f)
       +            print(self.menu(), file=f)
       +            print('---', file=f)
       +
       +            print('[1|{desc}|{path}|server|port]'.format(
       +                desc='changeset {changeset}'.format(changeset=c.node().decode()),
       +                path='{base_path}/commit/{changeset}.gph'.format(
       +                    base_path=bp,
       +                    changeset=c.node().decode())), file=f)
       +
       +            for p in c.parents():
       +                if p.node() == b'0000000000000000000000000000000000000000':
       +                    continue
       +                print('[1|{desc}|{path}|server|port]'.format(
       +                    desc='parent {changeset}'.format(changeset=p.node().decode()),
       +                    path='{base_path}/commit/{changeset}.gph'.format(
       +                        base_path=bp,
       +                        changeset=p.node().decode())), file=f)
       +
       +            print('[h|Author: {author}|URL:mailto:{email}|server|port]'.format(
       +                author=gph_escape_entry(c.author().decode()),
       +                email=gph_escape_entry(author_email(c.author().decode()))), file=f)
       +
       +            print('Date:   {date}'.format(
       +                date=c.date().strftime('%a, %e %b %Y %H:%M:%S %z')), file=f)
       +
       +            print(file=f)
       +            print(gph_escape_text(c.description().decode()), file=f)
       +            print(file=f)
       +
       +            print('Diffstat:', file=f)
       +            print(gph_escape_text(self.client.diff(change=c.node(), stat=True).decode().rstrip()),
       +                  file=f)
       +            print('---', file=f)
       +
       +            print(gph_escape_text(self.client.diff(change=c.node()).decode()),
       +                  file=f)
       +
       +
       +    def file(self, file):
       +        """Generate file/<file>.gph listing <file> at `tip'"""
       +        bp = gph_escape_entry(self.base_prefix)
       +        fname = 'file/{file}.gph'.format(file=file.decode())
       +        os.makedirs(os.path.dirname(fname), exist_ok=True)
       +
       +        with open(fname, 'w') as f:
       +            print(self.title(os.path.basename(file.decode())), file=f)
       +            print(self.menu(), file=f)
       +            print('---', file=f)
       +
       +            print('{filename}'.format(
       +                filename=os.path.basename(file.decode())), file=f)
       +            print('---', file=f)
       +
       +            files = [self.client.root() + os.sep.encode() + file]
       +            for num, line in enumerate(self.client.cat(files).decode().splitlines(), start=1):
       +                print('{num:6d} {line}'.format(
       +                    num=num,
       +                    line=gph_escape_text(line)), file=f)
       +
       +
       +if __name__ == '__main__':
       +    import getopt
       +    import sys
       +
       +    def usage():
       +        print('usage: {} [-b baseprefix] [-l commits] repodir'.format(
       +               sys.argv[0]))
       +        exit(1)
       +
       +    try:
       +        opts, args = getopt.getopt(sys.argv[1:], 'b:l:')
       +    except:
       +        usage()
       +
       +    if len(args) != 1:
       +        usage()
       +
       +    base_prefix = ''
       +    limit = None
       +    for o, a in opts:
       +        if o == '-b':
       +            base_prefix = a
       +        elif o == '-l':
       +            limit = int(a)
       +
       +    repodir = args[0]
       +
       +    sh = Stahg(base_prefix=base_prefix, limit=limit)
       +    sh.open(repodir)
       +
       +    sh.log()
       +    sh.files()
       +    sh.refs()
       +
       +    shutil.rmtree('file', ignore_errors=True)
       +    os.makedirs('file', exist_ok=True)
       +    for e in sh.client.manifest(rev=b'tip'):
       +        sh.file(e[4])
       +
       +    os.makedirs('commit', exist_ok=True)
       +    for e in sh.client.log():
       +        if os.path.exists('commit/{changeset}.gph'.format(changeset=e.node.decode())):
       +            break
       +        sh.commit(e.node)
       diff -r 952e00b3a83e -r 4e8d26dffa31 stahg.py
       --- a/stahg.py  Sun May 12 21:49:58 2019 +0200
       +++ /dev/null   Thu Jan 01 00:00:00 1970 +0000
       @@ -1,337 +0,0 @@
       -#!/usr/bin/env python3.7
       -
       -#
       -# Copyright (c) 2019 Leonardo Taccari
       -# All rights reserved.
       -# 
       -# Redistribution and use in source and binary forms, with or without
       -# modification, are permitted provided that the following conditions
       -# are met:
       -# 
       -# 1. Redistributions of source code must retain the above copyright
       -#    notice, this list of conditions and the following disclaimer.
       -# 2. Redistributions in binary form must reproduce the above copyright
       -#    notice, this list of conditions and the following disclaimer in the
       -#    documentation and/or other materials provided with the distribution.
       -# 
       -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
       -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
       -# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
       -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
       -# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
       -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
       -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
       -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
       -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
       -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
       -# POSSIBILITY OF SUCH DAMAGE.
       -#
       -
       -
       -import datetime
       -import os
       -import shutil
       -import stat
       -
       -import hglib
       -
       -
       -LICENSE_FILES = [ 'LICENSE', 'LICENSE.md', 'COPYING' ]
       -README_FILES = [ 'README', 'README.md' ]
       -
       -
       -def gph_escape_entry(text):
       -    """Render text entry `[...]' by escaping/translating characters"""
       -    escaped_text = text.expandtabs().replace('|', '\|')
       -
       -    return escaped_text
       -
       -
       -def gph_escape_text(text):
       -    """Render text to .gph by escaping/translating characters"""
       -    escaped_text = []
       -
       -    for line in text.expandtabs().splitlines():
       -        # add leading 't' if needed
       -        if len(line) > 0 and line[0] == 't':
       -            line = 't' + line
       -
       -        escaped_text.append(line)
       -
       -    return '\n'.join(escaped_text)
       -
       -
       -def shorten(text, n=80):
       -    """Shorten text to the first `n' character of first line"""
       -    s, _, _ = text.partition('\n')
       -
       -    if len(s) > n:
       -        s = s[:n - 1] + '…'
       -
       -    return s
       -
       -
       -def rshorten(text, n=80):
       -    """Shorten text to the last `n' character of first line"""
       -    s, _, _ = text.partition('\n')
       -
       -    if len(s) > n:
       -        s = '…' + s[- (n - 1):] 
       -
       -    return s
       -
       -
       -def author_name(author):
       -    """Given an author `Name <email>' extract their name"""
       -    name, _, _ = author.rpartition(' <')
       -
       -    return name
       -
       -
       -def author_email(author):
       -    """Given an author `Name <email>' extract their email"""
       -    _, _, email = author.rpartition(' <')
       -    email = email.rstrip('>')
       -
       -    return email
       -
       -
       -class Stahg:
       -    def __init__(self, base_prefix='', limit=None):
       -        self.base_prefix = base_prefix
       -        self.client = None
       -        self.description = ''
       -        self.license = None
       -        self.limit = limit
       -        self.readme = None
       -        self.repodir = ''
       -        self.repository = ''
       -        self.url = ''
       -
       -
       -    def open(self, repodir):
       -        """Open repository in repodir"""
       -        self.repodir = os.path.normpath(repodir)
       -        self.client = hglib.open(self.repodir)
       -        self.base_prefix = base_prefix
       -        self.repository = os.path.basename(self.repodir)
       -
       -        try:
       -            for _, k, value in self.client.config([b'web']):
       -                if k == 'description':
       -                    self.description = value
       -                    break
       -        except:
       -            self.description = \
       -                "Unnamed repository, adjust .hg/hgrc `[web]' section, `description' key"
       -
       -        # XXX: For repository with a lot of files this is suboptimal...
       -        # XXX: Is there a simpler way to check for that?
       -        for e in self.client.manifest(rev=b'tip'):
       -            fpath = e[4].decode()
       -
       -            # file paths are sorted, break as soon as possible
       -            if fpath > max(LICENSE_FILES) and fpath > max(README_FILES):
       -                break
       -
       -            if fpath in LICENSE_FILES:
       -                self.license = fpath
       -            if fpath in README_FILES:
       -                self.readme = fpath
       -
       -
       -    def close(self):
       -        """Close repository"""
       -        self.client.close()
       -
       -
       -    def menu(self):
       -        """Generate menu for .gph files"""
       -        bp = gph_escape_entry(self.base_prefix)
       -
       -        m = '[1|Log|' + bp + '/log.gph|server|port]\n' + \
       -            '[1|Files|' + bp + '/files.gph|server|port]'
       -
       -        if self.readme:
       -            m += '\n[1|README|' + bp + '/file/{file}.gph|server|port]'.format(
       -                file=self.readme)
       -
       -        if self.license:
       -            m += '\n[1|LICENSE|' + bp + '/file/{file}.gph|server|port]'.format(
       -                file=self.license)
       -
       -        return m
       -
       -
       -    def title(self, text):
       -        """Generate title for .gph files"""
       -        return gph_escape_text(
       -            ' - '.join([text, self.repository, self.description]))
       -
       -
       -    def log(self):
       -        """Generate log.gph with latest commits"""
       -        bp = gph_escape_entry(self.base_prefix)
       -        fname = 'log.gph'
       -
       -        with open(fname, 'w') as f:
       -            print(self.title('Log'), file=f)
       -            print(self.menu(), file=f)
       -            print('---', file=f)
       -
       -            print('{:16}  {:40}  {}'.format('Date', 'Commit message', 'Author'),
       -                  file=f)
       -            for i, e in enumerate(self.client.log()):
       -                if self.limit and i > self.limit:
       -                    print('                  More commits remaining [...]',
       -                          file=f)
       -                    break
       -                print('[1|{desc}|{path}|server|port]'.format(
       -                    desc='{date:16}  {commit_message:40}  {author}'.format(
       -                        date=e.date.strftime('%Y-%m-%d %H:%M'),
       -                        commit_message=gph_escape_entry(shorten(e.desc.decode(), 40)),
       -                        author=author_name(e.author.decode())),
       -                    path='{base_path}/commit/{changeset}.gph'.format(
       -                        base_path=bp,
       -                        changeset=e.node.decode())), file=f)
       -
       -
       -    def files(self):
       -        """Generate files.gph with links to all files in `tip'"""
       -        bp = gph_escape_entry(self.base_prefix)
       -        fname = 'files.gph'
       -
       -        with open(fname, 'w') as f:
       -            print(self.title('Files'), file=f)
       -            print(self.menu(), file=f)
       -            print('---', file=f)
       -
       -            print('{:10}  {:68}'.format('Mode', 'Name'), file=f)
       -
       -            for e in self.client.manifest(rev=b'tip'):
       -                print('[1|{desc}|{path}|server|port]'.format(
       -                    desc='{mode:10}  {name:68}'.format(
       -                        mode=stat.filemode(int(e[1].decode(), base=8)),
       -                        name=gph_escape_entry(e[4].decode())),
       -                     path='{base_path}/file/{file}.gph'.format(
       -                        base_path=bp,
       -                        file=gph_escape_entry(e[4].decode()))), file=f)
       -
       -
       -    def refs(self):
       -        """Generate refs.gph listing all branches and tags"""
       -        pass # TODO
       -
       -
       -    def commit(self, changeset):
       -        """Generate commit/<changeset>.gph with commit message and diff"""
       -        bp = gph_escape_entry(self.base_prefix)
       -        c = self.client[changeset]
       -        fname = 'commit/{changeset}.gph'.format(changeset=c.node().decode())
       -
       -        with open(fname, 'w') as f:
       -            print(self.title(shorten(c.description().decode(), 80)), file=f)
       -            print(self.menu(), file=f)
       -            print('---', file=f)
       -
       -            print('[1|{desc}|{path}|server|port]'.format(
       -                desc='changeset {changeset}'.format(changeset=c.node().decode()),
       -                path='{base_path}/commit/{changeset}.gph'.format(
       -                    base_path=bp,
       -                    changeset=c.node().decode())), file=f)
       -
       -            for p in c.parents():
       -                if p.node() == b'0000000000000000000000000000000000000000':
       -                    continue
       -                print('[1|{desc}|{path}|server|port]'.format(
       -                    desc='parent {changeset}'.format(changeset=p.node().decode()),
       -                    path='{base_path}/commit/{changeset}.gph'.format(
       -                        base_path=bp,
       -                        changeset=p.node().decode())), file=f)
       -
       -            print('[h|Author: {author}|URL:mailto:{email}|server|port]'.format(
       -                author=gph_escape_entry(c.author().decode()),
       -                email=gph_escape_entry(author_email(c.author().decode()))), file=f)
       -
       -            print('Date:   {date}'.format(
       -                date=c.date().strftime('%a, %e %b %Y %H:%M:%S %z')), file=f)
       -
       -            print(file=f)
       -            print(gph_escape_text(c.description().decode()), file=f)
       -            print(file=f)
       -
       -            print('Diffstat:', file=f)
       -            print(gph_escape_text(self.client.diff(change=c.node(), stat=True).decode().rstrip()),
       -                  file=f)
       -            print('---', file=f)
       -
       -            print(gph_escape_text(self.client.diff(change=c.node()).decode()),
       -                  file=f)
       -
       -
       -    def file(self, file):
       -        """Generate file/<file>.gph listing <file> at `tip'"""
       -        bp = gph_escape_entry(self.base_prefix)
       -        fname = 'file/{file}.gph'.format(file=file.decode())
       -        os.makedirs(os.path.dirname(fname), exist_ok=True)
       -
       -        with open(fname, 'w') as f:
       -            print(self.title(os.path.basename(file.decode())), file=f)
       -            print(self.menu(), file=f)
       -            print('---', file=f)
       -
       -            print('{filename}'.format(
       -                filename=os.path.basename(file.decode())), file=f)
       -            print('---', file=f)
       -
       -            files = [self.client.root() + os.sep.encode() + file]
       -            for num, line in enumerate(self.client.cat(files).decode().splitlines(), start=1):
       -                print('{num:6d} {line}'.format(
       -                    num=num,
       -                    line=gph_escape_text(line)), file=f)
       -
       -
       -if __name__ == '__main__':
       -    import getopt
       -    import sys
       -
       -    def usage():
       -        print('usage: {} [-b baseprefix] [-l commits] repodir'.format(
       -               sys.argv[0]))
       -        exit(1)
       -
       -    try:
       -        opts, args = getopt.getopt(sys.argv[1:], 'b:l:')
       -    except:
       -        usage()
       -
       -    if len(args) != 1:
       -        usage()
       -
       -    base_prefix = ''
       -    limit = None
       -    for o, a in opts:
       -        if o == '-b':
       -            base_prefix = a
       -        elif o == '-l':
       -            limit = int(a)
       -
       -    repodir = args[0]
       -
       -    sh = Stahg(base_prefix=base_prefix, limit=limit)
       -    sh.open(repodir)
       -
       -    sh.log()
       -    sh.files()
       -    sh.refs()
       -
       -    shutil.rmtree('file', ignore_errors=True)
       -    os.makedirs('file', exist_ok=True)
       -    for e in sh.client.manifest(rev=b'tip'):
       -        sh.file(e[4])
       -
       -    os.makedirs('commit', exist_ok=True)
       -    for e in sh.client.log():
       -        if os.path.exists('commit/{changeset}.gph'.format(changeset=e.node.decode())):
       -            break
       -        sh.commit(e.node)