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)