- gopher? gopher.

- no js needed button
- removed donation, I'll figure out something better, sometimes, in the future
- text/plain alternate, also for gopher
- better Pandoc subclassing
- bye message
- first image becomes OG image
- removed duplicate reply symbol
- DAT .well-known prepare, not active
- oembed singular vars, should they ever be needed
- fixed target lookup for webmentions so it works both with index.html or with path only
This commit is contained in:
Peter Molnar 2019-02-25 22:40:01 +00:00
parent 67662b69e9
commit 9f73a9b111
10 changed files with 513 additions and 230 deletions

View file

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>facebook</title>
<path fill="#3b5998" d="M15.117 0h-14.235c-0.487 0-0.883 0.395-0.883 0.883v14.235c0 0.488 0.395 0.883 0.883 0.883h7.663v-6.196h-2.086v-2.414h2.086v-1.783c0-2.066 1.263-3.19 3.106-3.19 0.883 0 1.643 0.065 1.864 0.094v2.16h-1.281c-1 0-1.195 0.481-1.195 1.181v1.541h2.389l-0.31 2.42h-2.079v6.188h4.077c0.489 0 0.883-0.395 0.883-0.883v-14.235c0-0.487-0.395-0.883-0.883-0.883z"></path>
</svg>

After

Width:  |  Height:  |  Size: 543 B

27
assets/nojs-button.svg Normal file
View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="80" height="15" version="1.1" viewBox="0 0 80 15" xmlns="http://www.w3.org/2000/svg">
<rect width="80" height="15" fill="#666"/>
<rect x="1" y="1" width="78" height="13" fill="#fff"/>
<rect x="16" y="2" width="62" height="11" fill="#666"/>
<g fill="#fff">
<path d="m27 5v1h2v-1zm2 1v3h1v-3zm0 3h-2v1h2zm-2 0v-3h-1v3z"/>
<path d="m20 5h1v1h1v1h1v1h1v-3h1v5h-1v-1h-1v-1h-1v-1h-1v3h-1z"/>
<rect x="-1" y="259.03" width="78" height="13"/>
<path d="m70 5v5h3v-1h-2v-3h2v-1zm3 1v3h1v-3z"/>
<path d="m66 5h3v1h-2v1h2v1h-2v1h2v1h-3z"/>
<circle cx="7.5" cy="7.5" r="5.5" stroke="#666" style="paint-order:markers fill stroke"/>
</g>
<g fill="#666">
<rect transform="rotate(-45)" x="-6" y="10" width="11" height="1"/>
<path d="m9 5h3v1h-3v1h2v1h1v1h-1v1h-3v-1h3v-1h-2v-1h-1v-1h1"/>
<path d="m6 5v4h1v-4zm0 4h-2v1h2zm-2 0v-1h-1v1z"/>
</g>
<g fill="#fff">
<path d="m40 5h3v1h-3v1h2v1h1v1h-1v1h-3v-1h3v-1h-2v-1h-1v-1h1"/>
<path d="m37 5v4h1v-4zm0 4h-2v1h2zm-2 0v-1h-1v1z"/>
<path d="m57 5h3v1h-2v1h2v1h-2v1h2v1h-3z"/>
<path d="m61 5v5h3v-1h-2v-3h2v-1zm3 1v3h1v-3z"/>
<path d="m53 5h3v1h-2v1h2v1h-2v1h2v1h-3z"/>
<path d="m47 5h1v1h1v1h1v1h1v-3h1v5h-1v-1h-1v-1h-1v-1h-1v3h-1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -18,6 +18,7 @@ EXIFDATE = re.compile(
r'(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2})$' r'(?P<time>[0-9]{2}:[0-9]{2}:[0-9]{2})$'
) )
class CachedMeta(dict): class CachedMeta(dict):
def __init__(self, fpath): def __init__(self, fpath):
self.fpath = fpath self.fpath = fpath
@ -25,7 +26,7 @@ class CachedMeta(dict):
@property @property
def cfile(self): def cfile(self):
fname = os.path.basename(self.fpath) fname = os.path.basename(self.fpath)
if fname == 'index.md': if fname == 'index.md':
fname = os.path.basename(os.path.dirname(self.fpath)) fname = os.path.basename(os.path.dirname(self.fpath))
return os.path.join( return os.path.join(

338
nasg.py
View file

@ -21,6 +21,7 @@ from math import ceil
from urllib.parse import urlparse from urllib.parse import urlparse
from collections import OrderedDict, namedtuple from collections import OrderedDict, namedtuple
import logging import logging
import csv
import arrow import arrow
import langdetect import langdetect
@ -31,8 +32,9 @@ import frontmatter
from feedgen.feed import FeedGenerator from feedgen.feed import FeedGenerator
from slugify import slugify from slugify import slugify
import requests import requests
import lxml.etree as etree
from pandoc import PandocMarkdown from pandoc import PandocMarkdown, PandocTXT
from meta import Exif from meta import Exif
import settings import settings
from settings import struct from settings import struct
@ -40,6 +42,12 @@ import keys
logger = logging.getLogger('NASG') logger = logging.getLogger('NASG')
CATEGORY = 'category'
MDFILE = 'index.md'
TXTFILE = 'index.txt'
HTMLFILE = 'index.html'
GOPHERFILE = 'gophermap'
MarkdownImage = namedtuple( MarkdownImage = namedtuple(
'MarkdownImage', 'MarkdownImage',
['match', 'alt', 'fname', 'title', 'css'] ['match', 'alt', 'fname', 'title', 'css']
@ -72,6 +80,7 @@ def mtime(path):
return int(os.path.getmtime(path)) return int(os.path.getmtime(path))
return 0 return 0
def utfyamldump(data): def utfyamldump(data):
""" dump YAML with actual UTF-8 chars """ """ dump YAML with actual UTF-8 chars """
return yaml.dump( return yaml.dump(
@ -81,6 +90,7 @@ def utfyamldump(data):
allow_unicode=True allow_unicode=True
) )
def url2slug(url, limit=200): def url2slug(url, limit=200):
""" convert URL to max 200 char ASCII string """ """ convert URL to max 200 char ASCII string """
return slugify( return slugify(
@ -89,13 +99,16 @@ def url2slug(url, limit=200):
lower=True lower=True
)[:limit] )[:limit]
J2.filters['url2slug'] = url2slug J2.filters['url2slug'] = url2slug
def rfc3339todt(rfc3339): def rfc3339todt(rfc3339):
""" nice dates for humans """ """ nice dates for humans """
t = arrow.get(rfc3339).format('YYYY-MM-DD HH:mm ZZZ') t = arrow.get(rfc3339).format('YYYY-MM-DD HH:mm ZZZ')
return "%s" % (t) return "%s" % (t)
J2.filters['printdate'] = rfc3339todt J2.filters['printdate'] = rfc3339todt
RE_MYURL = re.compile( RE_MYURL = re.compile(
@ -105,6 +118,7 @@ RE_MYURL = re.compile(
) )
) )
def relurl(text, baseurl=None): def relurl(text, baseurl=None):
if not baseurl: if not baseurl:
baseurl = settings.site.url baseurl = settings.site.url
@ -118,15 +132,17 @@ def relurl(text, baseurl=None):
r = os.path.relpath(url, baseurl) r = os.path.relpath(url, baseurl)
if url.endswith('/') and not r.endswith('/'): if url.endswith('/') and not r.endswith('/'):
r = "%s/index.html" % r r = "%s/%s" % (r, HTMLFILE)
if needsquotes: if needsquotes:
r = '"%s"' % r r = '"%s"' % r
logger.debug("RELURL: %s => %s (base: %s)", match, r, baseurl) logger.debug("RELURL: %s => %s (base: %s)", match, r, baseurl)
text = text.replace(match, r) text = text.replace(match, r)
return text return text
J2.filters['relurl'] = relurl J2.filters['relurl'] = relurl
def writepath(fpath, content, mtime=0): def writepath(fpath, content, mtime=0):
""" f.write with extras """ """ f.write with extras """
d = os.path.dirname(fpath) d = os.path.dirname(fpath)
@ -163,6 +179,7 @@ class cached_property(object):
class AQ: class AQ:
""" Async queue which starts execution right on population """ """ Async queue which starts execution right on population """
def __init__(self): def __init__(self):
self.loop = asyncio.get_event_loop() self.loop = asyncio.get_event_loop()
self.queue = asyncio.Queue(loop=self.loop) self.queue = asyncio.Queue(loop=self.loop)
@ -174,7 +191,7 @@ class AQ:
while not self.queue.empty(): while not self.queue.empty():
item = await self.queue.get() item = await self.queue.get()
self.queue.task_done() self.queue.task_done()
#asyncio.gather() ? # asyncio.gather() ?
def run(self): def run(self):
consumer = asyncio.ensure_future(self.consume()) consumer = asyncio.ensure_future(self.consume())
@ -183,6 +200,7 @@ class AQ:
class Webmention(object): class Webmention(object):
""" outgoing webmention class """ """ outgoing webmention class """
def __init__(self, source, target, dpath, mtime=0): def __init__(self, source, target, dpath, mtime=0):
self.source = source self.source = source
self.target = target self.target = target
@ -275,7 +293,7 @@ class MarkdownDoc(object):
def pandoc(self, c): def pandoc(self, c):
if c and len(c): if c and len(c):
c = PandocMarkdown(c) c = str(PandocMarkdown(c))
c = RE_PRECODE.sub( c = RE_PRECODE.sub(
'<pre><code lang="\g<1>" class="language-\g<1>">', c) '<pre><code lang="\g<1>" class="language-\g<1>">', c)
return c return c
@ -305,7 +323,9 @@ class Comment(MarkdownDoc):
@property @property
def targetname(self): def targetname(self):
t = urlparse(self.meta.get('target')) 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 @property
def source(self): def source(self):
@ -335,11 +355,10 @@ class Comment(MarkdownDoc):
@property @property
def type(self): def type(self):
return self.meta.get('type', 'webmention') return self.meta.get('type', 'webmention')
#if len(self.content): # if len(self.content):
#maybe = clean(self.content, strip=True) #maybe = clean(self.content, strip=True)
#if maybe in UNICODE_EMOJI: # if maybe in UNICODE_EMOJI:
#return maybe # return maybe
@cached_property @cached_property
def jsonld(self): def jsonld(self):
@ -418,10 +437,24 @@ class Singular(MarkdownDoc):
maybe = c.dt maybe = c.dt
return maybe 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 @property
def sameas(self): def sameas(self):
r = [] 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: with open(k, 'rt') as f:
r.append(f.read()) r.append(f.read())
return r return r
@ -436,7 +469,7 @@ class Singular(MarkdownDoc):
files = [ files = [
k k
for k in glob.glob(os.path.join(os.path.dirname(self.fpath), '*.md')) 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: for f in files:
c = Comment(f) c = Comment(f)
@ -463,9 +496,9 @@ class Singular(MarkdownDoc):
images.update({match: WebImage(imgpath, mdimg, self)}) images.update({match: WebImage(imgpath, mdimg, self)})
else: else:
logger.error("Missing image: %s, referenced in %s", logger.error("Missing image: %s, referenced in %s",
imgpath, imgpath,
self.fpath self.fpath
) )
return images return images
@property @property
@ -493,7 +526,7 @@ class Singular(MarkdownDoc):
if len(self.images) != 1: if len(self.images) != 1:
return False return False
photo = next(iter(self.images.values())) 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: if photo.fpath == maybe:
return True return True
return False return False
@ -630,46 +663,46 @@ class Singular(MarkdownDoc):
else: else:
return False return False
#@cached_property @cached_property
#def oembed_xml(self): def oembed_xml(self):
#oembed = etree.Element("oembed", version="1.0") oembed = etree.Element("oembed", version="1.0")
#xmldoc = etree.ElementTree(oembed) xmldoc = etree.ElementTree(oembed)
#for k, v in self.oembed_json.items(): for k, v in self.oembed_json.items():
#x = etree.SubElement(oembed, k).text = "%s" % (v) x = etree.SubElement(oembed, k).text = "%s" % (v)
#s = etree.tostring( s = etree.tostring(
#xmldoc, xmldoc,
#encoding='utf-8', encoding='utf-8',
#xml_declaration=True, xml_declaration=True,
#pretty_print=True pretty_print=True
#) )
#return s return s
#@cached_property @cached_property
#def oembed_json(self): def oembed_json(self):
#r = { r = {
#"version": "1.0", "version": "1.0",
#"provider_name": settings.site.name, "provider_name": settings.site.name,
#"provider_url": settings.site.url, "provider_url": settings.site.url,
#"author_name": settings.author.name, "author_name": settings.author.name,
#"author_url": settings.author.url, "author_url": settings.author.url,
#"title": self.title, "title": self.title,
#"type": "link", "type": "link",
#"html": self.html_content, "html": self.html_content,
#} }
#img = None img = None
#if self.is_photo: if self.is_photo:
#img = self.photo img = self.photo
#elif not self.is_photo and len(self.images): elif not self.is_photo and len(self.images):
#img = list(self.images.values())[0] img = list(self.images.values())[0]
#if img: if img:
#r.update({ r.update({
#"type": "rich", "type": "rich",
#"thumbnail_url": img.jsonld.thumbnail.url, "thumbnail_url": img.jsonld.thumbnail.url,
#"thumbnail_width": img.jsonld.thumbnail.width, "thumbnail_width": img.jsonld.thumbnail.width,
#"thumbnail_height": img.jsonld.thumbnail.height "thumbnail_height": img.jsonld.thumbnail.height
#}) })
#return r return r
@cached_property @cached_property
def review(self): def review(self):
@ -744,7 +777,7 @@ class Singular(MarkdownDoc):
if self.is_photo: if self.is_photo:
r.update({ r.update({
"@type": "Photograph", "@type": "Photograph",
"image": self.photo.jsonld, #"image": self.photo.jsonld,
}) })
elif self.has_code: elif self.has_code:
r.update({ r.update({
@ -754,11 +787,15 @@ class Singular(MarkdownDoc):
r.update({ r.update({
"@type": "WebPage", "@type": "WebPage",
}) })
if not self.is_photo and len(self.images): if len(self.images):
img = list(self.images.values())[0] r["image"] = []
r.update({ for img in list(self.images.values()):
"image": img.jsonld, 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: if self.is_reply:
r.update({ r.update({
@ -775,8 +812,8 @@ class Singular(MarkdownDoc):
if self.event: if self.event:
r.update({"subjectOf": self.event}) r.update({"subjectOf": self.event})
for donation in settings.donateActions: #for donation in settings.donateActions:
r["potentialAction"].append(donation) #r["potentialAction"].append(donation)
for url in list(set(self.syndicate)): for url in list(set(self.syndicate)):
r["potentialAction"].append({ r["potentialAction"].append({
@ -794,16 +831,29 @@ class Singular(MarkdownDoc):
def template(self): def template(self):
return "%s.j2.html" % (self.__class__.__name__) return "%s.j2.html" % (self.__class__.__name__)
@property
def gophertemplate(self):
return "%s.j2.txt" % (self.__class__.__name__)
@property @property
def renderdir(self): def renderdir(self):
return os.path.dirname(self.renderfile) return os.path.join(
settings.paths.get('build'),
self.name
)
@property @property
def renderfile(self): def renderfile(self):
return os.path.join( return os.path.join(
settings.paths.get('build'), self.renderdir,
self.name, HTMLFILE
'index.html' )
@property
def gopherfile(self):
return os.path.join(
self.renderdir,
TXTFILE
) )
@property @property
@ -831,7 +881,15 @@ class Singular(MarkdownDoc):
]) ])
async def copyfiles(self): 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( files = glob.glob(
os.path.join( os.path.join(
os.path.dirname(self.fpath), os.path.dirname(self.fpath),
@ -854,39 +912,41 @@ class Singular(MarkdownDoc):
logger.info("copying '%s' to '%s'", f, t) logger.info("copying '%s' to '%s'", f, t)
cp(f, t) cp(f, t)
@cached_property async def render(self):
def html(self): if self.exists:
r = J2.get_template(self.template).render({ return
logger.info("rendering %s", self.name)
v = {
'baseurl': self.url, 'baseurl': self.url,
'post': self.jsonld, 'post': self.jsonld,
'site': settings.site, 'site': settings.site,
'menu': settings.menu, 'menu': settings.menu,
'meta': settings.meta, 'meta': settings.meta,
}) }
return r
async def render(self):
if self.exists:
return
logger.info("rendering %s", self.name)
writepath( writepath(
self.renderfile, 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 = settings.site.copy()
j.update({ j.update({
"mainEntity": self.jsonld "mainEntity": self.jsonld
}) })
writepath( writepath(
os.path.join(self.renderdir,'index.json'), os.path.join(self.renderdir, 'index.json'),
json.dumps(j, indent=4, ensure_ascii=False) json.dumps(j, indent=4, ensure_ascii=False)
) )
del(j) del(j)
cp(
self.fpath,
os.path.join(self.renderdir,'index.md')
)
class Home(Singular): class Home(Singular):
def __init__(self, fpath): def __init__(self, fpath):
@ -904,7 +964,7 @@ class Home(Singular):
def renderfile(self): def renderfile(self):
return os.path.join( return os.path.join(
settings.paths.get('build'), settings.paths.get('build'),
'index.html' HTMLFILE
) )
@property @property
@ -916,6 +976,27 @@ class Home(Singular):
maybe = pts maybe = pts
return maybe 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): async def render(self):
if self.exists: if self.exists:
return return
@ -929,6 +1010,7 @@ class Home(Singular):
'posts': self.posts 'posts': self.posts
}) })
writepath(self.renderfile, r) writepath(self.renderfile, r)
await self.render_gopher()
class WebImage(object): class WebImage(object):
@ -1111,7 +1193,7 @@ class WebImage(object):
'Model': ['Model'], 'Model': ['Model'],
'FNumber': ['FNumber', 'Aperture'], 'FNumber': ['FNumber', 'Aperture'],
'ExposureTime': ['ExposureTime'], 'ExposureTime': ['ExposureTime'],
'FocalLength': ['FocalLength'], # ['FocalLengthIn35mmFormat'], 'FocalLength': ['FocalLength'], # ['FocalLengthIn35mmFormat'],
'ISO': ['ISO'], 'ISO': ['ISO'],
'LensID': ['LensID', 'LensSpec', 'Lens'], 'LensID': ['LensID', 'LensSpec', 'Lens'],
'CreateDate': ['CreateDate', 'DateTimeOriginal'] 'CreateDate': ['CreateDate', 'DateTimeOriginal']
@ -1189,7 +1271,8 @@ class WebImage(object):
def data(self): def data(self):
with open(self.fpath, 'rb') as f: with open(self.fpath, 'rb') as f:
encoded = base64.b64encode(f.read()) 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 @property
def suffix(self): def suffix(self):
@ -1328,8 +1411,8 @@ class PHPFile(object):
raise ValueError('Not implemented') raise ValueError('Not implemented')
async def render(self): async def render(self):
#if self.exists: # if self.exists:
#return # return
await self._render() await self._render()
@ -1359,7 +1442,7 @@ class Search(PHPFile):
notindexed=mtime, notindexed=mtime,
tokenize=porter tokenize=porter
)''' )'''
) )
self.is_changed = False self.is_changed = False
def __exit__(self): def __exit__(self):
@ -1548,7 +1631,7 @@ class Category(dict):
@property @property
def url(self): def url(self):
if len(self.name): 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: else:
url = '%s/' % (settings.site.get('url')) url = '%s/' % (settings.site.get('url'))
return url return url
@ -1566,7 +1649,7 @@ class Category(dict):
if len(self.name): if len(self.name):
return os.path.join( return os.path.join(
settings.paths.get('build'), settings.paths.get('build'),
'category', CATEGORY,
self.name self.name
) )
else: else:
@ -1665,17 +1748,17 @@ class Category(dict):
'posts': posts, 'posts': posts,
} }
def indexfpath(self, subpath=None): def indexfpath(self, subpath=None, fname=HTMLFILE):
if subpath: if subpath:
return os.path.join( return os.path.join(
self.dpath, self.dpath,
subpath, subpath,
'index.html' fname
) )
else: else:
return os.path.join( return os.path.join(
self.dpath, self.dpath,
'index.html' fname
) )
async def render_feed(self, xmlformat): async def render_feed(self, xmlformat):
@ -1711,8 +1794,9 @@ class Category(dict):
fe.category({ fe.category({
'term': post.category, 'term': post.category,
'label': post.category, 'label': post.category,
'scheme': "%s/category/%s/" % ( 'scheme': "%s/%s/%s/" % (
settings.site.get('url'), settings.site.get('url'),
CATEGORY,
post.category post.category
) )
}) })
@ -1758,6 +1842,32 @@ class Category(dict):
) )
writepath(self.indexfpath(), r) 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): async def render_archives(self):
for year in self.years.keys(): for year in self.years.keys():
if year == self.newest_year: if year == self.newest_year:
@ -1779,7 +1889,7 @@ class Category(dict):
end = index end = index
if self.is_uptodate(fpath, self[self.sortedkeys[start]].dt): 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: else:
logger.info("updating %s / %d", self.name, year) logger.info("updating %s / %d", self.name, year)
logger.info("getting posts from %d to %d", start, end) 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 # I don't know why end needs the +1, but without that
# some posts disappear # some posts disappear
# TODO figure this out... # TODO figure this out...
self.get_posts(start, end+1), self.get_posts(start, end + 1),
tyear tyear
) )
) )
@ -1819,9 +1929,10 @@ class Category(dict):
self.name self.name
) )
async def render(self): async def render(self):
await self.render_feeds() 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_paginated:
if not self.is_uptodate(self.indexfpath(), self.newest()): if not self.is_uptodate(self.indexfpath(), self.newest()):
logger.info( logger.info(
@ -1829,6 +1940,7 @@ class Category(dict):
self.name self.name
) )
await self.render_flat() await self.render_flat()
else: else:
logger.info( logger.info(
'%s flat index is up to date', '%s flat index is up to date',
@ -1839,6 +1951,7 @@ class Category(dict):
await self.render_archives() await self.render_archives()
class Sitemap(dict): class Sitemap(dict):
@property @property
def mtime(self): def mtime(self):
@ -1875,7 +1988,7 @@ class WebmentionIO(object):
newest = 0 newest = 0
content = settings.paths.get('content') content = settings.paths.get('content')
for e in glob.glob(os.path.join(content, '*', '*', '*.md')): for e in glob.glob(os.path.join(content, '*', '*', '*.md')):
if os.path.basename(e) == 'index.md': if os.path.basename(e) == MDFILE:
continue continue
# filenames are like [received epoch]-[slugified source url].md # filenames are like [received epoch]-[slugified source url].md
try: try:
@ -1889,7 +2002,7 @@ class WebmentionIO(object):
continue continue
if mtime > newest: if mtime > newest:
newest = mtime newest = mtime
return arrow.get(newest+1) return arrow.get(newest + 1)
def makecomment(self, webmention): def makecomment(self, webmention):
if 'published_ts' in webmention.get('data'): if 'published_ts' in webmention.get('data'):
@ -1899,12 +2012,19 @@ class WebmentionIO(object):
else: else:
dt = arrow.get(webmention.get('data').get('published')) 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 # ignore selfpings
if slug == settings.site.get('name'): if slug == settings.site.get('name'):
return 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): if not len(fdir):
logger.error( logger.error(
"couldn't find post for incoming webmention: %s", "couldn't find post for incoming webmention: %s",
@ -1962,6 +2082,7 @@ class WebmentionIO(object):
logger.error('failed to query webmention.io: %s', e) logger.error('failed to query webmention.io: %s', e)
pass pass
def make(): def make():
start = int(round(time.time() * 1000)) start = int(round(time.time() * 1000))
last = 0 last = 0
@ -1989,7 +2110,7 @@ def make():
frontposts = Category() frontposts = Category()
home = Home(settings.paths.get('home')) 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) post = Singular(e)
# deal with images, if needed # deal with images, if needed
for i in post.images.values(): for i in post.images.values():
@ -2060,11 +2181,18 @@ def make():
for e in glob.glob(os.path.join(content, '*.*')): for e in glob.glob(os.path.join(content, '*.*')):
if e.endswith('.md'): if e.endswith('.md'):
continue 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): if os.path.exists(t) and mtime(e) <= mtime(t):
continue continue
cp(e, t) 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)) end = int(round(time.time() * 1000))
logger.info('process took %d ms' % (end - start)) logger.info('process took %d ms' % (end - start))

249
pandoc.py
View file

@ -7,34 +7,43 @@ __email__ = "mail@petermolnar.net"
import subprocess import subprocess
import logging import logging
class PandocMarkdown(str):
def __new__(cls, text): class PandocBase(str):
""" Pandoc command line call with piped in- and output """ in_format = 'html'
cmd = ( 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', 'pandoc',
'-o-', '-o-',
'--from=markdown+%s' % ( conv_to,
'+'.join([ conv_from,
'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', '--quiet',
'--no-highlight' '--no-highlight'
) ]
if self.columns:
cmd.append(self.columns)
p = subprocess.Popen( p = subprocess.Popen(
cmd, tuple(cmd),
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
@ -48,46 +57,158 @@ class PandocMarkdown(str):
stderr stderr
) )
r = stdout.decode('utf-8').strip() r = stdout.decode('utf-8').strip()
return str.__new__(cls, r) self.result = r
class PandocHTML(str): def __str__(self):
def __new__(cls, text): return str(self.result)
""" 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()) def __repr__(self):
if stderr: return str(self.result)
logging.warning(
"Error during pandoc covert:\n\t%s\n\t%s",
cmd, class PandocMarkdown(PandocBase):
stderr in_format = 'markdown'
) in_options = [
r = stdout.decode('utf-8').strip() 'footnotes',
return str.__new__(cls, r) '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)

View file

@ -47,6 +47,9 @@ site = struct({
"name": "petermolnar.net", "name": "petermolnar.net",
"image": "https://petermolnar.net/favicon.ico", "image": "https://petermolnar.net/favicon.ico",
"license": "https://spdx.org/licenses/%s.html" % (licence['_default']), "license": "https://spdx.org/licenses/%s.html" % (licence['_default']),
#"sameAs": [
#"dat://8d03735af11d82fff82028e0f830f9ac470f5e9fbe10ab5eb6feb877232714a2"
#],
"author": { "author": {
"@context": "http://schema.org", "@context": "http://schema.org",
"@type": "Person", "@type": "Person",
@ -88,30 +91,28 @@ site = struct({
"@type": "FollowAction", "@type": "FollowAction",
"url": "https://petermolnar.net/follow/", "url": "https://petermolnar.net/follow/",
"name": "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 = { menu = {
'home': { 'home': {
@ -170,6 +171,29 @@ photo = struct({
}, },
}) })
bye = """
"""
_parser = argparse.ArgumentParser(description='Parameters for NASG') _parser = argparse.ArgumentParser(description='Parameters for NASG')
_booleanparams = { _booleanparams = {
'regenerate': 'force downsizing images', 'regenerate': 'force downsizing images',

View file

@ -10,6 +10,7 @@
<link rel="alternate" type="application/json" href="{{ post.url }}index.json" /> <link rel="alternate" type="application/json" href="{{ post.url }}index.json" />
<link rel="alternate" type="application/ld+json" href="{{ post.url }}index.json" /> <link rel="alternate" type="application/ld+json" href="{{ post.url }}index.json" />
<link rel="alternate" type="application/mf2+json" href="https://pin13.net/mf2/?url={{ post.url|urlencode }}" /> <link rel="alternate" type="application/mf2+json" href="https://pin13.net/mf2/?url={{ post.url|urlencode }}" />
<link rel="alternate" type="text/plain" href="{{ post.url }}index.txt" />
<meta property="og:title" content="{{ post.headline }}" /> <meta property="og:title" content="{{ post.headline }}" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:url" content="{{ post.url }}" /> <meta property="og:url" content="{{ post.url }}" />
@ -17,11 +18,11 @@
<meta property="article:published_time" content="{{ post.datePublished }}" /> <meta property="article:published_time" content="{{ post.datePublished }}" />
<meta property="article:modified_time" content="{{ post.dateModified }}" /> <meta property="article:modified_time" content="{{ post.dateModified }}" />
<meta property="article:author" content="{{ post.author.name }} ({{ post.author.email}})" /> <meta property="article:author" content="{{ post.author.name }} ({{ post.author.email}})" />
{% if post.image.url is defined %} {% if post.image is iterable %}
<meta property="og:image" content="{{ post.image.url }}" /> <meta property="og:image" content="{{ post.image[0].url }}" />
<meta property="og:image:type" content="{{ post.image.encodingFormat }}" /> <meta property="og:image:type" content="{{ post.image[0].encodingFormat }}" />
<meta property="og:image:width" content="{{ post.image.width }}" /> <meta property="og:image:width" content="{{ post.image[0].width }}" />
<meta property="og:image:height" content="{{ post.image.height }}" /> <meta property="og:image:height" content="{{ post.image[0].height }}" />
{% else %} {% else %}
<meta property="og:image" content="{{ post.image }}" /> <meta property="og:image" content="{{ post.image }}" />
{% endif %} {% endif %}
@ -216,24 +217,6 @@
{% endfor %} {% endfor %}
</section> </section>
<section class="encourage">
<h2>Encourage creation!</h2>
<p>
If this entry helped you, or you simply liked it, leave a tip via <br />
{% set counters = {'donation': False} %}
{% for donate in post.potentialAction %}
{% if 'DonateAction' == donate['@type'] %}
{% if counters.donation %} or {% endif %}
<a href="{{ donate.url }}">
<svg width="16" height="16">
<use xlink:href="#icon-{{ donate.name }}"></use>
</svg> {{ donate.description }}</a>
{% if counters.update({'donation': True}) %} {% endif %}
{% endif %}
{% endfor %}
</p>
</section>
{% if post.comment|length %} {% if post.comment|length %}
<section class="comments"> <section class="comments">
<h2><a id="comments"></a>Responses</h2> <h2><a id="comments"></a>Responses</h2>

10
templates/Singular.j2.txt Normal file
View file

@ -0,0 +1,10 @@
---
Title: {{ post.headline }}
Author: {{ post.author.name }} <{{ post.author.email}}>
URL: {{ post.url }}
Published: {{ post.datePublished|printdate }}
---
{{ summary }}
{{ content }}

View file

@ -288,19 +288,6 @@ li p {
margin: 0; margin: 0;
} }
.encourage, .encourage a {
color: #090;
}
.encourage a {
color: #0a0;
font-weight: bold;
}
.encourage h2 {
border-color: #090;
}
.footnotes hr:before { .footnotes hr:before {
content: 'Links'; content: 'Links';
color: #ccc; color: #ccc;

View file

@ -77,9 +77,6 @@
<symbol id="icon-star" viewBox="0 0 16 16"> <symbol id="icon-star" viewBox="0 0 16 16">
<path d="M16 6.204l-5.528-0.803-2.472-5.009-2.472 5.009-5.528 0.803 4 3.899-0.944 5.505 4.944-2.599 4.944 2.599-0.944-5.505 4-3.899z"></path> <path d="M16 6.204l-5.528-0.803-2.472-5.009-2.472 5.009-5.528 0.803 4 3.899-0.944 5.505 4.944-2.599 4.944 2.599-0.944-5.505 4-3.899z"></path>
</symbol> </symbol>
<symbol id="icon-reply" viewBox="0 0 16 16">
<path d="M7 12.119v3.881l-6-6 6-6v3.966c6.98 0.164 6.681-4.747 4.904-7.966 4.386 4.741 3.455 12.337-4.904 12.119z"></path>
</symbol>
<symbol id="button-indieweb" viewBox="0 0 80 15"> <symbol id="button-indieweb" viewBox="0 0 80 15">
<rect width="80" height="15" fill="#666"/> <rect width="80" height="15" fill="#666"/>
<rect x="1" y="1" width="78" height="13" fill="#fff"/> <rect x="1" y="1" width="78" height="13" fill="#fff"/>

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB