diff --git a/assets/icomoon/SVG/facebook.svg b/assets/icomoon/SVG/facebook.svg new file mode 100644 index 0000000..922200d --- /dev/null +++ b/assets/icomoon/SVG/facebook.svg @@ -0,0 +1,5 @@ + + +facebook + + diff --git a/assets/nojs-button.svg b/assets/nojs-button.svg new file mode 100644 index 0000000..44399e6 --- /dev/null +++ b/assets/nojs-button.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/meta.py b/meta.py index 5b68e36..02e6241 100644 --- a/meta.py +++ b/meta.py @@ -18,6 +18,7 @@ EXIFDATE = re.compile( r'(?P[0-9]{2}:[0-9]{2}:[0-9]{2})$' ) + class CachedMeta(dict): def __init__(self, fpath): self.fpath = fpath @@ -25,7 +26,7 @@ class CachedMeta(dict): @property def cfile(self): fname = os.path.basename(self.fpath) - if fname == 'index.md': + if fname == 'index.md': fname = os.path.basename(os.path.dirname(self.fpath)) return os.path.join( diff --git a/nasg.py b/nasg.py index 76b8770..365b9ed 100644 --- a/nasg.py +++ b/nasg.py @@ -21,6 +21,7 @@ from math import ceil from urllib.parse import urlparse from collections import OrderedDict, namedtuple import logging +import csv import arrow import langdetect @@ -31,8 +32,9 @@ import frontmatter from feedgen.feed import FeedGenerator from slugify import slugify import requests +import lxml.etree as etree -from pandoc import PandocMarkdown +from pandoc import PandocMarkdown, PandocTXT from meta import Exif import settings from settings import struct @@ -40,6 +42,12 @@ import keys logger = logging.getLogger('NASG') +CATEGORY = 'category' +MDFILE = 'index.md' +TXTFILE = 'index.txt' +HTMLFILE = 'index.html' +GOPHERFILE = 'gophermap' + MarkdownImage = namedtuple( 'MarkdownImage', ['match', 'alt', 'fname', 'title', 'css'] @@ -72,6 +80,7 @@ def mtime(path): return int(os.path.getmtime(path)) return 0 + def utfyamldump(data): """ dump YAML with actual UTF-8 chars """ return yaml.dump( @@ -81,6 +90,7 @@ def utfyamldump(data): allow_unicode=True ) + def url2slug(url, limit=200): """ convert URL to max 200 char ASCII string """ return slugify( @@ -89,13 +99,16 @@ def url2slug(url, limit=200): lower=True )[:limit] + J2.filters['url2slug'] = url2slug + def rfc3339todt(rfc3339): """ nice dates for humans """ t = arrow.get(rfc3339).format('YYYY-MM-DD HH:mm ZZZ') return "%s" % (t) + J2.filters['printdate'] = rfc3339todt RE_MYURL = re.compile( @@ -105,6 +118,7 @@ RE_MYURL = re.compile( ) ) + def relurl(text, baseurl=None): if not baseurl: baseurl = settings.site.url @@ -118,15 +132,17 @@ def relurl(text, baseurl=None): r = os.path.relpath(url, baseurl) if url.endswith('/') and not r.endswith('/'): - r = "%s/index.html" % r + r = "%s/%s" % (r, HTMLFILE) if needsquotes: r = '"%s"' % r logger.debug("RELURL: %s => %s (base: %s)", match, r, baseurl) text = text.replace(match, r) return text + J2.filters['relurl'] = relurl + def writepath(fpath, content, mtime=0): """ f.write with extras """ d = os.path.dirname(fpath) @@ -163,6 +179,7 @@ class cached_property(object): class AQ: """ Async queue which starts execution right on population """ + def __init__(self): self.loop = asyncio.get_event_loop() self.queue = asyncio.Queue(loop=self.loop) @@ -174,7 +191,7 @@ class AQ: while not self.queue.empty(): item = await self.queue.get() self.queue.task_done() - #asyncio.gather() ? + # asyncio.gather() ? def run(self): consumer = asyncio.ensure_future(self.consume()) @@ -183,6 +200,7 @@ class AQ: class Webmention(object): """ outgoing webmention class """ + def __init__(self, source, target, dpath, mtime=0): self.source = source self.target = target @@ -275,7 +293,7 @@ class MarkdownDoc(object): def pandoc(self, c): if c and len(c): - c = PandocMarkdown(c) + c = str(PandocMarkdown(c)) c = RE_PRECODE.sub( '', c) return c @@ -305,7 +323,9 @@ class Comment(MarkdownDoc): @property def targetname(self): t = urlparse(self.meta.get('target')) - return t.path.rstrip('/').strip('/').split('/')[-1] + return os.path.split(t.path.lstrip('/'))[0] + #t = urlparse(self.meta.get('target')) + #return t.path.rstrip('/').strip('/').split('/')[-1] @property def source(self): @@ -335,11 +355,10 @@ class Comment(MarkdownDoc): @property def type(self): return self.meta.get('type', 'webmention') - #if len(self.content): - #maybe = clean(self.content, strip=True) - #if maybe in UNICODE_EMOJI: - #return maybe - + # if len(self.content): + #maybe = clean(self.content, strip=True) + # if maybe in UNICODE_EMOJI: + # return maybe @cached_property def jsonld(self): @@ -418,10 +437,24 @@ class Singular(MarkdownDoc): maybe = c.dt return maybe + + @property + def dt(self): + dt = int(MarkdownDoc.dt.fget(self)) + for maybe in self.comments.keys(): + if int(dt) < int(maybe): + dt = int(maybe) + return dt + @property def sameas(self): r = [] - for k in glob.glob(os.path.join(os.path.dirname(self.fpath), '*.copy')): + for k in glob.glob( + os.path.join( + os.path.dirname(self.fpath), + '*.copy' + ) + ): with open(k, 'rt') as f: r.append(f.read()) return r @@ -436,7 +469,7 @@ class Singular(MarkdownDoc): files = [ k for k in glob.glob(os.path.join(os.path.dirname(self.fpath), '*.md')) - if os.path.basename(k) != 'index.md' + if os.path.basename(k) != MDFILE ] for f in files: c = Comment(f) @@ -463,9 +496,9 @@ class Singular(MarkdownDoc): images.update({match: WebImage(imgpath, mdimg, self)}) else: logger.error("Missing image: %s, referenced in %s", - imgpath, - self.fpath - ) + imgpath, + self.fpath + ) return images @property @@ -493,7 +526,7 @@ class Singular(MarkdownDoc): if len(self.images) != 1: return False photo = next(iter(self.images.values())) - maybe = self.fpath.replace("index.md", "%s.jpg" % (self.name)) + maybe = self.fpath.replace(MDFILE, "%s.jpg" % (self.name)) if photo.fpath == maybe: return True return False @@ -630,46 +663,46 @@ class Singular(MarkdownDoc): else: return False - #@cached_property - #def oembed_xml(self): - #oembed = etree.Element("oembed", version="1.0") - #xmldoc = etree.ElementTree(oembed) - #for k, v in self.oembed_json.items(): - #x = etree.SubElement(oembed, k).text = "%s" % (v) - #s = etree.tostring( - #xmldoc, - #encoding='utf-8', - #xml_declaration=True, - #pretty_print=True - #) - #return s + @cached_property + def oembed_xml(self): + oembed = etree.Element("oembed", version="1.0") + xmldoc = etree.ElementTree(oembed) + for k, v in self.oembed_json.items(): + x = etree.SubElement(oembed, k).text = "%s" % (v) + s = etree.tostring( + xmldoc, + encoding='utf-8', + xml_declaration=True, + pretty_print=True + ) + return s - #@cached_property - #def oembed_json(self): - #r = { - #"version": "1.0", - #"provider_name": settings.site.name, - #"provider_url": settings.site.url, - #"author_name": settings.author.name, - #"author_url": settings.author.url, - #"title": self.title, - #"type": "link", - #"html": self.html_content, - #} + @cached_property + def oembed_json(self): + r = { + "version": "1.0", + "provider_name": settings.site.name, + "provider_url": settings.site.url, + "author_name": settings.author.name, + "author_url": settings.author.url, + "title": self.title, + "type": "link", + "html": self.html_content, + } - #img = None - #if self.is_photo: - #img = self.photo - #elif not self.is_photo and len(self.images): - #img = list(self.images.values())[0] - #if img: - #r.update({ - #"type": "rich", - #"thumbnail_url": img.jsonld.thumbnail.url, - #"thumbnail_width": img.jsonld.thumbnail.width, - #"thumbnail_height": img.jsonld.thumbnail.height - #}) - #return r + img = None + if self.is_photo: + img = self.photo + elif not self.is_photo and len(self.images): + img = list(self.images.values())[0] + if img: + r.update({ + "type": "rich", + "thumbnail_url": img.jsonld.thumbnail.url, + "thumbnail_width": img.jsonld.thumbnail.width, + "thumbnail_height": img.jsonld.thumbnail.height + }) + return r @cached_property def review(self): @@ -744,7 +777,7 @@ class Singular(MarkdownDoc): if self.is_photo: r.update({ "@type": "Photograph", - "image": self.photo.jsonld, + #"image": self.photo.jsonld, }) elif self.has_code: r.update({ @@ -754,11 +787,15 @@ class Singular(MarkdownDoc): r.update({ "@type": "WebPage", }) - if not self.is_photo and len(self.images): - img = list(self.images.values())[0] - r.update({ - "image": img.jsonld, - }) + if len(self.images): + r["image"] = [] + for img in list(self.images.values()): + r["image"].append(img.jsonld) + # if not self.is_photo and len(self.images): + # img = list(self.images.values())[0] + # r.update({ + # "image": img.jsonld, + # }) if self.is_reply: r.update({ @@ -775,8 +812,8 @@ class Singular(MarkdownDoc): if self.event: r.update({"subjectOf": self.event}) - for donation in settings.donateActions: - r["potentialAction"].append(donation) + #for donation in settings.donateActions: + #r["potentialAction"].append(donation) for url in list(set(self.syndicate)): r["potentialAction"].append({ @@ -794,16 +831,29 @@ class Singular(MarkdownDoc): def template(self): return "%s.j2.html" % (self.__class__.__name__) + @property + def gophertemplate(self): + return "%s.j2.txt" % (self.__class__.__name__) + @property def renderdir(self): - return os.path.dirname(self.renderfile) + return os.path.join( + settings.paths.get('build'), + self.name + ) @property def renderfile(self): return os.path.join( - settings.paths.get('build'), - self.name, - 'index.html' + self.renderdir, + HTMLFILE + ) + + @property + def gopherfile(self): + return os.path.join( + self.renderdir, + TXTFILE ) @property @@ -831,7 +881,15 @@ class Singular(MarkdownDoc): ]) async def copyfiles(self): - exclude = ['.md', '.jpg', '.png', '.gif', '.ping', '.url', '.del', '.copy'] + exclude = [ + '.md', + '.jpg', + '.png', + '.gif', + '.ping', + '.url', + '.del', + '.copy'] files = glob.glob( os.path.join( os.path.dirname(self.fpath), @@ -854,39 +912,41 @@ class Singular(MarkdownDoc): logger.info("copying '%s' to '%s'", f, t) cp(f, t) - @cached_property - def html(self): - r = J2.get_template(self.template).render({ + async def render(self): + if self.exists: + return + logger.info("rendering %s", self.name) + v = { 'baseurl': self.url, 'post': self.jsonld, 'site': settings.site, 'menu': settings.menu, 'meta': settings.meta, - }) - return r - - async def render(self): - if self.exists: - return - logger.info("rendering %s", self.name) + } writepath( self.renderfile, - self.html + J2.get_template(self.template).render(v) ) + + g = { + 'post': self.jsonld, + 'summary': PandocTXT(self.summary), + 'content': PandocTXT(self.content) + } + writepath( + self.gopherfile, + J2.get_template(self.gophertemplate).render(g) + ) + j = settings.site.copy() j.update({ "mainEntity": self.jsonld }) writepath( - os.path.join(self.renderdir,'index.json'), + os.path.join(self.renderdir, 'index.json'), json.dumps(j, indent=4, ensure_ascii=False) ) del(j) - cp( - self.fpath, - os.path.join(self.renderdir,'index.md') - ) - class Home(Singular): def __init__(self, fpath): @@ -904,7 +964,7 @@ class Home(Singular): def renderfile(self): return os.path.join( settings.paths.get('build'), - 'index.html' + HTMLFILE ) @property @@ -916,6 +976,27 @@ class Home(Singular): maybe = pts return maybe + async def render_gopher(self): + lines = [ + "%s's gopherhole - phlog, if you prefer" % (settings.site.name), + '', + '' + ] + + for category, post in self.posts: + line = "1%s\t/%s/%s\t%s\t70" % ( + category['name'], + CATEGORY, + category['name'], + settings.site.name + ) + lines.append(line) + lines.append('') + lines.append('') + lines = lines + list(map(lambda x: ("%s" % x), settings.bye.split('\n'))) + lines.append('') + writepath(self.renderfile.replace(HTMLFILE,GOPHERFILE), "\r\n".join(lines)) + async def render(self): if self.exists: return @@ -929,6 +1010,7 @@ class Home(Singular): 'posts': self.posts }) writepath(self.renderfile, r) + await self.render_gopher() class WebImage(object): @@ -1111,7 +1193,7 @@ class WebImage(object): 'Model': ['Model'], 'FNumber': ['FNumber', 'Aperture'], 'ExposureTime': ['ExposureTime'], - 'FocalLength': ['FocalLength'], # ['FocalLengthIn35mmFormat'], + 'FocalLength': ['FocalLength'], # ['FocalLengthIn35mmFormat'], 'ISO': ['ISO'], 'LensID': ['LensID', 'LensSpec', 'Lens'], 'CreateDate': ['CreateDate', 'DateTimeOriginal'] @@ -1189,7 +1271,8 @@ class WebImage(object): def data(self): with open(self.fpath, 'rb') as f: encoded = base64.b64encode(f.read()) - return "data:%s;base64,%s" % (self.parent.mime_type, encoded.decode('utf-8')) + return "data:%s;base64,%s" % ( + self.parent.mime_type, encoded.decode('utf-8')) @property def suffix(self): @@ -1328,8 +1411,8 @@ class PHPFile(object): raise ValueError('Not implemented') async def render(self): - #if self.exists: - #return + # if self.exists: + # return await self._render() @@ -1359,7 +1442,7 @@ class Search(PHPFile): notindexed=mtime, tokenize=porter )''' - ) + ) self.is_changed = False def __exit__(self): @@ -1548,7 +1631,7 @@ class Category(dict): @property def url(self): if len(self.name): - url = "%s/category/%s/" % (settings.site.get('url'), self.name) + url = "%s/%s/%s/" % (settings.site.get('url'), CATEGORY, self.name) else: url = '%s/' % (settings.site.get('url')) return url @@ -1566,7 +1649,7 @@ class Category(dict): if len(self.name): return os.path.join( settings.paths.get('build'), - 'category', + CATEGORY, self.name ) else: @@ -1665,17 +1748,17 @@ class Category(dict): 'posts': posts, } - def indexfpath(self, subpath=None): + def indexfpath(self, subpath=None, fname=HTMLFILE): if subpath: return os.path.join( self.dpath, subpath, - 'index.html' + fname ) else: return os.path.join( self.dpath, - 'index.html' + fname ) async def render_feed(self, xmlformat): @@ -1711,8 +1794,9 @@ class Category(dict): fe.category({ 'term': post.category, 'label': post.category, - 'scheme': "%s/category/%s/" % ( + 'scheme': "%s/%s/%s/" % ( settings.site.get('url'), + CATEGORY, post.category ) }) @@ -1758,6 +1842,32 @@ class Category(dict): ) writepath(self.indexfpath(), r) + async def render_gopher(self): + lines = [ + '%s - %s' % (self.name, settings.site.name), + '', + '' + ] + for post in self.get_posts(): + line = "0%s\t/%s/%s\t%s\t70" % ( + post.headline, + post.name, + TXTFILE, + settings.site.name + ) + lines.append(line) + if isinstance(post['image'], list): + for img in post['image']: + line = "I%s\t/%s/%s\t%s\t70" % ( + img.headline, + post.name, + img.name, + settings.site.name + ) + lines.append(line) + lines.append('') + writepath(self.indexfpath(fname=GOPHERFILE), "\r\n".join(lines)) + async def render_archives(self): for year in self.years.keys(): if year == self.newest_year: @@ -1779,7 +1889,7 @@ class Category(dict): end = index if self.is_uptodate(fpath, self[self.sortedkeys[start]].dt): - logger.info("%s / %d is up to date", self.name, year) + logger.info("%s / %d is up to date", self.name, year) else: logger.info("updating %s / %d", self.name, year) logger.info("getting posts from %d to %d", start, end) @@ -1788,7 +1898,7 @@ class Category(dict): # I don't know why end needs the +1, but without that # some posts disappear # TODO figure this out... - self.get_posts(start, end+1), + self.get_posts(start, end + 1), tyear ) ) @@ -1819,9 +1929,10 @@ class Category(dict): self.name ) - async def render(self): await self.render_feeds() + if not self.is_uptodate(self.indexfpath(), self.newest()): + await self.render_gopher() if not self.is_paginated: if not self.is_uptodate(self.indexfpath(), self.newest()): logger.info( @@ -1829,6 +1940,7 @@ class Category(dict): self.name ) await self.render_flat() + else: logger.info( '%s flat index is up to date', @@ -1839,6 +1951,7 @@ class Category(dict): await self.render_archives() + class Sitemap(dict): @property def mtime(self): @@ -1875,7 +1988,7 @@ class WebmentionIO(object): newest = 0 content = settings.paths.get('content') for e in glob.glob(os.path.join(content, '*', '*', '*.md')): - if os.path.basename(e) == 'index.md': + if os.path.basename(e) == MDFILE: continue # filenames are like [received epoch]-[slugified source url].md try: @@ -1889,7 +2002,7 @@ class WebmentionIO(object): continue if mtime > newest: newest = mtime - return arrow.get(newest+1) + return arrow.get(newest + 1) def makecomment(self, webmention): if 'published_ts' in webmention.get('data'): @@ -1899,12 +2012,19 @@ class WebmentionIO(object): else: dt = arrow.get(webmention.get('data').get('published')) - slug = webmention.get('target').strip('/').split('/')[-1] + slug = os.path.split(urlparse(webmention.get('target')).path.lstrip('/'))[0] + # ignore selfpings if slug == settings.site.get('name'): return - fdir = glob.glob(os.path.join(settings.paths.get('content'), '*', slug)) + fdir = glob.glob( + os.path.join( + settings.paths.get('content'), + '*', + slug + ) + ) if not len(fdir): logger.error( "couldn't find post for incoming webmention: %s", @@ -1962,6 +2082,7 @@ class WebmentionIO(object): logger.error('failed to query webmention.io: %s', e) pass + def make(): start = int(round(time.time() * 1000)) last = 0 @@ -1989,7 +2110,7 @@ def make(): frontposts = Category() home = Home(settings.paths.get('home')) - for e in sorted(glob.glob(os.path.join(content, '*', '*', 'index.md'))): + for e in sorted(glob.glob(os.path.join(content, '*', '*', MDFILE))): post = Singular(e) # deal with images, if needed for i in post.images.values(): @@ -2060,11 +2181,18 @@ def make(): for e in glob.glob(os.path.join(content, '*.*')): if e.endswith('.md'): continue - t = os.path.join(settings.paths.get('build'),os.path.basename(e)) + t = os.path.join(settings.paths.get('build'), os.path.basename(e)) if os.path.exists(t) and mtime(e) <= mtime(t): continue cp(e, t) + # ... + #for url in settings.site.sameAs: + #if "dat://" in url: + #p = os.path.join(settings.paths.build, '.well-known', 'dat') + #if not os.path.exists(p): + #writepath(p, "%s\nTTL=3600" % (url)) + end = int(round(time.time() * 1000)) logger.info('process took %d ms' % (end - start)) diff --git a/pandoc.py b/pandoc.py index 258f265..d250c45 100644 --- a/pandoc.py +++ b/pandoc.py @@ -7,34 +7,43 @@ __email__ = "mail@petermolnar.net" import subprocess import logging -class PandocMarkdown(str): - def __new__(cls, text): - """ Pandoc command line call with piped in- and output """ - cmd = ( + +class PandocBase(str): + in_format = 'html' + in_options = [] + out_format = 'plain' + out_options = [] + columns = None + + def __init__(self, text): + self.source = text + conv_to = '--to=%s' % (self.out_format) + if (len(self.out_options)): + conv_to = '%s+%s' % ( + conv_to, + '+'.join(self.out_options) + ) + + conv_from = '--from=%s' % (self.in_format) + if (len(self.in_options)): + conv_from = '%s+%s' % ( + conv_from, + '+'.join(self.in_options) + ) + + cmd = [ 'pandoc', '-o-', - '--from=markdown+%s' % ( - '+'.join([ - 'footnotes', - 'pipe_tables', - 'strikeout', - #'superscript', - #'subscript', - 'raw_html', - 'definition_lists', - 'backtick_code_blocks', - 'fenced_code_attributes', - 'shortcut_reference_links', - 'lists_without_preceding_blankline', - 'autolink_bare_uris', - ]) - ), - '--to=html5', + conv_to, + conv_from, '--quiet', '--no-highlight' - ) + ] + if self.columns: + cmd.append(self.columns) + p = subprocess.Popen( - cmd, + tuple(cmd), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -48,46 +57,158 @@ class PandocMarkdown(str): stderr ) r = stdout.decode('utf-8').strip() - return str.__new__(cls, r) + self.result = r -class PandocHTML(str): - def __new__(cls, text): - """ Pandoc command line call with piped in- and output """ - cmd = ( - 'pandoc', - '-o-', - '--to=markdown+%s' % ( - '+'.join([ - 'footnotes', - 'pipe_tables', - 'strikeout', - #'superscript', - #'subscript', - 'raw_html', - 'definition_lists', - 'backtick_code_blocks', - 'fenced_code_attributes', - 'shortcut_reference_links', - 'lists_without_preceding_blankline', - 'autolink_bare_uris', - ]) - ), - '--from=html', - '--quiet', - ) - p = subprocess.Popen( - cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + def __str__(self): + return str(self.result) - stdout, stderr = p.communicate(input=text.encode()) - if stderr: - logging.warning( - "Error during pandoc covert:\n\t%s\n\t%s", - cmd, - stderr - ) - r = stdout.decode('utf-8').strip() - return str.__new__(cls, r) + def __repr__(self): + return str(self.result) + + +class PandocMarkdown(PandocBase): + in_format = 'markdown' + in_options = [ + 'footnotes', + 'pipe_tables', + 'strikeout', + # 'superscript', + # 'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ] + out_format = 'html5' + out_options = [] + + +class PandocHTML(PandocBase): + in_format = 'html' + in_options = [] + out_format = 'markdown' + out_options = [ + 'footnotes', + 'pipe_tables', + 'strikeout', + # 'superscript', + # 'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ] + + +class PandocTXT(PandocBase): + in_format = 'markdown' + in_options = [ + 'footnotes', + 'pipe_tables', + 'strikeout', + # 'superscript', + # 'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ] + out_format = 'plain' + out_options = [] + columns = '--columns=72' + + +#class PandocMarkdown(str): + #def __new__(cls, text): + #""" Pandoc command line call with piped in- and output """ + #cmd = ( + #'pandoc', + #'-o-', + #'--from=markdown+%s' % ( + #'+'.join([ + #'footnotes', + #'pipe_tables', + #'strikeout', + ## 'superscript', + ## 'subscript', + #'raw_html', + #'definition_lists', + #'backtick_code_blocks', + #'fenced_code_attributes', + #'shortcut_reference_links', + #'lists_without_preceding_blankline', + #'autolink_bare_uris', + #]) + #), + #'--to=html5', + #'--quiet', + #'--no-highlight' + #) + #p = subprocess.Popen( + #cmd, + #stdin=subprocess.PIPE, + #stdout=subprocess.PIPE, + #stderr=subprocess.PIPE, + #) + + #stdout, stderr = p.communicate(input=text.encode()) + #if stderr: + #logging.warning( + #"Error during pandoc covert:\n\t%s\n\t%s", + #cmd, + #stderr + #) + #r = stdout.decode('utf-8').strip() + #return str.__new__(cls, r) + + +#class PandocHTML(str): + #def __new__(cls, text): + #""" Pandoc command line call with piped in- and output """ + #cmd = ( + #'pandoc', + #'-o-', + #'--to=markdown+%s' % ( + #'+'.join([ + #'footnotes', + #'pipe_tables', + #'strikeout', + ## 'superscript', + ## 'subscript', + #'raw_html', + #'definition_lists', + #'backtick_code_blocks', + #'fenced_code_attributes', + #'shortcut_reference_links', + #'lists_without_preceding_blankline', + #'autolink_bare_uris', + #]) + #), + #'--from=html', + #'--quiet', + #) + #p = subprocess.Popen( + #cmd, + #stdin=subprocess.PIPE, + #stdout=subprocess.PIPE, + #stderr=subprocess.PIPE, + #) + + #stdout, stderr = p.communicate(input=text.encode()) + #if stderr: + #logging.warning( + #"Error during pandoc covert:\n\t%s\n\t%s", + #cmd, + #stderr + #) + #r = stdout.decode('utf-8').strip() + #return str.__new__(cls, r) diff --git a/settings.py b/settings.py index 0c04389..0fdacaf 100644 --- a/settings.py +++ b/settings.py @@ -47,6 +47,9 @@ site = struct({ "name": "petermolnar.net", "image": "https://petermolnar.net/favicon.ico", "license": "https://spdx.org/licenses/%s.html" % (licence['_default']), + #"sameAs": [ + #"dat://8d03735af11d82fff82028e0f830f9ac470f5e9fbe10ab5eb6feb877232714a2" + #], "author": { "@context": "http://schema.org", "@type": "Person", @@ -88,30 +91,28 @@ site = struct({ "@type": "FollowAction", "url": "https://petermolnar.net/follow/", "name": "follow" - } + }, + #{ + #"@context": "http://schema.org", + #"@type": "DonateAction", + #"description": "Monzo (only in the UK or via Google Pay)", + #"name": "monzo", + #"price": "3GBP", + #"url": "https://monzo.me/petermolnar/3", + #"recipient": author + #}, + #{ + #"@context": "http://schema.org", + #"@type": "DonateAction", + #"description": "Paypal", + #"name": "paypal", + #"price": "3GBP", + #"url": "https://paypal.me/petermolnar/3GBP", + #"recipient": author + #} ] }) -donateActions = [ - { - "@context": "http://schema.org", - "@type": "DonateAction", - "description": "Monzo (only in the UK or via Google Pay)", - "name": "monzo", - "price": "3GBP", - "url": "https://monzo.me/petermolnar/3", - "recipient": author - }, - { - "@context": "http://schema.org", - "@type": "DonateAction", - "description": "Paypal", - "name": "paypal", - "price": "3GBP", - "url": "https://paypal.me/petermolnar/3GBP", - "recipient": author - } -] menu = { 'home': { @@ -170,6 +171,29 @@ photo = struct({ }, }) +bye = """ +███████╗███████╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ +██╔════╝██╔════╝██╔════╝ ╚██╗ ██╔╝██╔═══██╗██║ ██║ +███████╗█████╗ █████╗ ╚████╔╝ ██║ ██║██║ ██║ +╚════██║██╔══╝ ██╔══╝ ╚██╔╝ ██║ ██║██║ ██║ +███████║███████╗███████╗ ██║ ╚██████╔╝╚██████╔╝ +╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ + +███████╗██████╗ █████╗ ██████╗███████╗ +██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝ +███████╗██████╔╝███████║██║ █████╗ +╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ +███████║██║ ██║ ██║╚██████╗███████╗ +╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝ + + ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ██╗ ██╗ +██╔════╝██╔═══██╗██║ ██║██╔══██╗██╔═══██╗╚██╗ ██╔╝ +██║ ██║ ██║██║ █╗ ██║██████╔╝██║ ██║ ╚████╔╝ +██║ ██║ ██║██║███╗██║██╔══██╗██║ ██║ ╚██╔╝ +╚██████╗╚██████╔╝╚███╔███╔╝██████╔╝╚██████╔╝ ██║ + ╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═════╝ ╚═╝ + """ + _parser = argparse.ArgumentParser(description='Parameters for NASG') _booleanparams = { 'regenerate': 'force downsizing images', diff --git a/templates/Singular.j2.html b/templates/Singular.j2.html index e734cbf..e8dfc2e 100644 --- a/templates/Singular.j2.html +++ b/templates/Singular.j2.html @@ -10,6 +10,7 @@ + @@ -17,11 +18,11 @@ - {% if post.image.url is defined %} - - - - + {% if post.image is iterable %} + + + + {% else %} {% endif %} @@ -216,24 +217,6 @@ {% endfor %} - - Encourage creation! - - If this entry helped you, or you simply liked it, leave a tip via - {% set counters = {'donation': False} %} - {% for donate in post.potentialAction %} - {% if 'DonateAction' == donate['@type'] %} - {% if counters.donation %} or {% endif %} - - - - {{ donate.description }} - {% if counters.update({'donation': True}) %} {% endif %} - {% endif %} - {% endfor %} - - - {% if post.comment|length %} Responses diff --git a/templates/Singular.j2.txt b/templates/Singular.j2.txt new file mode 100644 index 0000000..cf05694 --- /dev/null +++ b/templates/Singular.j2.txt @@ -0,0 +1,10 @@ +--- +Title: {{ post.headline }} +Author: {{ post.author.name }} <{{ post.author.email}}> +URL: {{ post.url }} +Published: {{ post.datePublished|printdate }} +--- + +{{ summary }} + +{{ content }} diff --git a/templates/style.css b/templates/style.css index c40d49b..b439755 100644 --- a/templates/style.css +++ b/templates/style.css @@ -288,19 +288,6 @@ li p { margin: 0; } -.encourage, .encourage a { - color: #090; -} - -.encourage a { - color: #0a0; - font-weight: bold; -} - -.encourage h2 { - border-color: #090; -} - .footnotes hr:before { content: 'Links'; color: #ccc; diff --git a/templates/symbols.svg b/templates/symbols.svg index 5ddd0f9..87b583f 100644 --- a/templates/symbols.svg +++ b/templates/symbols.svg @@ -77,9 +77,6 @@ - - -
', c) return c @@ -305,7 +323,9 @@ class Comment(MarkdownDoc): @property def targetname(self): t = urlparse(self.meta.get('target')) - return t.path.rstrip('/').strip('/').split('/')[-1] + return os.path.split(t.path.lstrip('/'))[0] + #t = urlparse(self.meta.get('target')) + #return t.path.rstrip('/').strip('/').split('/')[-1] @property def source(self): @@ -335,11 +355,10 @@ class Comment(MarkdownDoc): @property def type(self): return self.meta.get('type', 'webmention') - #if len(self.content): - #maybe = clean(self.content, strip=True) - #if maybe in UNICODE_EMOJI: - #return maybe - + # if len(self.content): + #maybe = clean(self.content, strip=True) + # if maybe in UNICODE_EMOJI: + # return maybe @cached_property def jsonld(self): @@ -418,10 +437,24 @@ class Singular(MarkdownDoc): maybe = c.dt return maybe + + @property + def dt(self): + dt = int(MarkdownDoc.dt.fget(self)) + for maybe in self.comments.keys(): + if int(dt) < int(maybe): + dt = int(maybe) + return dt + @property def sameas(self): r = [] - for k in glob.glob(os.path.join(os.path.dirname(self.fpath), '*.copy')): + for k in glob.glob( + os.path.join( + os.path.dirname(self.fpath), + '*.copy' + ) + ): with open(k, 'rt') as f: r.append(f.read()) return r @@ -436,7 +469,7 @@ class Singular(MarkdownDoc): files = [ k for k in glob.glob(os.path.join(os.path.dirname(self.fpath), '*.md')) - if os.path.basename(k) != 'index.md' + if os.path.basename(k) != MDFILE ] for f in files: c = Comment(f) @@ -463,9 +496,9 @@ class Singular(MarkdownDoc): images.update({match: WebImage(imgpath, mdimg, self)}) else: logger.error("Missing image: %s, referenced in %s", - imgpath, - self.fpath - ) + imgpath, + self.fpath + ) return images @property @@ -493,7 +526,7 @@ class Singular(MarkdownDoc): if len(self.images) != 1: return False photo = next(iter(self.images.values())) - maybe = self.fpath.replace("index.md", "%s.jpg" % (self.name)) + maybe = self.fpath.replace(MDFILE, "%s.jpg" % (self.name)) if photo.fpath == maybe: return True return False @@ -630,46 +663,46 @@ class Singular(MarkdownDoc): else: return False - #@cached_property - #def oembed_xml(self): - #oembed = etree.Element("oembed", version="1.0") - #xmldoc = etree.ElementTree(oembed) - #for k, v in self.oembed_json.items(): - #x = etree.SubElement(oembed, k).text = "%s" % (v) - #s = etree.tostring( - #xmldoc, - #encoding='utf-8', - #xml_declaration=True, - #pretty_print=True - #) - #return s + @cached_property + def oembed_xml(self): + oembed = etree.Element("oembed", version="1.0") + xmldoc = etree.ElementTree(oembed) + for k, v in self.oembed_json.items(): + x = etree.SubElement(oembed, k).text = "%s" % (v) + s = etree.tostring( + xmldoc, + encoding='utf-8', + xml_declaration=True, + pretty_print=True + ) + return s - #@cached_property - #def oembed_json(self): - #r = { - #"version": "1.0", - #"provider_name": settings.site.name, - #"provider_url": settings.site.url, - #"author_name": settings.author.name, - #"author_url": settings.author.url, - #"title": self.title, - #"type": "link", - #"html": self.html_content, - #} + @cached_property + def oembed_json(self): + r = { + "version": "1.0", + "provider_name": settings.site.name, + "provider_url": settings.site.url, + "author_name": settings.author.name, + "author_url": settings.author.url, + "title": self.title, + "type": "link", + "html": self.html_content, + } - #img = None - #if self.is_photo: - #img = self.photo - #elif not self.is_photo and len(self.images): - #img = list(self.images.values())[0] - #if img: - #r.update({ - #"type": "rich", - #"thumbnail_url": img.jsonld.thumbnail.url, - #"thumbnail_width": img.jsonld.thumbnail.width, - #"thumbnail_height": img.jsonld.thumbnail.height - #}) - #return r + img = None + if self.is_photo: + img = self.photo + elif not self.is_photo and len(self.images): + img = list(self.images.values())[0] + if img: + r.update({ + "type": "rich", + "thumbnail_url": img.jsonld.thumbnail.url, + "thumbnail_width": img.jsonld.thumbnail.width, + "thumbnail_height": img.jsonld.thumbnail.height + }) + return r @cached_property def review(self): @@ -744,7 +777,7 @@ class Singular(MarkdownDoc): if self.is_photo: r.update({ "@type": "Photograph", - "image": self.photo.jsonld, + #"image": self.photo.jsonld, }) elif self.has_code: r.update({ @@ -754,11 +787,15 @@ class Singular(MarkdownDoc): r.update({ "@type": "WebPage", }) - if not self.is_photo and len(self.images): - img = list(self.images.values())[0] - r.update({ - "image": img.jsonld, - }) + if len(self.images): + r["image"] = [] + for img in list(self.images.values()): + r["image"].append(img.jsonld) + # if not self.is_photo and len(self.images): + # img = list(self.images.values())[0] + # r.update({ + # "image": img.jsonld, + # }) if self.is_reply: r.update({ @@ -775,8 +812,8 @@ class Singular(MarkdownDoc): if self.event: r.update({"subjectOf": self.event}) - for donation in settings.donateActions: - r["potentialAction"].append(donation) + #for donation in settings.donateActions: + #r["potentialAction"].append(donation) for url in list(set(self.syndicate)): r["potentialAction"].append({ @@ -794,16 +831,29 @@ class Singular(MarkdownDoc): def template(self): return "%s.j2.html" % (self.__class__.__name__) + @property + def gophertemplate(self): + return "%s.j2.txt" % (self.__class__.__name__) + @property def renderdir(self): - return os.path.dirname(self.renderfile) + return os.path.join( + settings.paths.get('build'), + self.name + ) @property def renderfile(self): return os.path.join( - settings.paths.get('build'), - self.name, - 'index.html' + self.renderdir, + HTMLFILE + ) + + @property + def gopherfile(self): + return os.path.join( + self.renderdir, + TXTFILE ) @property @@ -831,7 +881,15 @@ class Singular(MarkdownDoc): ]) async def copyfiles(self): - exclude = ['.md', '.jpg', '.png', '.gif', '.ping', '.url', '.del', '.copy'] + exclude = [ + '.md', + '.jpg', + '.png', + '.gif', + '.ping', + '.url', + '.del', + '.copy'] files = glob.glob( os.path.join( os.path.dirname(self.fpath), @@ -854,39 +912,41 @@ class Singular(MarkdownDoc): logger.info("copying '%s' to '%s'", f, t) cp(f, t) - @cached_property - def html(self): - r = J2.get_template(self.template).render({ + async def render(self): + if self.exists: + return + logger.info("rendering %s", self.name) + v = { 'baseurl': self.url, 'post': self.jsonld, 'site': settings.site, 'menu': settings.menu, 'meta': settings.meta, - }) - return r - - async def render(self): - if self.exists: - return - logger.info("rendering %s", self.name) + } writepath( self.renderfile, - self.html + J2.get_template(self.template).render(v) ) + + g = { + 'post': self.jsonld, + 'summary': PandocTXT(self.summary), + 'content': PandocTXT(self.content) + } + writepath( + self.gopherfile, + J2.get_template(self.gophertemplate).render(g) + ) + j = settings.site.copy() j.update({ "mainEntity": self.jsonld }) writepath( - os.path.join(self.renderdir,'index.json'), + os.path.join(self.renderdir, 'index.json'), json.dumps(j, indent=4, ensure_ascii=False) ) del(j) - cp( - self.fpath, - os.path.join(self.renderdir,'index.md') - ) - class Home(Singular): def __init__(self, fpath): @@ -904,7 +964,7 @@ class Home(Singular): def renderfile(self): return os.path.join( settings.paths.get('build'), - 'index.html' + HTMLFILE ) @property @@ -916,6 +976,27 @@ class Home(Singular): maybe = pts return maybe + async def render_gopher(self): + lines = [ + "%s's gopherhole - phlog, if you prefer" % (settings.site.name), + '', + '' + ] + + for category, post in self.posts: + line = "1%s\t/%s/%s\t%s\t70" % ( + category['name'], + CATEGORY, + category['name'], + settings.site.name + ) + lines.append(line) + lines.append('') + lines.append('') + lines = lines + list(map(lambda x: ("%s" % x), settings.bye.split('\n'))) + lines.append('') + writepath(self.renderfile.replace(HTMLFILE,GOPHERFILE), "\r\n".join(lines)) + async def render(self): if self.exists: return @@ -929,6 +1010,7 @@ class Home(Singular): 'posts': self.posts }) writepath(self.renderfile, r) + await self.render_gopher() class WebImage(object): @@ -1111,7 +1193,7 @@ class WebImage(object): 'Model': ['Model'], 'FNumber': ['FNumber', 'Aperture'], 'ExposureTime': ['ExposureTime'], - 'FocalLength': ['FocalLength'], # ['FocalLengthIn35mmFormat'], + 'FocalLength': ['FocalLength'], # ['FocalLengthIn35mmFormat'], 'ISO': ['ISO'], 'LensID': ['LensID', 'LensSpec', 'Lens'], 'CreateDate': ['CreateDate', 'DateTimeOriginal'] @@ -1189,7 +1271,8 @@ class WebImage(object): def data(self): with open(self.fpath, 'rb') as f: encoded = base64.b64encode(f.read()) - return "data:%s;base64,%s" % (self.parent.mime_type, encoded.decode('utf-8')) + return "data:%s;base64,%s" % ( + self.parent.mime_type, encoded.decode('utf-8')) @property def suffix(self): @@ -1328,8 +1411,8 @@ class PHPFile(object): raise ValueError('Not implemented') async def render(self): - #if self.exists: - #return + # if self.exists: + # return await self._render() @@ -1359,7 +1442,7 @@ class Search(PHPFile): notindexed=mtime, tokenize=porter )''' - ) + ) self.is_changed = False def __exit__(self): @@ -1548,7 +1631,7 @@ class Category(dict): @property def url(self): if len(self.name): - url = "%s/category/%s/" % (settings.site.get('url'), self.name) + url = "%s/%s/%s/" % (settings.site.get('url'), CATEGORY, self.name) else: url = '%s/' % (settings.site.get('url')) return url @@ -1566,7 +1649,7 @@ class Category(dict): if len(self.name): return os.path.join( settings.paths.get('build'), - 'category', + CATEGORY, self.name ) else: @@ -1665,17 +1748,17 @@ class Category(dict): 'posts': posts, } - def indexfpath(self, subpath=None): + def indexfpath(self, subpath=None, fname=HTMLFILE): if subpath: return os.path.join( self.dpath, subpath, - 'index.html' + fname ) else: return os.path.join( self.dpath, - 'index.html' + fname ) async def render_feed(self, xmlformat): @@ -1711,8 +1794,9 @@ class Category(dict): fe.category({ 'term': post.category, 'label': post.category, - 'scheme': "%s/category/%s/" % ( + 'scheme': "%s/%s/%s/" % ( settings.site.get('url'), + CATEGORY, post.category ) }) @@ -1758,6 +1842,32 @@ class Category(dict): ) writepath(self.indexfpath(), r) + async def render_gopher(self): + lines = [ + '%s - %s' % (self.name, settings.site.name), + '', + '' + ] + for post in self.get_posts(): + line = "0%s\t/%s/%s\t%s\t70" % ( + post.headline, + post.name, + TXTFILE, + settings.site.name + ) + lines.append(line) + if isinstance(post['image'], list): + for img in post['image']: + line = "I%s\t/%s/%s\t%s\t70" % ( + img.headline, + post.name, + img.name, + settings.site.name + ) + lines.append(line) + lines.append('') + writepath(self.indexfpath(fname=GOPHERFILE), "\r\n".join(lines)) + async def render_archives(self): for year in self.years.keys(): if year == self.newest_year: @@ -1779,7 +1889,7 @@ class Category(dict): end = index if self.is_uptodate(fpath, self[self.sortedkeys[start]].dt): - logger.info("%s / %d is up to date", self.name, year) + logger.info("%s / %d is up to date", self.name, year) else: logger.info("updating %s / %d", self.name, year) logger.info("getting posts from %d to %d", start, end) @@ -1788,7 +1898,7 @@ class Category(dict): # I don't know why end needs the +1, but without that # some posts disappear # TODO figure this out... - self.get_posts(start, end+1), + self.get_posts(start, end + 1), tyear ) ) @@ -1819,9 +1929,10 @@ class Category(dict): self.name ) - async def render(self): await self.render_feeds() + if not self.is_uptodate(self.indexfpath(), self.newest()): + await self.render_gopher() if not self.is_paginated: if not self.is_uptodate(self.indexfpath(), self.newest()): logger.info( @@ -1829,6 +1940,7 @@ class Category(dict): self.name ) await self.render_flat() + else: logger.info( '%s flat index is up to date', @@ -1839,6 +1951,7 @@ class Category(dict): await self.render_archives() + class Sitemap(dict): @property def mtime(self): @@ -1875,7 +1988,7 @@ class WebmentionIO(object): newest = 0 content = settings.paths.get('content') for e in glob.glob(os.path.join(content, '*', '*', '*.md')): - if os.path.basename(e) == 'index.md': + if os.path.basename(e) == MDFILE: continue # filenames are like [received epoch]-[slugified source url].md try: @@ -1889,7 +2002,7 @@ class WebmentionIO(object): continue if mtime > newest: newest = mtime - return arrow.get(newest+1) + return arrow.get(newest + 1) def makecomment(self, webmention): if 'published_ts' in webmention.get('data'): @@ -1899,12 +2012,19 @@ class WebmentionIO(object): else: dt = arrow.get(webmention.get('data').get('published')) - slug = webmention.get('target').strip('/').split('/')[-1] + slug = os.path.split(urlparse(webmention.get('target')).path.lstrip('/'))[0] + # ignore selfpings if slug == settings.site.get('name'): return - fdir = glob.glob(os.path.join(settings.paths.get('content'), '*', slug)) + fdir = glob.glob( + os.path.join( + settings.paths.get('content'), + '*', + slug + ) + ) if not len(fdir): logger.error( "couldn't find post for incoming webmention: %s", @@ -1962,6 +2082,7 @@ class WebmentionIO(object): logger.error('failed to query webmention.io: %s', e) pass + def make(): start = int(round(time.time() * 1000)) last = 0 @@ -1989,7 +2110,7 @@ def make(): frontposts = Category() home = Home(settings.paths.get('home')) - for e in sorted(glob.glob(os.path.join(content, '*', '*', 'index.md'))): + for e in sorted(glob.glob(os.path.join(content, '*', '*', MDFILE))): post = Singular(e) # deal with images, if needed for i in post.images.values(): @@ -2060,11 +2181,18 @@ def make(): for e in glob.glob(os.path.join(content, '*.*')): if e.endswith('.md'): continue - t = os.path.join(settings.paths.get('build'),os.path.basename(e)) + t = os.path.join(settings.paths.get('build'), os.path.basename(e)) if os.path.exists(t) and mtime(e) <= mtime(t): continue cp(e, t) + # ... + #for url in settings.site.sameAs: + #if "dat://" in url: + #p = os.path.join(settings.paths.build, '.well-known', 'dat') + #if not os.path.exists(p): + #writepath(p, "%s\nTTL=3600" % (url)) + end = int(round(time.time() * 1000)) logger.info('process took %d ms' % (end - start)) diff --git a/pandoc.py b/pandoc.py index 258f265..d250c45 100644 --- a/pandoc.py +++ b/pandoc.py @@ -7,34 +7,43 @@ __email__ = "mail@petermolnar.net" import subprocess import logging -class PandocMarkdown(str): - def __new__(cls, text): - """ Pandoc command line call with piped in- and output """ - cmd = ( + +class PandocBase(str): + in_format = 'html' + in_options = [] + out_format = 'plain' + out_options = [] + columns = None + + def __init__(self, text): + self.source = text + conv_to = '--to=%s' % (self.out_format) + if (len(self.out_options)): + conv_to = '%s+%s' % ( + conv_to, + '+'.join(self.out_options) + ) + + conv_from = '--from=%s' % (self.in_format) + if (len(self.in_options)): + conv_from = '%s+%s' % ( + conv_from, + '+'.join(self.in_options) + ) + + cmd = [ 'pandoc', '-o-', - '--from=markdown+%s' % ( - '+'.join([ - 'footnotes', - 'pipe_tables', - 'strikeout', - #'superscript', - #'subscript', - 'raw_html', - 'definition_lists', - 'backtick_code_blocks', - 'fenced_code_attributes', - 'shortcut_reference_links', - 'lists_without_preceding_blankline', - 'autolink_bare_uris', - ]) - ), - '--to=html5', + conv_to, + conv_from, '--quiet', '--no-highlight' - ) + ] + if self.columns: + cmd.append(self.columns) + p = subprocess.Popen( - cmd, + tuple(cmd), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -48,46 +57,158 @@ class PandocMarkdown(str): stderr ) r = stdout.decode('utf-8').strip() - return str.__new__(cls, r) + self.result = r -class PandocHTML(str): - def __new__(cls, text): - """ Pandoc command line call with piped in- and output """ - cmd = ( - 'pandoc', - '-o-', - '--to=markdown+%s' % ( - '+'.join([ - 'footnotes', - 'pipe_tables', - 'strikeout', - #'superscript', - #'subscript', - 'raw_html', - 'definition_lists', - 'backtick_code_blocks', - 'fenced_code_attributes', - 'shortcut_reference_links', - 'lists_without_preceding_blankline', - 'autolink_bare_uris', - ]) - ), - '--from=html', - '--quiet', - ) - p = subprocess.Popen( - cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + def __str__(self): + return str(self.result) - stdout, stderr = p.communicate(input=text.encode()) - if stderr: - logging.warning( - "Error during pandoc covert:\n\t%s\n\t%s", - cmd, - stderr - ) - r = stdout.decode('utf-8').strip() - return str.__new__(cls, r) + def __repr__(self): + return str(self.result) + + +class PandocMarkdown(PandocBase): + in_format = 'markdown' + in_options = [ + 'footnotes', + 'pipe_tables', + 'strikeout', + # 'superscript', + # 'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ] + out_format = 'html5' + out_options = [] + + +class PandocHTML(PandocBase): + in_format = 'html' + in_options = [] + out_format = 'markdown' + out_options = [ + 'footnotes', + 'pipe_tables', + 'strikeout', + # 'superscript', + # 'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ] + + +class PandocTXT(PandocBase): + in_format = 'markdown' + in_options = [ + 'footnotes', + 'pipe_tables', + 'strikeout', + # 'superscript', + # 'subscript', + 'raw_html', + 'definition_lists', + 'backtick_code_blocks', + 'fenced_code_attributes', + 'shortcut_reference_links', + 'lists_without_preceding_blankline', + 'autolink_bare_uris', + ] + out_format = 'plain' + out_options = [] + columns = '--columns=72' + + +#class PandocMarkdown(str): + #def __new__(cls, text): + #""" Pandoc command line call with piped in- and output """ + #cmd = ( + #'pandoc', + #'-o-', + #'--from=markdown+%s' % ( + #'+'.join([ + #'footnotes', + #'pipe_tables', + #'strikeout', + ## 'superscript', + ## 'subscript', + #'raw_html', + #'definition_lists', + #'backtick_code_blocks', + #'fenced_code_attributes', + #'shortcut_reference_links', + #'lists_without_preceding_blankline', + #'autolink_bare_uris', + #]) + #), + #'--to=html5', + #'--quiet', + #'--no-highlight' + #) + #p = subprocess.Popen( + #cmd, + #stdin=subprocess.PIPE, + #stdout=subprocess.PIPE, + #stderr=subprocess.PIPE, + #) + + #stdout, stderr = p.communicate(input=text.encode()) + #if stderr: + #logging.warning( + #"Error during pandoc covert:\n\t%s\n\t%s", + #cmd, + #stderr + #) + #r = stdout.decode('utf-8').strip() + #return str.__new__(cls, r) + + +#class PandocHTML(str): + #def __new__(cls, text): + #""" Pandoc command line call with piped in- and output """ + #cmd = ( + #'pandoc', + #'-o-', + #'--to=markdown+%s' % ( + #'+'.join([ + #'footnotes', + #'pipe_tables', + #'strikeout', + ## 'superscript', + ## 'subscript', + #'raw_html', + #'definition_lists', + #'backtick_code_blocks', + #'fenced_code_attributes', + #'shortcut_reference_links', + #'lists_without_preceding_blankline', + #'autolink_bare_uris', + #]) + #), + #'--from=html', + #'--quiet', + #) + #p = subprocess.Popen( + #cmd, + #stdin=subprocess.PIPE, + #stdout=subprocess.PIPE, + #stderr=subprocess.PIPE, + #) + + #stdout, stderr = p.communicate(input=text.encode()) + #if stderr: + #logging.warning( + #"Error during pandoc covert:\n\t%s\n\t%s", + #cmd, + #stderr + #) + #r = stdout.decode('utf-8').strip() + #return str.__new__(cls, r) diff --git a/settings.py b/settings.py index 0c04389..0fdacaf 100644 --- a/settings.py +++ b/settings.py @@ -47,6 +47,9 @@ site = struct({ "name": "petermolnar.net", "image": "https://petermolnar.net/favicon.ico", "license": "https://spdx.org/licenses/%s.html" % (licence['_default']), + #"sameAs": [ + #"dat://8d03735af11d82fff82028e0f830f9ac470f5e9fbe10ab5eb6feb877232714a2" + #], "author": { "@context": "http://schema.org", "@type": "Person", @@ -88,30 +91,28 @@ site = struct({ "@type": "FollowAction", "url": "https://petermolnar.net/follow/", "name": "follow" - } + }, + #{ + #"@context": "http://schema.org", + #"@type": "DonateAction", + #"description": "Monzo (only in the UK or via Google Pay)", + #"name": "monzo", + #"price": "3GBP", + #"url": "https://monzo.me/petermolnar/3", + #"recipient": author + #}, + #{ + #"@context": "http://schema.org", + #"@type": "DonateAction", + #"description": "Paypal", + #"name": "paypal", + #"price": "3GBP", + #"url": "https://paypal.me/petermolnar/3GBP", + #"recipient": author + #} ] }) -donateActions = [ - { - "@context": "http://schema.org", - "@type": "DonateAction", - "description": "Monzo (only in the UK or via Google Pay)", - "name": "monzo", - "price": "3GBP", - "url": "https://monzo.me/petermolnar/3", - "recipient": author - }, - { - "@context": "http://schema.org", - "@type": "DonateAction", - "description": "Paypal", - "name": "paypal", - "price": "3GBP", - "url": "https://paypal.me/petermolnar/3GBP", - "recipient": author - } -] menu = { 'home': { @@ -170,6 +171,29 @@ photo = struct({ }, }) +bye = """ +███████╗███████╗███████╗ ██╗ ██╗ ██████╗ ██╗ ██╗ +██╔════╝██╔════╝██╔════╝ ╚██╗ ██╔╝██╔═══██╗██║ ██║ +███████╗█████╗ █████╗ ╚████╔╝ ██║ ██║██║ ██║ +╚════██║██╔══╝ ██╔══╝ ╚██╔╝ ██║ ██║██║ ██║ +███████║███████╗███████╗ ██║ ╚██████╔╝╚██████╔╝ +╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ + +███████╗██████╗ █████╗ ██████╗███████╗ +██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝ +███████╗██████╔╝███████║██║ █████╗ +╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ +███████║██║ ██║ ██║╚██████╗███████╗ +╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝ + + ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ██╗ ██╗ +██╔════╝██╔═══██╗██║ ██║██╔══██╗██╔═══██╗╚██╗ ██╔╝ +██║ ██║ ██║██║ █╗ ██║██████╔╝██║ ██║ ╚████╔╝ +██║ ██║ ██║██║███╗██║██╔══██╗██║ ██║ ╚██╔╝ +╚██████╗╚██████╔╝╚███╔███╔╝██████╔╝╚██████╔╝ ██║ + ╚═════╝ ╚═════╝ ╚══╝╚══╝ ╚═════╝ ╚═════╝ ╚═╝ + """ + _parser = argparse.ArgumentParser(description='Parameters for NASG') _booleanparams = { 'regenerate': 'force downsizing images', diff --git a/templates/Singular.j2.html b/templates/Singular.j2.html index e734cbf..e8dfc2e 100644 --- a/templates/Singular.j2.html +++ b/templates/Singular.j2.html @@ -10,6 +10,7 @@ + @@ -17,11 +18,11 @@ - {% if post.image.url is defined %} - - - - + {% if post.image is iterable %} + + + + {% else %} {% endif %} @@ -216,24 +217,6 @@ {% endfor %} - - Encourage creation! - - If this entry helped you, or you simply liked it, leave a tip via - {% set counters = {'donation': False} %} - {% for donate in post.potentialAction %} - {% if 'DonateAction' == donate['@type'] %} - {% if counters.donation %} or {% endif %} - - - - {{ donate.description }} - {% if counters.update({'donation': True}) %} {% endif %} - {% endif %} - {% endfor %} - - - {% if post.comment|length %} Responses diff --git a/templates/Singular.j2.txt b/templates/Singular.j2.txt new file mode 100644 index 0000000..cf05694 --- /dev/null +++ b/templates/Singular.j2.txt @@ -0,0 +1,10 @@ +--- +Title: {{ post.headline }} +Author: {{ post.author.name }} <{{ post.author.email}}> +URL: {{ post.url }} +Published: {{ post.datePublished|printdate }} +--- + +{{ summary }} + +{{ content }} diff --git a/templates/style.css b/templates/style.css index c40d49b..b439755 100644 --- a/templates/style.css +++ b/templates/style.css @@ -288,19 +288,6 @@ li p { margin: 0; } -.encourage, .encourage a { - color: #090; -} - -.encourage a { - color: #0a0; - font-weight: bold; -} - -.encourage h2 { - border-color: #090; -} - .footnotes hr:before { content: 'Links'; color: #ccc; diff --git a/templates/symbols.svg b/templates/symbols.svg index 5ddd0f9..87b583f 100644 --- a/templates/symbols.svg +++ b/templates/symbols.svg @@ -77,9 +77,6 @@ - - -
- If this entry helped you, or you simply liked it, leave a tip via - {% set counters = {'donation': False} %} - {% for donate in post.potentialAction %} - {% if 'DonateAction' == donate['@type'] %} - {% if counters.donation %} or {% endif %} - - - - {{ donate.description }} - {% if counters.update({'donation': True}) %} {% endif %} - {% endif %} - {% endfor %} -