2017-05-23 11:14:47 +01:00
|
|
|
#!/usr/bin/env python3
|
2017-12-17 17:37:32 +00:00
|
|
|
|
|
|
|
__author__ = "Peter Molnar"
|
2019-01-05 11:55:40 +00:00
|
|
|
__copyright__ = "Copyright 2017-2019, Peter Molnar"
|
2018-12-03 10:36:10 +00:00
|
|
|
__license__ = "apache-2.0"
|
2017-12-17 17:37:32 +00:00
|
|
|
__maintainer__ = "Peter Molnar"
|
2018-04-30 20:44:04 +01:00
|
|
|
__email__ = "mail@petermolnar.net"
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
import glob
|
2017-05-23 11:14:47 +01:00
|
|
|
import os
|
2018-07-20 16:45:42 +01:00
|
|
|
import time
|
2017-05-23 11:14:47 +01:00
|
|
|
import re
|
|
|
|
import asyncio
|
2018-07-22 11:33:59 +01:00
|
|
|
import sqlite3
|
2018-07-25 13:24:31 +01:00
|
|
|
import json
|
2018-12-27 19:48:06 +00:00
|
|
|
import queue
|
2019-02-16 00:14:12 +00:00
|
|
|
import base64
|
2018-07-20 16:45:42 +01:00
|
|
|
from shutil import copy2 as cp
|
2019-06-24 13:52:44 +01:00
|
|
|
from shutil import rmtree
|
2017-10-27 10:29:33 +01:00
|
|
|
from math import ceil
|
2018-07-20 16:45:42 +01:00
|
|
|
from urllib.parse import urlparse
|
|
|
|
from collections import OrderedDict, namedtuple
|
2018-11-04 23:27:53 +00:00
|
|
|
import logging
|
2019-02-25 22:40:01 +00:00
|
|
|
import csv
|
2019-02-16 00:14:12 +00:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
import arrow
|
2017-05-23 11:14:47 +01:00
|
|
|
import langdetect
|
2017-10-27 10:29:33 +01:00
|
|
|
import wand.image
|
2019-05-23 09:06:34 +01:00
|
|
|
import filetype
|
2018-07-20 16:45:42 +01:00
|
|
|
import jinja2
|
2019-01-05 11:55:40 +00:00
|
|
|
import yaml
|
2019-07-14 20:48:49 +01:00
|
|
|
|
|
|
|
# python-frontmatter
|
2019-01-15 21:28:58 +00:00
|
|
|
import frontmatter
|
2017-11-30 17:01:14 +00:00
|
|
|
from feedgen.feed import FeedGenerator
|
2019-07-14 20:48:49 +01:00
|
|
|
|
|
|
|
# unicode-slugify
|
2018-07-25 13:24:31 +01:00
|
|
|
from slugify import slugify
|
2018-07-22 17:59:26 +01:00
|
|
|
import requests
|
2019-02-16 00:14:12 +00:00
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
from pandoc import PandocMD2HTML, PandocMD2TXT, PandocHTML2TXT
|
2019-02-08 23:32:52 +00:00
|
|
|
from meta import Exif
|
2018-07-20 16:45:42 +01:00
|
|
|
import settings
|
2019-02-16 00:14:12 +00:00
|
|
|
from settings import struct
|
2018-07-22 17:59:26 +01:00
|
|
|
import keys
|
2018-07-20 16:45:42 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
logger = logging.getLogger("NASG")
|
2018-11-04 23:27:53 +00:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
MarkdownImage = namedtuple("MarkdownImage", ["match", "alt", "fname", "title", "css"])
|
2018-07-20 16:45:42 +01:00
|
|
|
|
|
|
|
J2 = jinja2.Environment(
|
2019-06-25 22:48:04 +01:00
|
|
|
loader=jinja2.FileSystemLoader(searchpath=settings.paths.get("tmpl")),
|
2018-07-20 16:45:42 +01:00
|
|
|
lstrip_blocks=True,
|
2019-06-25 22:48:04 +01:00
|
|
|
trim_blocks=True,
|
2018-07-20 16:45:42 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
RE_MDIMG = re.compile(
|
2019-06-25 22:48:04 +01:00
|
|
|
r"(?P<match>!\[(?P<alt>[^\]]+)?\]\((?P<fname>[^\s\]]+)"
|
|
|
|
r"(?:\s[\'\"](?P<title>[^\"\']+)[\'\"])?\)(?:{(?P<css>[^\}]+)\})?)",
|
|
|
|
re.IGNORECASE,
|
2018-07-20 16:45:42 +01:00
|
|
|
)
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
RE_CODE = re.compile(r"^(?:[~`]{3,4}).+$", re.MULTILINE)
|
|
|
|
|
|
|
|
RE_PRECODE = re.compile(r'<pre class="([^"]+)"><code>')
|
2018-07-20 16:45:42 +01:00
|
|
|
|
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
def mtime(path):
|
2019-02-17 20:17:35 +00:00
|
|
|
""" return seconds level mtime or 0 (chomp microsecs) """
|
2019-02-16 00:14:12 +00:00
|
|
|
if os.path.exists(path):
|
|
|
|
return int(os.path.getmtime(path))
|
|
|
|
return 0
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-01-05 11:55:40 +00:00
|
|
|
def utfyamldump(data):
|
2019-02-17 20:17:35 +00:00
|
|
|
""" dump YAML with actual UTF-8 chars """
|
2019-06-25 22:48:04 +01:00
|
|
|
return yaml.dump(data, default_flow_style=False, indent=4, allow_unicode=True)
|
2018-11-19 16:16:52 +00:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
def url2slug(url, limit=200):
|
2019-02-17 20:17:35 +00:00
|
|
|
""" convert URL to max 200 char ASCII string """
|
2019-06-25 22:48:04 +01:00
|
|
|
return slugify(re.sub(r"^https?://(?:www)?", "", url), only_ascii=True, lower=True)[
|
|
|
|
:limit
|
|
|
|
]
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
J2.filters["url2slug"] = url2slug
|
2019-02-16 00:14:12 +00:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
def rfc3339todt(rfc3339):
|
2019-02-17 20:17:35 +00:00
|
|
|
""" nice dates for humans """
|
2019-06-25 22:48:04 +01:00
|
|
|
t = arrow.get(rfc3339).format("YYYY-MM-DD HH:mm ZZZ")
|
2019-02-08 23:32:52 +00:00
|
|
|
return "%s" % (t)
|
2019-02-07 19:27:15 +00:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
J2.filters["printdate"] = rfc3339todt
|
2019-02-16 00:14:12 +00:00
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
|
|
|
|
def extractlicense(url):
|
|
|
|
""" extract license name """
|
|
|
|
n, e = os.path.splitext(os.path.basename(url))
|
|
|
|
return n.upper()
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
|
|
|
|
J2.filters["extractlicense"] = extractlicense
|
2019-03-22 15:49:24 +00:00
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
RE_MYURL = re.compile(
|
2019-06-25 22:48:04 +01:00
|
|
|
r'(^(%s[^"]+)$|"(%s[^"]+)")' % (settings.site.url, settings.site.url)
|
2019-02-16 00:14:12 +00:00
|
|
|
)
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
def relurl(text, baseurl=None):
|
|
|
|
if not baseurl:
|
|
|
|
baseurl = settings.site.url
|
|
|
|
for match, standalone, href in RE_MYURL.findall(text):
|
|
|
|
needsquotes = False
|
|
|
|
if len(href):
|
|
|
|
needsquotes = True
|
|
|
|
url = href
|
|
|
|
else:
|
|
|
|
url = standalone
|
|
|
|
|
|
|
|
r = os.path.relpath(url, baseurl)
|
2019-06-25 22:48:04 +01:00
|
|
|
if url.endswith("/") and not r.endswith("/"):
|
2019-05-23 09:06:34 +01:00
|
|
|
r = "%s/%s" % (r, settings.filenames.html)
|
2019-02-16 00:14:12 +00:00
|
|
|
if needsquotes:
|
|
|
|
r = '"%s"' % r
|
|
|
|
logger.debug("RELURL: %s => %s (base: %s)", match, r, baseurl)
|
|
|
|
text = text.replace(match, r)
|
|
|
|
return text
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
J2.filters["relurl"] = relurl
|
2018-11-19 16:16:52 +00:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2018-11-04 23:27:53 +00:00
|
|
|
def writepath(fpath, content, mtime=0):
|
2019-02-17 20:17:35 +00:00
|
|
|
""" f.write with extras """
|
2018-11-04 23:27:53 +00:00
|
|
|
d = os.path.dirname(fpath)
|
|
|
|
if not os.path.isdir(d):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.debug("creating directory tree %s", d)
|
2018-11-04 23:27:53 +00:00
|
|
|
os.makedirs(d)
|
2018-11-10 20:49:13 +00:00
|
|
|
if isinstance(content, str):
|
2019-06-25 22:48:04 +01:00
|
|
|
mode = "wt"
|
2018-11-10 20:49:13 +00:00
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
mode = "wb"
|
2018-11-10 20:49:13 +00:00
|
|
|
with open(fpath, mode) as f:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("writing file %s", fpath)
|
2018-11-04 23:27:53 +00:00
|
|
|
f.write(content)
|
|
|
|
|
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
class cached_property(object):
|
2018-08-08 09:42:42 +01:00
|
|
|
""" extermely simple cached_property decorator:
|
|
|
|
whenever something is called as @cached_property, on first run, the
|
|
|
|
result is calculated, then the class method is overwritten to be
|
|
|
|
a property, contaning the result from the method
|
|
|
|
"""
|
2018-11-10 20:49:13 +00:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
def __init__(self, method, name=None):
|
|
|
|
self.method = method
|
|
|
|
self.name = name or method.__name__
|
2018-11-03 09:48:37 +00:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
def __get__(self, inst, cls):
|
|
|
|
if inst is None:
|
|
|
|
return self
|
|
|
|
result = self.method(inst)
|
|
|
|
setattr(inst, self.name, result)
|
|
|
|
return result
|
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
class AQ:
|
2019-02-16 00:14:12 +00:00
|
|
|
""" Async queue which starts execution right on population """
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
def __init__(self):
|
|
|
|
self.loop = asyncio.get_event_loop()
|
|
|
|
self.queue = asyncio.Queue(loop=self.loop)
|
|
|
|
|
|
|
|
def put(self, task):
|
|
|
|
self.queue.put(asyncio.ensure_future(task))
|
|
|
|
|
|
|
|
async def consume(self):
|
|
|
|
while not self.queue.empty():
|
|
|
|
item = await self.queue.get()
|
|
|
|
self.queue.task_done()
|
2019-02-25 22:40:01 +00:00
|
|
|
# asyncio.gather() ?
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
consumer = asyncio.ensure_future(self.consume())
|
|
|
|
self.loop.run_until_complete(consumer)
|
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
class Webmention(object):
|
2019-02-16 00:14:12 +00:00
|
|
|
""" outgoing webmention class """
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-02-03 16:40:34 +00:00
|
|
|
def __init__(self, source, target, dpath, mtime=0):
|
|
|
|
self.source = source
|
|
|
|
self.target = target
|
2019-02-07 19:27:15 +00:00
|
|
|
self.dpath = dpath
|
2019-02-03 16:40:34 +00:00
|
|
|
if not mtime:
|
|
|
|
mtime = arrow.utcnow().timestamp
|
|
|
|
self.mtime = mtime
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def fpath(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.dpath, "%s.ping" % (url2slug(self.target, 200)))
|
2018-08-15 11:02:59 +01:00
|
|
|
|
2019-05-05 13:04:36 +01:00
|
|
|
def check_syndication(self):
|
|
|
|
""" this is very specific to webmention.io and brid.gy publish """
|
2019-05-22 20:54:10 +01:00
|
|
|
|
2019-05-05 13:04:36 +01:00
|
|
|
if "fed.brid.gy" in self.target:
|
|
|
|
return
|
|
|
|
if "brid.gy" not in self.target:
|
|
|
|
return
|
|
|
|
if not self.exists:
|
|
|
|
return
|
|
|
|
|
|
|
|
with open(self.fpath) as f:
|
|
|
|
txt = f.read()
|
2019-05-22 20:54:10 +01:00
|
|
|
|
|
|
|
if "telegraph.p3k.io" not in txt:
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
|
|
|
maybe = json.loads(txt)
|
|
|
|
if "location" not in maybe:
|
2019-05-05 13:04:36 +01:00
|
|
|
return
|
2019-05-22 20:54:10 +01:00
|
|
|
if "http_body" not in maybe:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.debug(
|
|
|
|
"trying to re-fetch %s for %s", maybe["location"], self.fpath
|
|
|
|
)
|
2019-05-22 20:54:10 +01:00
|
|
|
wio = requests.get(maybe["location"])
|
|
|
|
if wio.status_code != requests.codes.ok:
|
2019-05-05 13:04:36 +01:00
|
|
|
return
|
2019-05-22 20:54:10 +01:00
|
|
|
maybe = wio.json()
|
|
|
|
logger.debug("response: %s", maybe)
|
|
|
|
with open(self.fpath, "wt") as update:
|
|
|
|
update.write(json.dumps(maybe, sort_keys=True, indent=4))
|
|
|
|
if "url" in maybe["http_body"]:
|
|
|
|
data = json.loads(maybe["http_body"])
|
|
|
|
url = data["url"]
|
|
|
|
sp = os.path.join(self.dpath, "%s.copy" % url2slug(url, 200))
|
|
|
|
if os.path.exists(sp):
|
|
|
|
return
|
|
|
|
with open(sp, "wt") as f:
|
|
|
|
logger.info("writing syndication copy %s to %s", url, sp)
|
|
|
|
f.write(url)
|
|
|
|
except Exception as e:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("failed to fetch syndication URL for %s: %s", self.dpath, e)
|
2019-05-22 20:54:10 +01:00
|
|
|
pass
|
2019-05-05 13:04:36 +01:00
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
@property
|
|
|
|
def exists(self):
|
|
|
|
if not os.path.isfile(self.fpath):
|
|
|
|
return False
|
2019-02-16 00:14:12 +00:00
|
|
|
elif mtime(self.fpath) > self.mtime:
|
2018-08-15 11:02:59 +01:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
def save(self, content):
|
2018-11-04 23:27:53 +00:00
|
|
|
writepath(self.fpath, content)
|
2018-08-15 11:02:59 +01:00
|
|
|
|
2018-11-19 14:36:06 +00:00
|
|
|
async def send(self):
|
2018-08-15 11:02:59 +01:00
|
|
|
if self.exists:
|
2019-05-05 13:04:36 +01:00
|
|
|
self.check_syndication()
|
2018-08-15 11:02:59 +01:00
|
|
|
return
|
2019-06-25 22:48:04 +01:00
|
|
|
elif settings.args.get("noping"):
|
|
|
|
self.save("noping entry at %s" % arrow.now())
|
2019-04-10 09:27:37 +01:00
|
|
|
return
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
telegraph_url = "https://telegraph.p3k.io/webmention"
|
2018-08-15 11:02:59 +01:00
|
|
|
telegraph_params = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"token": "%s" % (keys.telegraph.get("token")),
|
|
|
|
"source": "%s" % (self.source),
|
|
|
|
"target": "%s" % (self.target),
|
2018-08-15 11:02:59 +01:00
|
|
|
}
|
|
|
|
r = requests.post(telegraph_url, data=telegraph_params)
|
2018-11-04 23:27:53 +00:00
|
|
|
logger.info(
|
2019-06-25 22:48:04 +01:00
|
|
|
"sent webmention to telegraph from %s to %s", self.source, self.target
|
2018-08-15 11:02:59 +01:00
|
|
|
)
|
|
|
|
if r.status_code not in [200, 201, 202]:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("sending failed: %s %s", r.status_code, r.text)
|
2018-08-15 11:02:59 +01:00
|
|
|
else:
|
2018-11-10 20:49:13 +00:00
|
|
|
self.save(r.text)
|
2018-08-15 11:02:59 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
class MarkdownDoc(object):
|
2019-02-16 00:14:12 +00:00
|
|
|
""" Base class for anything that is stored as .md """
|
2019-06-25 22:48:04 +01:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
@property
|
|
|
|
def mtime(self):
|
2019-02-16 00:14:12 +00:00
|
|
|
return mtime(self.fpath)
|
2019-01-15 21:28:58 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def dt(self):
|
2019-05-22 10:15:21 +01:00
|
|
|
""" this returns a timestamp, not an arrow object """
|
2019-01-15 21:28:58 +00:00
|
|
|
maybe = self.mtime
|
2019-06-25 22:48:04 +01:00
|
|
|
for key in ["published", "date"]:
|
2019-01-15 21:28:58 +00:00
|
|
|
t = self.meta.get(key, None)
|
2019-06-25 22:48:04 +01:00
|
|
|
if t and "null" != t:
|
2019-01-15 21:28:58 +00:00
|
|
|
try:
|
|
|
|
t = arrow.get(t)
|
|
|
|
if t.timestamp > maybe:
|
|
|
|
maybe = t.timestamp
|
|
|
|
except Exception as e:
|
|
|
|
logger.error(
|
2019-06-25 22:48:04 +01:00
|
|
|
"failed to parse date: %s for key %s in %s", t, key, self.fpath
|
2019-01-15 21:28:58 +00:00
|
|
|
)
|
|
|
|
return maybe
|
2019-01-05 11:55:40 +00:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def _parsed(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
with open(self.fpath, mode="rt") as f:
|
|
|
|
logger.debug("parsing YAML+MD file %s", self.fpath)
|
2019-01-15 21:28:58 +00:00
|
|
|
meta, txt = frontmatter.parse(f.read())
|
2019-06-25 22:48:04 +01:00
|
|
|
return (meta, txt)
|
2019-01-15 21:28:58 +00:00
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def meta(self):
|
|
|
|
return self._parsed[0]
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def content(self):
|
|
|
|
return self._parsed[1]
|
2017-06-12 15:40:30 +01:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def html_content(self):
|
|
|
|
c = "%s" % (self.content)
|
2019-03-22 15:49:24 +00:00
|
|
|
if not len(c):
|
|
|
|
return c
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if hasattr(self, "images") and len(self.images):
|
2018-07-20 16:45:42 +01:00
|
|
|
for match, img in self.images.items():
|
2018-09-04 21:58:25 +01:00
|
|
|
c = c.replace(match, str(img))
|
2019-03-22 15:49:24 +00:00
|
|
|
c = str(PandocMD2HTML(c))
|
2019-06-25 22:48:04 +01:00
|
|
|
c = RE_PRECODE.sub('<pre><code lang="\g<1>" class="language-\g<1>">', c)
|
2019-03-22 15:49:24 +00:00
|
|
|
return c
|
2017-10-27 10:29:33 +01:00
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
class Comment(MarkdownDoc):
|
|
|
|
def __init__(self, fpath):
|
|
|
|
self.fpath = fpath
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def dt(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
maybe = self.meta.get("date")
|
|
|
|
if maybe and "null" != maybe:
|
2018-07-20 16:45:42 +01:00
|
|
|
dt = arrow.get(maybe)
|
|
|
|
else:
|
2019-02-16 00:14:12 +00:00
|
|
|
dt = arrow.get(mtime(self.fpath))
|
2018-07-20 16:45:42 +01:00
|
|
|
return dt
|
2017-06-04 11:38:36 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2019-02-08 23:32:52 +00:00
|
|
|
def targetname(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
t = urlparse(self.meta.get("target"))
|
|
|
|
return os.path.split(t.path.lstrip("/"))[0]
|
|
|
|
# t = urlparse(self.meta.get('target'))
|
|
|
|
# return t.path.rstrip('/').strip('/').split('/')[-1]
|
2017-06-04 11:38:36 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def source(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("source")
|
2017-10-27 10:29:33 +01:00
|
|
|
|
2018-03-28 15:19:14 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def author(self):
|
|
|
|
r = {
|
2019-02-08 23:32:52 +00:00
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "Person",
|
2019-06-25 22:48:04 +01:00
|
|
|
"name": urlparse(self.source).hostname,
|
|
|
|
"url": self.source,
|
2018-07-20 16:45:42 +01:00
|
|
|
}
|
2019-06-25 22:48:04 +01:00
|
|
|
author = self.meta.get("author")
|
2018-07-20 16:45:42 +01:00
|
|
|
if not author:
|
|
|
|
return r
|
2019-06-25 22:48:04 +01:00
|
|
|
if "name" in author:
|
|
|
|
r.update({"name": self.meta.get("author").get("name")})
|
|
|
|
elif "url" in author:
|
|
|
|
r.update({"name": urlparse(self.meta.get("author").get("url")).hostname})
|
2018-07-20 16:45:42 +01:00
|
|
|
return r
|
2018-03-28 15:19:14 +01:00
|
|
|
|
2017-10-28 19:08:40 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def type(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("type", "webmention")
|
2019-02-25 22:40:01 +00:00
|
|
|
# if len(self.content):
|
2019-06-25 22:48:04 +01:00
|
|
|
# maybe = clean(self.content, strip=True)
|
2019-02-25 22:40:01 +00:00
|
|
|
# if maybe in UNICODE_EMOJI:
|
|
|
|
# return maybe
|
2018-03-28 15:19:14 +01:00
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
@cached_property
|
|
|
|
def jsonld(self):
|
|
|
|
r = {
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "Comment",
|
2019-02-08 23:32:52 +00:00
|
|
|
"author": self.author,
|
2019-02-07 19:27:15 +00:00
|
|
|
"url": self.source,
|
2019-06-25 22:48:04 +01:00
|
|
|
"discussionUrl": self.meta.get("target"),
|
2019-02-07 19:27:15 +00:00
|
|
|
"datePublished": str(self.dt),
|
2019-06-25 22:48:04 +01:00
|
|
|
"disambiguatingDescription": self.type,
|
2018-03-28 15:19:14 +01:00
|
|
|
}
|
2019-02-07 19:27:15 +00:00
|
|
|
return r
|
2018-03-28 15:19:14 +01:00
|
|
|
|
2017-11-30 17:01:14 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
class Gone(object):
|
|
|
|
"""
|
|
|
|
Gone object for delete entries
|
|
|
|
"""
|
2018-04-30 20:44:04 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
def __init__(self, fpath):
|
|
|
|
self.fpath = fpath
|
2019-02-16 00:14:12 +00:00
|
|
|
self.mtime = mtime(fpath)
|
2017-11-30 17:01:14 +00:00
|
|
|
|
2019-06-24 13:52:44 +01:00
|
|
|
@property
|
|
|
|
def renderdir(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), self.source)
|
2019-06-24 13:52:44 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.renderdir, settings.filenames.html)
|
2019-06-24 13:52:44 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
2018-07-22 14:52:32 +01:00
|
|
|
def source(self):
|
|
|
|
source, fext = os.path.splitext(os.path.basename(self.fpath))
|
|
|
|
return source
|
2017-11-30 17:01:14 +00:00
|
|
|
|
2019-06-24 13:52:44 +01:00
|
|
|
@property
|
|
|
|
def template(self):
|
|
|
|
return "%s.j2.html" % (self.__class__.__name__)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def tmplvars(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return {"source": self.source}
|
2019-06-24 13:52:44 +01:00
|
|
|
|
|
|
|
async def render(self):
|
|
|
|
if os.path.exists(self.renderfile):
|
|
|
|
rmtree(os.path.dirname(self.renderfile))
|
2019-06-25 22:48:04 +01:00
|
|
|
# logger.info(
|
|
|
|
#'rendering %s to %s',
|
|
|
|
# self.__class__.__name__,
|
|
|
|
# self.source
|
|
|
|
# )
|
|
|
|
# r = J2.get_template(self.template).render(
|
|
|
|
# self.tmplvars
|
|
|
|
# )
|
|
|
|
# writepath(self.renderfile, r)
|
2019-06-24 13:52:44 +01:00
|
|
|
|
2018-04-30 20:44:04 +01:00
|
|
|
|
2018-07-25 13:24:31 +01:00
|
|
|
class Redirect(Gone):
|
2018-07-20 16:45:42 +01:00
|
|
|
"""
|
|
|
|
Redirect object for entries that moved
|
|
|
|
"""
|
2017-11-30 17:01:14 +00:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def target(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
target = ""
|
|
|
|
with open(self.fpath, "rt") as f:
|
2018-07-20 16:45:42 +01:00
|
|
|
target = f.read().strip()
|
|
|
|
return target
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2019-06-24 13:52:44 +01:00
|
|
|
@property
|
|
|
|
def tmplvars(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return {"source": self.source, "target": self.target}
|
|
|
|
|
2017-07-17 14:21:28 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
class Singular(MarkdownDoc):
|
|
|
|
"""
|
|
|
|
A Singular object: a complete representation of a post, including
|
|
|
|
all it's comments, files, images, etc
|
|
|
|
"""
|
2018-07-22 14:52:32 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
def __init__(self, fpath):
|
|
|
|
self.fpath = fpath
|
2018-07-20 16:45:42 +01:00
|
|
|
n = os.path.dirname(fpath)
|
|
|
|
self.name = os.path.basename(n)
|
|
|
|
self.category = os.path.basename(os.path.dirname(n))
|
2018-07-22 17:59:26 +01:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def files(self):
|
|
|
|
"""
|
|
|
|
An array of files present at the same directory level as
|
|
|
|
the Singular object, excluding hidden (starting with .) and markdown
|
|
|
|
(ending with .md) files
|
|
|
|
"""
|
|
|
|
return [
|
|
|
|
k
|
2019-06-25 22:48:04 +01:00
|
|
|
for k in glob.glob(os.path.join(os.path.dirname(self.fpath), "*.*"))
|
|
|
|
if not k.startswith(".")
|
2018-07-20 16:45:42 +01:00
|
|
|
]
|
2018-03-29 17:07:53 +01:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
@property
|
|
|
|
def updated(self):
|
|
|
|
maybe = self.dt
|
|
|
|
if len(self.comments):
|
|
|
|
for c in self.comments.values():
|
|
|
|
if c.dt > maybe:
|
|
|
|
maybe = c.dt
|
|
|
|
return maybe
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
@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
|
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
@property
|
|
|
|
def sameas(self):
|
2019-05-05 13:04:36 +01:00
|
|
|
r = {}
|
2019-06-25 22:48:04 +01:00
|
|
|
for k in glob.glob(os.path.join(os.path.dirname(self.fpath), "*.copy")):
|
|
|
|
with open(k, "rt") as f:
|
2019-05-05 13:04:36 +01:00
|
|
|
r.update({f.read(): True})
|
|
|
|
return list(r.keys())
|
2019-02-07 19:27:15 +00:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def comments(self):
|
|
|
|
"""
|
|
|
|
An dict of Comment objects keyed with their path, populated from the
|
|
|
|
same directory level as the Singular objects
|
|
|
|
"""
|
2018-07-22 17:59:26 +01:00
|
|
|
comments = {}
|
2018-07-20 16:45:42 +01:00
|
|
|
files = [
|
|
|
|
k
|
2019-06-25 22:48:04 +01:00
|
|
|
for k in glob.glob(os.path.join(os.path.dirname(self.fpath), "*.md"))
|
2019-05-23 09:06:34 +01:00
|
|
|
if os.path.basename(k) != settings.filenames.md
|
2018-07-20 16:45:42 +01:00
|
|
|
]
|
|
|
|
for f in files:
|
|
|
|
c = Comment(f)
|
|
|
|
comments[c.dt.timestamp] = c
|
|
|
|
return comments
|
2017-06-12 15:17:29 +01:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def images(self):
|
|
|
|
"""
|
|
|
|
A dict of WebImage objects, populated by:
|
|
|
|
- images that are present in the Markdown content
|
|
|
|
- and have an actual image file at the same directory level as
|
|
|
|
the Singular object
|
|
|
|
"""
|
|
|
|
images = {}
|
|
|
|
for match, alt, fname, title, css in RE_MDIMG.findall(self.content):
|
|
|
|
mdimg = MarkdownImage(match, alt, fname, title, css)
|
2019-06-25 22:48:04 +01:00
|
|
|
imgpath = os.path.join(os.path.dirname(self.fpath), fname)
|
2018-07-20 16:45:42 +01:00
|
|
|
if imgpath in self.files:
|
2019-05-23 09:06:34 +01:00
|
|
|
kind = filetype.guess(imgpath)
|
2019-06-25 22:48:04 +01:00
|
|
|
if kind and "image" in kind.mime.lower():
|
2018-07-20 16:45:42 +01:00
|
|
|
images.update({match: WebImage(imgpath, mdimg, self)})
|
2019-02-08 23:32:52 +00:00
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("Missing image: %s, referenced in %s", imgpath, self.fpath)
|
2018-07-20 16:45:42 +01:00
|
|
|
return images
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
@property
|
|
|
|
def is_page(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if self.category.startswith("_"):
|
2018-12-27 19:48:06 +00:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2018-06-17 18:30:50 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def is_front(self):
|
|
|
|
"""
|
|
|
|
Returns if the post should be displayed on the front
|
|
|
|
"""
|
2019-02-08 23:32:52 +00:00
|
|
|
if self.category in settings.notinfeed:
|
|
|
|
return False
|
|
|
|
return True
|
2018-06-17 18:30:50 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def is_photo(self):
|
|
|
|
"""
|
|
|
|
This is true if there is a file, with the same name as the entry's
|
|
|
|
directory - so, it's slug -, and that that image believes it's a a
|
|
|
|
photo.
|
|
|
|
"""
|
2018-07-22 17:59:26 +01:00
|
|
|
if len(self.images) != 1:
|
|
|
|
return False
|
|
|
|
photo = next(iter(self.images.values()))
|
2019-05-23 09:06:34 +01:00
|
|
|
maybe = self.fpath.replace(settings.filenames.md, "%s.jpg" % (self.name))
|
2018-07-22 17:59:26 +01:00
|
|
|
if photo.fpath == maybe:
|
2018-07-20 16:45:42 +01:00
|
|
|
return True
|
|
|
|
return False
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2018-07-27 14:55:21 +01:00
|
|
|
@property
|
|
|
|
def photo(self):
|
|
|
|
if not self.is_photo:
|
|
|
|
return None
|
|
|
|
return next(iter(self.images.values()))
|
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def summary(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("summary", "")
|
2017-10-27 10:29:33 +01:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def html_summary(self):
|
2019-03-22 15:49:24 +00:00
|
|
|
c = "%s" % (self.summary)
|
|
|
|
return PandocMD2HTML(c)
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def txt_summary(self):
|
|
|
|
return PandocMD2TXT(self.summary)
|
|
|
|
|
|
|
|
@cached_property
|
|
|
|
def txt_content(self):
|
|
|
|
return PandocMD2TXT(self.content)
|
2017-10-27 15:56:05 +01:00
|
|
|
|
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def title(self):
|
|
|
|
if self.is_reply:
|
|
|
|
return "RE: %s" % self.is_reply
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("title", self.published.format(settings.displaydate))
|
2017-10-27 15:56:05 +01:00
|
|
|
|
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def tags(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("tags", [])
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def syndicate(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
urls = self.meta.get("syndicate", [])
|
2019-02-08 23:32:52 +00:00
|
|
|
urls.append("https://fed.brid.gy/")
|
2018-07-20 16:45:42 +01:00
|
|
|
if self.is_photo:
|
|
|
|
urls.append("https://brid.gy/publish/flickr")
|
|
|
|
return urls
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
def baseN(self, num, b=36, numerals="0123456789abcdefghijklmnopqrstuvwxyz"):
|
2018-07-25 13:24:31 +01:00
|
|
|
"""
|
|
|
|
Creates short, lowercase slug for a number (an epoch) passed
|
|
|
|
"""
|
|
|
|
num = int(num)
|
|
|
|
return ((num == 0) and numerals[0]) or (
|
2019-06-25 22:48:04 +01:00
|
|
|
self.baseN(num // b, b, numerals).lstrip(numerals[0]) + numerals[num % b]
|
2018-07-25 13:24:31 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def shortslug(self):
|
|
|
|
return self.baseN(self.published.timestamp)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def published(self):
|
2019-03-24 15:26:55 +00:00
|
|
|
# ok, so here's a hack: because I have no idea when my older photos
|
|
|
|
# were actually published, any photo from before 2014 will have
|
|
|
|
# the EXIF createdate as publish date
|
2019-06-25 22:48:04 +01:00
|
|
|
pub = arrow.get(self.meta.get("published"))
|
2019-03-24 15:26:55 +00:00
|
|
|
if self.is_photo:
|
2019-06-25 22:48:04 +01:00
|
|
|
maybe = arrow.get(self.photo.exif.get("CreateDate"))
|
2019-03-24 15:26:55 +00:00
|
|
|
if maybe.year < settings.photo.earlyyears:
|
|
|
|
pub = maybe
|
|
|
|
return pub
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
|
|
|
def is_reply(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("in-reply-to", False)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
|
|
|
def is_future(self):
|
2018-07-20 16:45:42 +01:00
|
|
|
if self.published.timestamp > arrow.utcnow().timestamp:
|
2017-05-23 11:14:47 +01:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
@property
|
|
|
|
def to_ping(self):
|
|
|
|
urls = []
|
2019-02-16 00:14:12 +00:00
|
|
|
if not self.is_page and self.is_front:
|
|
|
|
w = Webmention(
|
2019-06-25 22:48:04 +01:00
|
|
|
self.url, "https://fed.brid.gy/", os.path.dirname(self.fpath), self.dt
|
2019-02-16 00:14:12 +00:00
|
|
|
)
|
|
|
|
urls.append(w)
|
2018-08-15 11:02:59 +01:00
|
|
|
if self.is_reply:
|
2019-02-03 16:40:34 +00:00
|
|
|
w = Webmention(
|
2019-06-25 22:48:04 +01:00
|
|
|
self.url, self.is_reply, os.path.dirname(self.fpath), self.dt
|
2019-02-03 16:40:34 +00:00
|
|
|
)
|
|
|
|
urls.append(w)
|
|
|
|
elif self.is_photo:
|
|
|
|
w = Webmention(
|
|
|
|
self.url,
|
2019-06-25 22:48:04 +01:00
|
|
|
"https://brid.gy/publish/flickr",
|
2019-02-03 16:40:34 +00:00
|
|
|
os.path.dirname(self.fpath),
|
2019-06-25 22:48:04 +01:00
|
|
|
self.dt,
|
2019-02-03 16:40:34 +00:00
|
|
|
)
|
2018-08-15 11:02:59 +01:00
|
|
|
urls.append(w)
|
|
|
|
return urls
|
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
|
|
|
def licence(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
k = "_default"
|
2018-07-20 16:45:42 +01:00
|
|
|
if self.category in settings.licence:
|
2019-02-08 23:32:52 +00:00
|
|
|
k = self.category
|
|
|
|
return settings.licence[k]
|
2017-06-02 11:19:55 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
|
|
|
def lang(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
lang = "en"
|
2017-10-27 10:29:33 +01:00
|
|
|
try:
|
2019-06-25 22:48:04 +01:00
|
|
|
lang = langdetect.detect(
|
|
|
|
"\n".join([self.meta.get("title", ""), self.content])
|
|
|
|
)
|
2017-11-10 16:04:05 +00:00
|
|
|
except BaseException:
|
2017-10-27 10:29:33 +01:00
|
|
|
pass
|
|
|
|
return lang
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-06-02 11:19:55 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def url(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return "%s/%s/" % (settings.site.get("url"), self.name)
|
2017-06-03 12:07:03 +01:00
|
|
|
|
2018-08-02 22:47:49 +01:00
|
|
|
@property
|
|
|
|
def has_code(self):
|
|
|
|
if RE_CODE.search(self.content):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
@cached_property
|
|
|
|
def review(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if "review" not in self.meta:
|
2019-02-07 19:27:15 +00:00
|
|
|
return False
|
2019-06-25 22:48:04 +01:00
|
|
|
review = self.meta.get("review")
|
|
|
|
rated, outof = review.get("rating").split("/")
|
2019-02-07 19:27:15 +00:00
|
|
|
r = {
|
|
|
|
"@context": "https://schema.org/",
|
|
|
|
"@type": "Review",
|
|
|
|
"reviewRating": {
|
|
|
|
"@type": "Rating",
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"ratingValue": rated,
|
|
|
|
"bestRating": outof,
|
2019-06-25 22:48:04 +01:00
|
|
|
"worstRating": 1,
|
2019-02-07 19:27:15 +00:00
|
|
|
},
|
2019-06-25 22:48:04 +01:00
|
|
|
"name": review.get("title"),
|
|
|
|
"text": review.get("summary"),
|
|
|
|
"url": review.get("url"),
|
2019-02-07 19:27:15 +00:00
|
|
|
"author": settings.author,
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
|
|
|
|
@cached_property
|
2018-11-04 14:45:54 +00:00
|
|
|
def event(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if "event" not in self.meta:
|
2018-11-04 14:45:54 +00:00
|
|
|
return False
|
2019-06-25 22:48:04 +01:00
|
|
|
event = self.meta.get("event", {})
|
2019-02-07 19:27:15 +00:00
|
|
|
r = {
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "Event",
|
2019-06-25 22:48:04 +01:00
|
|
|
"endDate": str(arrow.get(event.get("end"))),
|
|
|
|
"startDate": str(arrow.get(event.get("start"))),
|
2019-02-07 19:27:15 +00:00
|
|
|
"location": {
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "Place",
|
2019-06-25 22:48:04 +01:00
|
|
|
"address": event.get("location"),
|
|
|
|
"name": event.get("location"),
|
2019-02-07 19:27:15 +00:00
|
|
|
},
|
2019-06-25 22:48:04 +01:00
|
|
|
"name": self.title,
|
2019-02-07 19:27:15 +00:00
|
|
|
}
|
|
|
|
return r
|
|
|
|
|
2019-02-03 16:40:34 +00:00
|
|
|
@cached_property
|
|
|
|
def jsonld(self):
|
|
|
|
r = {
|
|
|
|
"@context": "http://schema.org",
|
2019-02-07 19:27:15 +00:00
|
|
|
"@type": "Article",
|
|
|
|
"@id": self.url,
|
|
|
|
"inLanguage": self.lang,
|
2019-02-03 16:40:34 +00:00
|
|
|
"headline": self.title,
|
|
|
|
"url": self.url,
|
2019-02-07 19:27:15 +00:00
|
|
|
"genre": self.category,
|
|
|
|
"mainEntityOfPage": "%s#article" % (self.url),
|
|
|
|
"dateModified": str(arrow.get(self.dt)),
|
|
|
|
"datePublished": str(self.published),
|
2019-06-25 22:48:04 +01:00
|
|
|
"copyrightYear": str(self.published.format("YYYY")),
|
2019-02-07 19:27:15 +00:00
|
|
|
"license": "https://spdx.org/licenses/%s.html" % (self.licence),
|
2019-02-08 23:32:52 +00:00
|
|
|
"image": settings.site.image,
|
2019-02-07 19:27:15 +00:00
|
|
|
"author": settings.author,
|
|
|
|
"sameAs": self.sameas,
|
2019-02-08 23:32:52 +00:00
|
|
|
"publisher": settings.site.publisher,
|
2019-02-07 19:27:15 +00:00
|
|
|
"name": self.name,
|
|
|
|
"text": self.html_content,
|
2019-02-03 16:40:34 +00:00
|
|
|
"description": self.html_summary,
|
2019-02-07 19:27:15 +00:00
|
|
|
"potentialAction": [],
|
|
|
|
"comment": [],
|
|
|
|
"commentCount": len(self.comments.keys()),
|
2019-06-25 22:48:04 +01:00
|
|
|
"keywords": self.tags,
|
2019-02-03 16:40:34 +00:00
|
|
|
}
|
2019-02-07 19:27:15 +00:00
|
|
|
|
|
|
|
if self.is_photo:
|
2019-06-25 22:48:04 +01:00
|
|
|
r.update(
|
|
|
|
{
|
|
|
|
"@type": "Photograph",
|
|
|
|
# "image": self.photo.jsonld,
|
|
|
|
}
|
|
|
|
)
|
2019-02-07 19:27:15 +00:00
|
|
|
elif self.has_code:
|
2019-06-25 22:48:04 +01:00
|
|
|
r.update({"@type": "TechArticle"})
|
2019-02-07 19:27:15 +00:00
|
|
|
elif self.is_page:
|
2019-06-25 22:48:04 +01:00
|
|
|
r.update({"@type": "WebPage"})
|
2019-02-25 22:40:01 +00:00
|
|
|
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):
|
2019-06-25 22:48:04 +01:00
|
|
|
# img = list(self.images.values())[0]
|
|
|
|
# r.update({
|
|
|
|
# "image": img.jsonld,
|
|
|
|
# })
|
2019-02-07 19:27:15 +00:00
|
|
|
|
|
|
|
if self.is_reply:
|
2019-06-25 22:48:04 +01:00
|
|
|
r.update(
|
|
|
|
{
|
|
|
|
"mentions": {
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "Thing",
|
|
|
|
"url": self.is_reply,
|
|
|
|
}
|
2019-02-07 19:27:15 +00:00
|
|
|
}
|
2019-06-25 22:48:04 +01:00
|
|
|
)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
if self.review:
|
|
|
|
r.update({"review": self.review})
|
|
|
|
|
|
|
|
if self.event:
|
|
|
|
r.update({"subjectOf": self.event})
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
# for donation in settings.donateActions:
|
|
|
|
# r["potentialAction"].append(donation)
|
2019-02-07 19:27:15 +00:00
|
|
|
|
2019-02-08 23:32:52 +00:00
|
|
|
for url in list(set(self.syndicate)):
|
2019-06-25 22:48:04 +01:00
|
|
|
r["potentialAction"].append(
|
|
|
|
{"@context": "http://schema.org", "@type": "InteractAction", "url": url}
|
|
|
|
)
|
2019-02-07 19:27:15 +00:00
|
|
|
|
|
|
|
for mtime in sorted(self.comments.keys()):
|
|
|
|
r["comment"].append(self.comments[mtime].jsonld)
|
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
return struct(r)
|
2018-09-04 21:58:25 +01:00
|
|
|
|
2017-06-02 11:19:55 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def template(self):
|
|
|
|
return "%s.j2.html" % (self.__class__.__name__)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
@property
|
|
|
|
def gophertemplate(self):
|
|
|
|
return "%s.j2.txt" % (self.__class__.__name__)
|
|
|
|
|
2018-11-04 23:27:53 +00:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def renderdir(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), self.name)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2018-04-30 20:44:04 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.renderdir, settings.filenames.html)
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-06-24 13:52:44 +01:00
|
|
|
@property
|
|
|
|
def mementofile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(os.path.dirname(self.fpath), settings.filenames.memento)
|
2019-06-24 13:52:44 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def has_memento(self):
|
|
|
|
if os.path.exists(self.mementofile):
|
|
|
|
if os.path.getsize(self.mementofile) > 0:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
@property
|
|
|
|
def gopherfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.renderdir, settings.filenames.txt)
|
2018-04-30 20:44:04 +01:00
|
|
|
|
2017-06-02 11:19:55 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def exists(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if settings.args.get("force"):
|
|
|
|
logger.debug("rendering required: force mode on")
|
2018-07-20 16:45:42 +01:00
|
|
|
return False
|
|
|
|
elif not os.path.exists(self.renderfile):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.debug("rendering required: no html yet")
|
2018-07-20 16:45:42 +01:00
|
|
|
return False
|
2019-02-16 00:14:12 +00:00
|
|
|
elif self.dt > mtime(self.renderfile):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.debug("rendering required: self.dt > html mtime")
|
2018-07-20 16:45:42 +01:00
|
|
|
return False
|
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.debug("rendering not required")
|
2018-07-20 16:45:42 +01:00
|
|
|
return True
|
2018-06-29 10:40:22 +01:00
|
|
|
|
2018-07-22 08:48:47 +01:00
|
|
|
@property
|
|
|
|
def corpus(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return "\n".join([self.title, self.name, self.summary, self.content])
|
2018-07-22 08:48:47 +01:00
|
|
|
|
2019-07-14 20:48:49 +01:00
|
|
|
async def copy_files(self):
|
|
|
|
exclude = [
|
|
|
|
".md",
|
|
|
|
".jpg",
|
|
|
|
".png",
|
|
|
|
".gif",
|
|
|
|
".ping",
|
|
|
|
".url",
|
|
|
|
".del",
|
|
|
|
".copy",
|
|
|
|
".cache",
|
|
|
|
]
|
2019-06-25 22:48:04 +01:00
|
|
|
files = glob.glob(os.path.join(os.path.dirname(self.fpath), "*.*"))
|
2018-10-01 10:33:07 +01:00
|
|
|
for f in files:
|
|
|
|
fname, fext = os.path.splitext(f)
|
|
|
|
if fext.lower() in exclude:
|
|
|
|
continue
|
|
|
|
|
|
|
|
t = os.path.join(
|
2019-06-25 22:48:04 +01:00
|
|
|
settings.paths.get("build"), self.name, os.path.basename(f)
|
2018-10-01 10:33:07 +01:00
|
|
|
)
|
2019-06-25 22:48:04 +01:00
|
|
|
if os.path.exists(t) and mtime(f) <= mtime(t):
|
2018-10-01 10:33:07 +01:00
|
|
|
continue
|
2018-11-04 23:27:53 +00:00
|
|
|
logger.info("copying '%s' to '%s'", f, t)
|
2018-10-01 10:33:07 +01:00
|
|
|
cp(f, t)
|
2018-07-25 13:24:31 +01:00
|
|
|
|
2019-06-25 22:43:45 +01:00
|
|
|
async def save_memento(self):
|
2019-06-24 13:52:44 +01:00
|
|
|
cp(self.renderfile, self.mementofile)
|
|
|
|
return
|
|
|
|
|
2019-06-25 22:43:45 +01:00
|
|
|
async def save_to_archiveorg(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
requests.get("http://web.archive.org/save/%s" % (self.url))
|
2019-06-24 13:52:44 +01:00
|
|
|
|
|
|
|
def try_memento(self, url):
|
|
|
|
try:
|
|
|
|
params = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"url": url,
|
|
|
|
"timestamp": "%s" % (self.published.format("YYYY-MM-DD")),
|
2019-06-24 13:52:44 +01:00
|
|
|
}
|
2019-06-25 22:48:04 +01:00
|
|
|
waybackmachine = "http://archive.org/wayback/available"
|
2019-06-24 13:52:44 +01:00
|
|
|
snapshots = requests.get(waybackmachine, params=params).json()
|
|
|
|
# no archived version...
|
2019-06-25 22:48:04 +01:00
|
|
|
if not len(snapshots.get("archived_snapshots", None)):
|
|
|
|
logger.warning("no snapshot found for %s", url)
|
2019-06-24 13:52:44 +01:00
|
|
|
return None
|
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("snapshot FOUND for %s", url)
|
2019-06-24 13:52:44 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
snapshot = snapshots.get("archived_snapshots").get("closest")
|
|
|
|
logger.info("getting %s", snapshot["url"])
|
|
|
|
original = requests.get(snapshot["url"])
|
2019-06-24 13:52:44 +01:00
|
|
|
return original.text
|
|
|
|
except Exception as e:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.warning("wayback memento failed for %s: %s", url, e)
|
2019-06-24 13:52:44 +01:00
|
|
|
return None
|
|
|
|
|
2019-06-25 22:43:45 +01:00
|
|
|
def maybe_fetch_memento(self):
|
2019-06-24 13:52:44 +01:00
|
|
|
if self.has_memento:
|
|
|
|
return
|
|
|
|
|
|
|
|
# this commented out part is extremely specific to my old site
|
|
|
|
# but it helps anyone who had multiple domains and/or taxonomy
|
|
|
|
# structures
|
|
|
|
# formerdomains = [
|
2019-06-25 22:48:04 +01:00
|
|
|
# 'cadeyrn.webporfolio.hu',
|
|
|
|
# 'blog.petermolnar.eu',
|
|
|
|
# 'petermolnar.eu',
|
|
|
|
# 'petermolnar.net',
|
2019-06-24 13:52:44 +01:00
|
|
|
# ]
|
|
|
|
|
|
|
|
# formercategories = [
|
2019-06-25 22:48:04 +01:00
|
|
|
# 'linux-tech-coding',
|
|
|
|
# 'diy-do-it-yourself',
|
|
|
|
# 'photoblog',
|
|
|
|
# 'it',
|
|
|
|
# 'sysadmin-blog',
|
|
|
|
# 'sysadmin',
|
|
|
|
# 'fotography',
|
|
|
|
# 'blips',
|
|
|
|
# 'blog',
|
|
|
|
# 'r'
|
2019-06-24 13:52:44 +01:00
|
|
|
# ]
|
|
|
|
|
|
|
|
# for domain in formerdomains:
|
2019-06-25 22:48:04 +01:00
|
|
|
# maybe = None
|
|
|
|
# url = url = 'http://%s/%s/' % (domain, self.name)
|
|
|
|
# maybe = self.try_memento(url)
|
|
|
|
# if maybe:
|
|
|
|
# break
|
|
|
|
# for formercategory in formercategories:
|
|
|
|
# url = 'http://%s/%s/%s/' % (domain, formercategory, self.name)
|
|
|
|
# maybe = self.try_memento(url)
|
|
|
|
# if maybe:
|
|
|
|
# break
|
|
|
|
# if maybe:
|
|
|
|
# break
|
2019-06-24 13:52:44 +01:00
|
|
|
|
|
|
|
maybe = self.try_memento(self.url)
|
|
|
|
if maybe:
|
2019-06-25 22:48:04 +01:00
|
|
|
with open(self.mementofile, "wt") as f:
|
|
|
|
logger.info("saving memento for %s to %s", self.name, self.mementofile)
|
2019-06-24 13:52:44 +01:00
|
|
|
f.write(maybe)
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
async def render(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if settings.args.get("memento"):
|
2019-06-25 22:43:45 +01:00
|
|
|
self.maybe_fetch_memento()
|
2019-06-24 13:52:44 +01:00
|
|
|
|
2019-07-14 20:48:49 +01:00
|
|
|
if self.exists and not self.has_memento:
|
|
|
|
if self.published.timestamp >= settings.mementostartime:
|
|
|
|
cp(self.renderfile, self.mementofile)
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
if self.exists:
|
|
|
|
return
|
2019-06-24 13:52:44 +01:00
|
|
|
|
|
|
|
memento = False
|
|
|
|
if self.has_memento:
|
|
|
|
memento = "%s%s" % (self.url, settings.filenames.memento)
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
logger.info("rendering %s", self.name)
|
|
|
|
v = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"baseurl": self.url,
|
|
|
|
"post": self.jsonld,
|
|
|
|
"site": settings.site,
|
|
|
|
"menu": settings.menu,
|
|
|
|
"meta": settings.meta,
|
|
|
|
"fnames": settings.filenames,
|
|
|
|
"memento": memento,
|
2019-02-25 22:40:01 +00:00
|
|
|
}
|
2019-06-25 22:48:04 +01:00
|
|
|
writepath(self.renderfile, J2.get_template(self.template).render(v))
|
|
|
|
del v
|
2019-02-25 22:40:01 +00:00
|
|
|
|
|
|
|
g = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"post": self.jsonld,
|
|
|
|
"summary": self.txt_summary,
|
|
|
|
"content": self.txt_content,
|
2019-02-25 22:40:01 +00:00
|
|
|
}
|
2019-06-25 22:48:04 +01:00
|
|
|
writepath(self.gopherfile, J2.get_template(self.gophertemplate).render(g))
|
|
|
|
del g
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
j = settings.site.copy()
|
2019-06-25 22:48:04 +01:00
|
|
|
j.update({"mainEntity": self.jsonld})
|
2019-02-07 19:27:15 +00:00
|
|
|
writepath(
|
2019-05-29 20:34:47 +01:00
|
|
|
os.path.join(self.renderdir, settings.filenames.json),
|
2019-06-25 22:48:04 +01:00
|
|
|
json.dumps(j, indent=4, ensure_ascii=False),
|
2019-02-07 19:27:15 +00:00
|
|
|
)
|
2019-06-25 22:48:04 +01:00
|
|
|
del j
|
2019-06-24 13:52:44 +01:00
|
|
|
|
2019-05-28 13:27:34 +01:00
|
|
|
# oembed
|
2019-06-13 15:32:13 +01:00
|
|
|
# writepath(
|
2019-06-25 22:48:04 +01:00
|
|
|
# os.path.join(self.renderdir, settings.filenames.oembed_json),
|
|
|
|
# json.dumps(self.oembed_json, indent=4, ensure_ascii=False)
|
2019-06-13 15:32:13 +01:00
|
|
|
# )
|
|
|
|
# writepath(
|
2019-06-25 22:48:04 +01:00
|
|
|
# os.path.join(self.renderdir, settings.filenames.oembed_xml),
|
|
|
|
# self.oembed_xml
|
2019-06-13 15:32:13 +01:00
|
|
|
# )
|
2019-05-28 13:27:34 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
class Home(Singular):
|
|
|
|
def __init__(self, fpath):
|
|
|
|
super().__init__(fpath)
|
2019-02-07 19:27:15 +00:00
|
|
|
self.posts = []
|
2019-01-15 21:28:58 +00:00
|
|
|
|
|
|
|
def add(self, category, post):
|
2019-02-07 19:27:15 +00:00
|
|
|
self.posts.append((category.ctmplvars, post.jsonld))
|
2019-01-15 21:28:58 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def renderdir(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return settings.paths.get("build")
|
2019-01-15 21:28:58 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), settings.filenames.html)
|
2019-01-15 21:28:58 +00:00
|
|
|
|
2019-01-21 16:10:27 +00:00
|
|
|
@property
|
|
|
|
def dt(self):
|
|
|
|
maybe = super().dt
|
2019-02-07 19:27:15 +00:00
|
|
|
for cat, post in self.posts:
|
2019-06-25 22:48:04 +01:00
|
|
|
pts = arrow.get(post["dateModified"]).timestamp
|
2019-02-07 19:27:15 +00:00
|
|
|
if pts > maybe:
|
|
|
|
maybe = pts
|
2019-01-21 16:10:27 +00:00
|
|
|
return maybe
|
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
async def render_gopher(self):
|
|
|
|
lines = [
|
|
|
|
"%s's gopherhole - phlog, if you prefer" % (settings.site.name),
|
2019-06-25 22:48:04 +01:00
|
|
|
"",
|
|
|
|
"",
|
2019-02-25 22:40:01 +00:00
|
|
|
]
|
|
|
|
|
|
|
|
for category, post in self.posts:
|
|
|
|
line = "1%s\t/%s/%s\t%s\t70" % (
|
2019-06-25 22:48:04 +01:00
|
|
|
category["name"],
|
2019-05-23 09:06:34 +01:00
|
|
|
settings.paths.category,
|
2019-06-25 22:48:04 +01:00
|
|
|
category["name"],
|
|
|
|
settings.site.name,
|
2019-02-25 22:40:01 +00:00
|
|
|
)
|
|
|
|
lines.append(line)
|
2019-06-25 22:48:04 +01:00
|
|
|
lines.append("")
|
2019-05-23 09:06:34 +01:00
|
|
|
writepath(
|
2019-06-25 22:48:04 +01:00
|
|
|
self.renderfile.replace(settings.filenames.html, settings.filenames.gopher),
|
|
|
|
"\r\n".join(lines),
|
2019-05-23 09:06:34 +01:00
|
|
|
)
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
async def render(self):
|
|
|
|
if self.exists:
|
|
|
|
return
|
|
|
|
logger.info("rendering %s", self.name)
|
2019-06-25 22:48:04 +01:00
|
|
|
r = J2.get_template(self.template).render(
|
|
|
|
{
|
|
|
|
"baseurl": settings.site.get("url"),
|
|
|
|
"post": self.jsonld,
|
|
|
|
"site": settings.site,
|
|
|
|
"menu": settings.menu,
|
|
|
|
"meta": settings.meta,
|
|
|
|
"posts": self.posts,
|
|
|
|
"fnames": settings.filenames,
|
|
|
|
}
|
|
|
|
)
|
2019-01-15 21:28:58 +00:00
|
|
|
writepath(self.renderfile, r)
|
2019-02-25 22:40:01 +00:00
|
|
|
await self.render_gopher()
|
2019-01-15 21:28:58 +00:00
|
|
|
|
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
class WebImage(object):
|
|
|
|
def __init__(self, fpath, mdimg, parent):
|
2018-11-04 23:27:53 +00:00
|
|
|
logger.debug("loading image: %s", fpath)
|
2018-07-20 16:45:42 +01:00
|
|
|
self.mdimg = mdimg
|
|
|
|
self.fpath = fpath
|
|
|
|
self.parent = parent
|
2019-02-16 00:14:12 +00:00
|
|
|
self.mtime = mtime(self.fpath)
|
2018-07-20 16:45:42 +01:00
|
|
|
self.fname, self.fext = os.path.splitext(os.path.basename(fpath))
|
|
|
|
self.resized_images = [
|
|
|
|
(k, self.Resized(self, k))
|
2019-06-25 22:48:04 +01:00
|
|
|
for k in settings.photo.get("sizes").keys()
|
2018-07-20 16:45:42 +01:00
|
|
|
if k < max(self.width, self.height)
|
|
|
|
]
|
|
|
|
if not len(self.resized_images):
|
2019-06-25 22:48:04 +01:00
|
|
|
self.resized_images.append(
|
|
|
|
(
|
|
|
|
max(self.width, self.height),
|
|
|
|
self.Resized(self, max(self.width, self.height)),
|
|
|
|
)
|
|
|
|
)
|
2017-11-03 22:54:36 +00:00
|
|
|
|
2018-09-04 21:58:25 +01:00
|
|
|
@property
|
|
|
|
def is_mainimg(self):
|
|
|
|
if self.fname == self.parent.name:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
@property
|
2019-02-07 19:27:15 +00:00
|
|
|
def jsonld(self):
|
|
|
|
r = {
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "ImageObject",
|
|
|
|
"url": self.href,
|
|
|
|
"image": self.href,
|
2019-06-25 22:48:04 +01:00
|
|
|
"thumbnail": struct(
|
|
|
|
{
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "ImageObject",
|
|
|
|
"url": self.src,
|
|
|
|
"width": self.displayed.width,
|
|
|
|
"height": self.displayed.height,
|
|
|
|
}
|
|
|
|
),
|
2019-02-07 19:27:15 +00:00
|
|
|
"name": os.path.basename(self.fpath),
|
|
|
|
"encodingFormat": self.mime_type,
|
|
|
|
"contentSize": self.mime_size,
|
|
|
|
"width": self.linked.width,
|
|
|
|
"height": self.linked.height,
|
2019-06-25 22:48:04 +01:00
|
|
|
"dateCreated": self.exif.get("CreateDate"),
|
2019-02-07 19:27:15 +00:00
|
|
|
"exifData": [],
|
|
|
|
"caption": self.caption,
|
|
|
|
"headline": self.title,
|
2019-06-25 22:48:04 +01:00
|
|
|
"representativeOfPage": False,
|
2018-09-04 21:58:25 +01:00
|
|
|
}
|
2019-02-07 19:27:15 +00:00
|
|
|
for k, v in self.exif.items():
|
2019-06-25 22:48:04 +01:00
|
|
|
r["exifData"].append({"@type": "PropertyValue", "name": k, "value": v})
|
2019-02-07 19:27:15 +00:00
|
|
|
if self.is_photo:
|
2019-06-25 22:48:04 +01:00
|
|
|
r.update(
|
|
|
|
{
|
|
|
|
"creator": settings.author,
|
|
|
|
"copyrightHolder": settings.author,
|
|
|
|
"license": settings.licence["_default"],
|
|
|
|
}
|
|
|
|
)
|
2019-02-07 19:27:15 +00:00
|
|
|
if self.is_mainimg:
|
|
|
|
r.update({"representativeOfPage": True})
|
2019-05-05 13:04:36 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if self.exif["GPSLatitude"] != 0 and self.exif["GPSLongitude"] != 0:
|
|
|
|
r.update(
|
|
|
|
{
|
|
|
|
"locationCreated": struct(
|
|
|
|
{
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "Place",
|
|
|
|
"geo": struct(
|
|
|
|
{
|
|
|
|
"@context": "http://schema.org",
|
|
|
|
"@type": "GeoCoordinates",
|
|
|
|
"latitude": self.exif["GPSLatitude"],
|
|
|
|
"longitude": self.exif["GPSLongitude"],
|
|
|
|
}
|
|
|
|
),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2019-02-16 00:14:12 +00:00
|
|
|
return struct(r)
|
2018-09-04 21:58:25 +01:00
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
if len(self.mdimg.css):
|
|
|
|
return self.mdimg.match
|
|
|
|
tmpl = J2.get_template("%s.j2.html" % (self.__class__.__name__))
|
2019-02-07 19:27:15 +00:00
|
|
|
return tmpl.render(self.jsonld)
|
2018-12-11 14:06:18 +00:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
@cached_property
|
2018-07-20 16:45:42 +01:00
|
|
|
def meta(self):
|
2018-08-08 09:42:42 +01:00
|
|
|
return Exif(self.fpath)
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-06-08 10:14:39 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def caption(self):
|
|
|
|
if len(self.mdimg.alt):
|
|
|
|
return self.mdimg.alt
|
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("Description", "")
|
2018-06-08 10:14:39 +01:00
|
|
|
|
2017-06-28 12:20:26 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def title(self):
|
|
|
|
if len(self.mdimg.title):
|
|
|
|
return self.mdimg.title
|
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
return self.meta.get("Headline", self.fname)
|
2017-06-02 11:19:55 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def tags(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return list(set(self.meta.get("Subject", [])))
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def published(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return arrow.get(self.meta.get("ReleaseDate", self.meta.get("ModifyDate")))
|
2017-06-02 11:19:55 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def width(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return int(self.meta.get("ImageWidth"))
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def height(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return int(self.meta.get("ImageHeight"))
|
2017-06-02 11:19:55 +01:00
|
|
|
|
2018-04-30 20:44:04 +01:00
|
|
|
@property
|
|
|
|
def mime_type(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return str(self.meta.get("MIMEType", "image/jpeg"))
|
2018-04-30 20:44:04 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def mime_size(self):
|
2019-02-16 00:14:12 +00:00
|
|
|
try:
|
|
|
|
size = os.path.getsize(self.linked.fpath)
|
|
|
|
except Exception as e:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("Failed to get mime size of %s", self.linked.fpath)
|
|
|
|
size = self.meta.get("FileSize", 0)
|
2019-02-16 00:14:12 +00:00
|
|
|
return size
|
2018-04-30 20:44:04 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def displayed(self):
|
|
|
|
ret = self.resized_images[0][1]
|
|
|
|
for size, r in self.resized_images:
|
2019-06-25 22:48:04 +01:00
|
|
|
if size == settings.photo.get("default"):
|
2018-07-20 16:45:42 +01:00
|
|
|
ret = r
|
|
|
|
return ret
|
2017-06-02 11:19:55 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def linked(self):
|
|
|
|
m = 0
|
|
|
|
ret = self.resized_images[0][1]
|
|
|
|
for size, r in self.resized_images:
|
|
|
|
if size > m:
|
|
|
|
m = size
|
|
|
|
ret = r
|
|
|
|
return ret
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
|
|
|
def src(self):
|
2018-07-20 16:45:42 +01:00
|
|
|
return self.displayed.url
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def href(self):
|
|
|
|
return self.linked.url
|
2017-07-26 11:23:06 +01:00
|
|
|
|
|
|
|
@property
|
2017-10-27 10:29:33 +01:00
|
|
|
def is_photo(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
r = settings.photo.get("re_author", None)
|
2018-07-20 16:45:42 +01:00
|
|
|
if not r:
|
2017-10-27 10:29:33 +01:00
|
|
|
return False
|
2019-06-25 22:48:04 +01:00
|
|
|
cpr = self.meta.get("Copyright", "")
|
|
|
|
art = self.meta.get("Artist", "")
|
2017-10-27 10:29:33 +01:00
|
|
|
# both Artist and Copyright missing from EXIF
|
|
|
|
if not cpr and not art:
|
|
|
|
return False
|
|
|
|
# we have regex, Artist and Copyright, try matching them
|
2018-07-20 16:45:42 +01:00
|
|
|
if r.search(cpr) or r.search(art):
|
2017-10-27 10:29:33 +01:00
|
|
|
return True
|
|
|
|
return False
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-06-12 15:17:29 +01:00
|
|
|
@property
|
|
|
|
def exif(self):
|
2018-07-20 16:45:42 +01:00
|
|
|
exif = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"Model": "",
|
|
|
|
"FNumber": "",
|
|
|
|
"ExposureTime": "",
|
|
|
|
"FocalLength": "",
|
|
|
|
"ISO": "",
|
|
|
|
"LensID": "",
|
|
|
|
"CreateDate": str(arrow.get(self.mtime)),
|
|
|
|
"GPSLatitude": 0,
|
|
|
|
"GPSLongitude": 0,
|
2018-07-20 16:45:42 +01:00
|
|
|
}
|
2017-06-12 15:17:29 +01:00
|
|
|
if not self.is_photo:
|
2017-10-27 10:29:33 +01:00
|
|
|
return exif
|
2017-06-12 15:17:29 +01:00
|
|
|
|
|
|
|
mapping = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"Model": ["Model"],
|
|
|
|
"FNumber": ["FNumber", "Aperture"],
|
|
|
|
"ExposureTime": ["ExposureTime"],
|
|
|
|
"FocalLength": ["FocalLength"], # ['FocalLengthIn35mmFormat'],
|
|
|
|
"ISO": ["ISO"],
|
|
|
|
"LensID": ["LensID", "LensSpec", "Lens"],
|
|
|
|
"CreateDate": ["CreateDate", "DateTimeOriginal"],
|
|
|
|
"GPSLatitude": ["GPSLatitude"],
|
|
|
|
"GPSLongitude": ["GPSLongitude"],
|
2017-06-12 15:17:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for ekey, candidates in mapping.items():
|
|
|
|
for candidate in candidates:
|
|
|
|
maybe = self.meta.get(candidate, None)
|
2017-10-27 10:29:33 +01:00
|
|
|
if not maybe:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
exif[ekey] = maybe
|
|
|
|
break
|
2019-04-10 09:27:37 +01:00
|
|
|
return struct(exif)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
def _maybe_watermark(self, img):
|
|
|
|
if not self.is_photo:
|
|
|
|
return img
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
wmarkfile = settings.paths.get("watermark")
|
2018-07-20 16:45:42 +01:00
|
|
|
if not os.path.exists(wmarkfile):
|
2017-05-23 11:14:47 +01:00
|
|
|
return img
|
|
|
|
|
|
|
|
with wand.image.Image(filename=wmarkfile) as wmark:
|
2019-04-10 09:27:37 +01:00
|
|
|
w = self.height * 0.2
|
|
|
|
h = wmark.height * (w / wmark.width)
|
2018-07-20 16:45:42 +01:00
|
|
|
if self.width > self.height:
|
|
|
|
x = self.width - w - (self.width * 0.01)
|
|
|
|
y = self.height - h - (self.height * 0.01)
|
2017-05-23 11:14:47 +01:00
|
|
|
else:
|
2018-07-20 16:45:42 +01:00
|
|
|
x = self.width - h - (self.width * 0.01)
|
|
|
|
y = self.height - w - (self.height * 0.01)
|
2017-05-23 11:14:47 +01:00
|
|
|
|
|
|
|
w = round(w)
|
|
|
|
h = round(h)
|
|
|
|
x = round(x)
|
|
|
|
y = round(y)
|
|
|
|
|
|
|
|
wmark.resize(w, h)
|
2018-07-20 16:45:42 +01:00
|
|
|
if self.width <= self.height:
|
2017-05-23 11:14:47 +01:00
|
|
|
wmark.rotate(-90)
|
|
|
|
img.composite(image=wmark, left=x, top=y)
|
|
|
|
return img
|
|
|
|
|
2017-10-27 10:29:33 +01:00
|
|
|
async def downsize(self):
|
2018-07-20 16:45:42 +01:00
|
|
|
need = False
|
|
|
|
for size, resized in self.resized_images:
|
2019-06-25 22:48:04 +01:00
|
|
|
if not resized.exists or settings.args.get("regenerate"):
|
2018-07-20 16:45:42 +01:00
|
|
|
need = True
|
|
|
|
break
|
|
|
|
if not need:
|
2017-10-27 10:29:33 +01:00
|
|
|
return
|
2017-05-23 11:14:47 +01:00
|
|
|
|
|
|
|
with wand.image.Image(filename=self.fpath) as img:
|
|
|
|
img.auto_orient()
|
2017-10-27 10:29:33 +01:00
|
|
|
img = self._maybe_watermark(img)
|
2018-07-20 16:45:42 +01:00
|
|
|
for size, resized in self.resized_images:
|
2019-06-25 22:48:04 +01:00
|
|
|
if not resized.exists or settings.args.get("regenerate"):
|
2018-11-04 23:27:53 +00:00
|
|
|
logger.info(
|
2018-07-20 16:45:42 +01:00
|
|
|
"resizing image: %s to size %d",
|
|
|
|
os.path.basename(self.fpath),
|
2019-06-25 22:48:04 +01:00
|
|
|
size,
|
2018-07-20 16:45:42 +01:00
|
|
|
)
|
|
|
|
await resized.make(img)
|
|
|
|
|
|
|
|
class Resized:
|
|
|
|
def __init__(self, parent, size, crop=False):
|
|
|
|
self.parent = parent
|
|
|
|
self.size = size
|
|
|
|
self.crop = crop
|
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
@property
|
|
|
|
def data(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
with open(self.fpath, "rb") as f:
|
2019-02-16 00:14:12 +00:00
|
|
|
encoded = base64.b64encode(f.read())
|
2019-02-25 22:40:01 +00:00
|
|
|
return "data:%s;base64,%s" % (
|
2019-06-25 22:48:04 +01:00
|
|
|
self.parent.mime_type,
|
|
|
|
encoded.decode("utf-8"),
|
|
|
|
)
|
2019-02-16 00:14:12 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def suffix(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return settings.photo.get("sizes").get(self.size, "")
|
2018-07-20 16:45:42 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def fname(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return "%s%s%s" % (self.parent.fname, self.suffix, self.parent.fext)
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def fpath(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.parent.parent.renderdir, self.fname)
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def url(self):
|
|
|
|
return "%s/%s/%s" % (
|
2019-06-25 22:48:04 +01:00
|
|
|
settings.site.get("url"),
|
2018-07-20 16:45:42 +01:00
|
|
|
self.parent.parent.name,
|
2019-06-25 22:48:04 +01:00
|
|
|
"%s%s%s" % (self.parent.fname, self.suffix, self.parent.fext),
|
2018-07-20 16:45:42 +01:00
|
|
|
)
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def relpath(self):
|
|
|
|
return "%s/%s" % (
|
2019-06-25 22:48:04 +01:00
|
|
|
self.parent.parent.renderdir.replace(settings.paths.get("build"), ""),
|
|
|
|
self.fname,
|
2018-07-20 16:45:42 +01:00
|
|
|
)
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def exists(self):
|
|
|
|
if os.path.isfile(self.fpath):
|
2019-02-16 00:14:12 +00:00
|
|
|
if mtime(self.fpath) >= self.parent.mtime:
|
2018-07-20 16:45:42 +01:00
|
|
|
return True
|
|
|
|
return False
|
2017-05-31 13:53:47 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def width(self):
|
|
|
|
return self.dimensions[0]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def height(self):
|
|
|
|
return self.dimensions[1]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def dimensions(self):
|
|
|
|
width = self.parent.width
|
|
|
|
height = self.parent.height
|
|
|
|
size = self.size
|
|
|
|
|
|
|
|
ratio = max(width, height) / min(width, height)
|
|
|
|
horizontal = True if (width / height) >= 1 else False
|
|
|
|
|
|
|
|
# panorama: reverse "horizontal" because the limit should be on
|
|
|
|
# the shorter side, not the longer, and make it a bit smaller, than
|
|
|
|
# the actual limit
|
|
|
|
# 2.39 is the wide angle cinematic view: anything wider, than that
|
|
|
|
# is panorama land
|
|
|
|
if ratio > 2.4 and not self.crop:
|
|
|
|
size = int(size * 0.6)
|
|
|
|
horizontal = not horizontal
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if (horizontal and not self.crop) or (not horizontal and self.crop):
|
2018-07-20 16:45:42 +01:00
|
|
|
w = size
|
|
|
|
h = int(float(size / width) * height)
|
|
|
|
else:
|
|
|
|
h = size
|
|
|
|
w = int(float(size / height) * width)
|
|
|
|
return (w, h)
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
async def make(self, original):
|
|
|
|
if not os.path.isdir(os.path.dirname(self.fpath)):
|
|
|
|
os.makedirs(os.path.dirname(self.fpath))
|
2017-06-12 15:40:30 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
with original.clone() as thumb:
|
|
|
|
thumb.resize(self.width, self.height)
|
2017-05-31 13:53:47 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
if self.crop:
|
|
|
|
thumb.liquid_rescale(self.size, self.size, 1, 1)
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if self.parent.meta.get("FileType", "jpeg").lower() == "jpeg":
|
2018-07-25 13:24:31 +01:00
|
|
|
thumb.compression_quality = 88
|
2019-06-25 22:48:04 +01:00
|
|
|
thumb.unsharp_mask(radius=1, sigma=0.5, amount=0.7, threshold=0.5)
|
|
|
|
thumb.format = "pjpeg"
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
# this is to make sure pjpeg happens
|
2019-06-25 22:48:04 +01:00
|
|
|
with open(self.fpath, "wb") as f:
|
2018-11-04 23:27:53 +00:00
|
|
|
logger.info("writing %s", self.fpath)
|
2018-07-20 16:45:42 +01:00
|
|
|
thumb.save(file=f)
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2019-03-24 15:26:55 +00:00
|
|
|
# n, e = os.path.splitext(os.path.basename(self.fpath))
|
|
|
|
# webppath = self.fpath.replace(e, '.webp')
|
|
|
|
# with open(webppath, 'wb') as f:
|
2019-06-25 22:48:04 +01:00
|
|
|
# logger.info("writing %s", webppath)
|
|
|
|
# thumb.format = 'webp'
|
|
|
|
# thumb.compression_quality = 88
|
|
|
|
# thumb.save(file=f)
|
2019-03-24 15:26:55 +00:00
|
|
|
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
class PHPFile(object):
|
|
|
|
@property
|
|
|
|
def exists(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if settings.args.get("force"):
|
2018-08-15 11:02:59 +01:00
|
|
|
return False
|
|
|
|
if not os.path.exists(self.renderfile):
|
|
|
|
return False
|
2019-02-16 00:14:12 +00:00
|
|
|
if self.mtime > mtime(self.renderfile):
|
2018-08-15 11:02:59 +01:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
@property
|
|
|
|
def mtime(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return mtime(os.path.join(settings.paths.get("tmpl"), self.templatefile))
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
raise ValueError("Not implemented")
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
@property
|
|
|
|
def templatefile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
raise ValueError("Not implemented")
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
async def render(self):
|
2019-02-25 22:40:01 +00:00
|
|
|
# if self.exists:
|
2019-06-25 22:48:04 +01:00
|
|
|
# return
|
2018-08-15 11:02:59 +01:00
|
|
|
await self._render()
|
|
|
|
|
|
|
|
|
|
|
|
class Search(PHPFile):
|
2018-07-22 14:52:32 +01:00
|
|
|
def __init__(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
self.fpath = os.path.join(settings.paths.get("build"), "search.sqlite")
|
2018-08-15 11:02:59 +01:00
|
|
|
self.db = sqlite3.connect(self.fpath)
|
2019-06-25 22:48:04 +01:00
|
|
|
self.db.execute("PRAGMA auto_vacuum = INCREMENTAL;")
|
|
|
|
self.db.execute("PRAGMA journal_mode = MEMORY;")
|
|
|
|
self.db.execute("PRAGMA temp_store = MEMORY;")
|
|
|
|
self.db.execute("PRAGMA locking_mode = NORMAL;")
|
|
|
|
self.db.execute("PRAGMA synchronous = FULL;")
|
2018-08-15 11:02:59 +01:00
|
|
|
self.db.execute('PRAGMA encoding = "UTF-8";')
|
2019-06-25 22:48:04 +01:00
|
|
|
self.db.execute(
|
|
|
|
"""
|
2018-08-15 11:02:59 +01:00
|
|
|
CREATE VIRTUAL TABLE IF NOT EXISTS data USING fts4(
|
|
|
|
url,
|
|
|
|
mtime,
|
|
|
|
name,
|
|
|
|
title,
|
|
|
|
category,
|
|
|
|
content,
|
|
|
|
notindexed=category,
|
|
|
|
notindexed=url,
|
|
|
|
notindexed=mtime,
|
|
|
|
tokenize=porter
|
2019-06-25 22:48:04 +01:00
|
|
|
)"""
|
|
|
|
)
|
2018-11-10 20:49:13 +00:00
|
|
|
self.is_changed = False
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
def __exit__(self):
|
2018-11-10 20:49:13 +00:00
|
|
|
if self.is_changed:
|
|
|
|
self.db.commit()
|
2019-06-25 22:48:04 +01:00
|
|
|
self.db.execute("PRAGMA auto_vacuum;")
|
2018-08-15 11:02:59 +01:00
|
|
|
self.db.close()
|
|
|
|
|
|
|
|
def check(self, name):
|
|
|
|
ret = 0
|
2019-06-25 22:48:04 +01:00
|
|
|
maybe = self.db.execute(
|
|
|
|
"""
|
2018-08-15 11:02:59 +01:00
|
|
|
SELECT
|
|
|
|
mtime
|
|
|
|
FROM
|
|
|
|
data
|
|
|
|
WHERE
|
|
|
|
name = ?
|
2019-06-25 22:48:04 +01:00
|
|
|
""",
|
|
|
|
(name,),
|
|
|
|
).fetchone()
|
2018-08-15 11:02:59 +01:00
|
|
|
if maybe:
|
|
|
|
ret = int(maybe[0])
|
|
|
|
return ret
|
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
def append(self, post):
|
2019-05-22 10:15:21 +01:00
|
|
|
mtime = int(post.published.timestamp)
|
2018-12-27 19:48:06 +00:00
|
|
|
check = self.check(post.name)
|
2019-06-25 22:48:04 +01:00
|
|
|
if check and check < mtime:
|
|
|
|
self.db.execute(
|
|
|
|
"""
|
2018-08-15 11:02:59 +01:00
|
|
|
DELETE
|
|
|
|
FROM
|
|
|
|
data
|
|
|
|
WHERE
|
2019-06-25 22:48:04 +01:00
|
|
|
name=?""",
|
|
|
|
(post.name,),
|
|
|
|
)
|
2018-08-15 11:02:59 +01:00
|
|
|
check = False
|
|
|
|
if not check:
|
2019-06-25 22:48:04 +01:00
|
|
|
self.db.execute(
|
|
|
|
"""
|
2018-08-15 11:02:59 +01:00
|
|
|
INSERT INTO
|
|
|
|
data
|
|
|
|
(url, mtime, name, title, category, content)
|
|
|
|
VALUES
|
|
|
|
(?,?,?,?,?,?);
|
2019-06-25 22:48:04 +01:00
|
|
|
""",
|
|
|
|
(post.url, mtime, post.name, post.title, post.category, post.content),
|
|
|
|
)
|
2018-11-10 20:49:13 +00:00
|
|
|
self.is_changed = True
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-07-23 11:04:48 +01:00
|
|
|
@property
|
2019-05-22 10:15:21 +01:00
|
|
|
def templates(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return ["Search.j2.php", "OpenSearch.j2.xml"]
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
async def _render(self):
|
2019-05-22 10:15:21 +01:00
|
|
|
for template in self.templates:
|
2019-06-25 22:48:04 +01:00
|
|
|
r = J2.get_template(template).render(
|
|
|
|
{
|
|
|
|
"post": {},
|
|
|
|
"site": settings.site,
|
|
|
|
"menu": settings.menu,
|
|
|
|
"meta": settings.meta,
|
|
|
|
}
|
|
|
|
)
|
2019-05-22 10:15:21 +01:00
|
|
|
target = os.path.join(
|
2019-06-25 22:48:04 +01:00
|
|
|
settings.paths.get("build"), template.replace(".j2", "").lower()
|
2019-05-22 10:15:21 +01:00
|
|
|
)
|
|
|
|
writepath(target, r)
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
|
|
|
|
class IndexPHP(PHPFile):
|
|
|
|
def __init__(self):
|
|
|
|
self.gone = {}
|
|
|
|
self.redirect = {}
|
2018-07-23 11:04:48 +01:00
|
|
|
|
2018-07-22 14:52:32 +01:00
|
|
|
def add_gone(self, uri):
|
|
|
|
self.gone[uri] = True
|
|
|
|
|
|
|
|
def add_redirect(self, source, target):
|
|
|
|
if target in self.gone:
|
|
|
|
self.add_gone(source)
|
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
if "://" not in target:
|
|
|
|
target = "%s/%s" % (settings.site.get("url"), target)
|
2018-07-22 14:52:32 +01:00
|
|
|
self.redirect[source] = target
|
|
|
|
|
2018-07-23 11:04:48 +01:00
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), "index.php")
|
2018-07-23 11:04:48 +01:00
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
@property
|
|
|
|
def templatefile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return "404.j2.php"
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
async def _render(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
r = J2.get_template(self.templatefile).render(
|
|
|
|
{
|
|
|
|
"post": {},
|
|
|
|
"site": settings.site,
|
|
|
|
"menu": settings.menu,
|
|
|
|
"gones": self.gone,
|
|
|
|
"redirects": self.redirect,
|
|
|
|
}
|
|
|
|
)
|
2018-11-10 20:49:13 +00:00
|
|
|
writepath(self.renderfile, r)
|
2017-10-27 15:56:05 +01:00
|
|
|
|
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
class WebhookPHP(PHPFile):
|
2018-08-08 09:42:42 +01:00
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), "webhook.php")
|
2018-08-08 09:42:42 +01:00
|
|
|
|
2018-08-15 11:02:59 +01:00
|
|
|
@property
|
|
|
|
def templatefile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return "Webhook.j2.php"
|
2018-08-15 11:02:59 +01:00
|
|
|
|
|
|
|
async def _render(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
r = J2.get_template(self.templatefile).render(
|
|
|
|
{"author": settings.author, "webmentionio": keys.webmentionio}
|
|
|
|
)
|
2018-11-10 20:49:13 +00:00
|
|
|
writepath(self.renderfile, r)
|
|
|
|
|
|
|
|
|
|
|
|
class MicropubPHP(PHPFile):
|
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), "micropub.php")
|
2018-11-10 20:49:13 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def templatefile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return "Micropub.j2.php"
|
2018-11-10 20:49:13 +00:00
|
|
|
|
|
|
|
async def _render(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
r = J2.get_template(self.templatefile).render(
|
|
|
|
{"site": settings.site, "menu": settings.menu, "paths": settings.paths}
|
|
|
|
)
|
2018-11-10 20:49:13 +00:00
|
|
|
writepath(self.renderfile, r)
|
2018-08-08 09:42:42 +01:00
|
|
|
|
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
class Category(dict):
|
2019-06-25 22:48:04 +01:00
|
|
|
def __init__(self, name=""):
|
2018-07-20 16:45:42 +01:00
|
|
|
self.name = name
|
2019-06-25 22:48:04 +01:00
|
|
|
self.trange = "YYYY"
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
def __setitem__(self, key, value):
|
|
|
|
if key in self:
|
|
|
|
raise LookupError(
|
2019-06-25 22:48:04 +01:00
|
|
|
"key '%s' already exists, colliding posts are: %s vs %s"
|
|
|
|
% (key, self[key].fpath, value.fpath)
|
2018-03-29 17:07:53 +01:00
|
|
|
)
|
2018-07-20 16:45:42 +01:00
|
|
|
dict.__setitem__(self, key, value)
|
2017-10-30 10:47:08 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def sortedkeys(self):
|
|
|
|
return list(sorted(self.keys(), reverse=True))
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2019-04-10 09:27:37 +01:00
|
|
|
@property
|
|
|
|
def is_photos(self):
|
|
|
|
r = True
|
|
|
|
for i in self.values():
|
|
|
|
r = r & i.is_photo
|
|
|
|
return r
|
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
2019-02-07 19:27:15 +00:00
|
|
|
def is_paginated(self):
|
|
|
|
if self.name in settings.flat:
|
|
|
|
return False
|
|
|
|
return True
|
2018-06-01 10:49:14 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
@property
|
|
|
|
def title(self):
|
|
|
|
if len(self.name):
|
2019-05-23 09:06:34 +01:00
|
|
|
return "%s - %s" % (self.name, settings.site.name)
|
2018-07-20 16:45:42 +01:00
|
|
|
else:
|
2019-05-23 09:06:34 +01:00
|
|
|
return settings.site.headline
|
2018-06-01 10:49:14 +01:00
|
|
|
|
2017-10-29 19:11:01 +00:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def url(self):
|
|
|
|
if len(self.name):
|
2019-05-23 09:06:34 +01:00
|
|
|
url = "%s/%s/%s/" % (settings.site.url, settings.paths.category, self.name)
|
2018-07-20 16:45:42 +01:00
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
url = "%s/" % (settings.site.url)
|
2018-07-20 16:45:42 +01:00
|
|
|
return url
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2018-07-25 13:24:31 +01:00
|
|
|
@property
|
2018-11-04 23:27:53 +00:00
|
|
|
def feedurl(self):
|
2018-07-25 13:24:31 +01:00
|
|
|
return "%sfeed/" % (self.url)
|
|
|
|
|
2017-10-29 19:11:01 +00:00
|
|
|
@property
|
2018-07-20 16:45:42 +01:00
|
|
|
def template(self):
|
|
|
|
return "%s.j2.html" % (self.__class__.__name__)
|
2017-10-27 15:56:05 +01:00
|
|
|
|
2017-10-29 19:11:01 +00:00
|
|
|
@property
|
2018-11-04 23:27:53 +00:00
|
|
|
def dpath(self):
|
2018-07-20 16:45:42 +01:00
|
|
|
if len(self.name):
|
|
|
|
return os.path.join(
|
2019-06-25 22:48:04 +01:00
|
|
|
settings.paths.build, settings.paths.category, self.name
|
2018-07-20 16:45:42 +01:00
|
|
|
)
|
2017-10-30 09:24:46 +00:00
|
|
|
else:
|
2019-05-23 09:06:34 +01:00
|
|
|
return settings.paths.build
|
2017-10-29 19:11:01 +00:00
|
|
|
|
2019-01-31 21:23:16 +00:00
|
|
|
@property
|
|
|
|
def newest_year(self):
|
|
|
|
return int(self[self.sortedkeys[0]].published.format(self.trange))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def years(self):
|
|
|
|
years = {}
|
|
|
|
for k in self.sortedkeys:
|
|
|
|
y = int(self[k].published.format(self.trange))
|
|
|
|
if y not in years:
|
|
|
|
if y == self.newest_year:
|
|
|
|
url = self.url
|
|
|
|
else:
|
|
|
|
url = "%s%d/" % (self.url, y)
|
2019-06-25 22:48:04 +01:00
|
|
|
years.update({y: url})
|
2019-01-31 21:23:16 +00:00
|
|
|
return years
|
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
@property
|
|
|
|
def mtime(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
if len(self.sortedkeys) > 0:
|
2019-04-09 21:34:03 +01:00
|
|
|
return self[self.sortedkeys[0]].published.timestamp
|
|
|
|
else:
|
|
|
|
return 0
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2019-05-23 09:06:34 +01:00
|
|
|
def feedpath(self, fname):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.dpath, settings.paths.feed, fname)
|
2018-11-10 20:49:13 +00:00
|
|
|
|
|
|
|
def get_posts(self, start=0, end=-1):
|
2019-06-25 22:48:04 +01:00
|
|
|
return [self[k].jsonld for k in self.sortedkeys[start:end]]
|
2018-11-10 20:49:13 +00:00
|
|
|
|
|
|
|
def is_uptodate(self, fpath, ts):
|
2019-06-25 22:48:04 +01:00
|
|
|
if settings.args.get("force"):
|
2018-11-10 20:49:13 +00:00
|
|
|
return False
|
|
|
|
if not os.path.exists(fpath):
|
|
|
|
return False
|
2019-02-16 00:14:12 +00:00
|
|
|
if mtime(fpath) >= ts:
|
2018-11-10 20:49:13 +00:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def newest(self, start=0, end=-1):
|
|
|
|
if start == end:
|
|
|
|
end = -1
|
2019-06-25 22:48:04 +01:00
|
|
|
s = sorted([self[k].dt for k in self.sortedkeys[start:end]], reverse=True)
|
2019-04-09 21:34:03 +01:00
|
|
|
if len(s) > 0:
|
2019-06-25 22:48:04 +01:00
|
|
|
return s[0] # Timestamp in seconds since epoch
|
2019-04-09 21:34:03 +01:00
|
|
|
else:
|
|
|
|
return 0
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
@property
|
|
|
|
def ctmplvars(self):
|
|
|
|
return {
|
2019-06-25 22:48:04 +01:00
|
|
|
"name": self.name,
|
|
|
|
"url": self.url,
|
|
|
|
"feed": self.feedurl,
|
|
|
|
"title": self.title,
|
2019-01-15 21:28:58 +00:00
|
|
|
}
|
|
|
|
|
2019-02-16 00:14:12 +00:00
|
|
|
def tmplvars(self, posts=[], year=None):
|
2019-01-31 21:23:16 +00:00
|
|
|
baseurl = self.url
|
|
|
|
if year:
|
2019-06-25 22:48:04 +01:00
|
|
|
baseurl = "%s%s/" % (baseurl, year)
|
2018-07-20 16:45:42 +01:00
|
|
|
return {
|
2019-06-25 22:48:04 +01:00
|
|
|
"baseurl": baseurl,
|
|
|
|
"site": settings.site,
|
|
|
|
"menu": settings.menu,
|
|
|
|
"meta": settings.meta,
|
|
|
|
"category": {
|
|
|
|
"name": self.name,
|
|
|
|
"paginated": self.is_paginated,
|
|
|
|
"url": self.url,
|
|
|
|
"feed": self.feedurl,
|
|
|
|
"title": self.title,
|
|
|
|
"year": year,
|
|
|
|
"years": self.years,
|
2018-11-04 12:57:51 +00:00
|
|
|
},
|
2019-06-25 22:48:04 +01:00
|
|
|
"posts": posts,
|
|
|
|
"fnames": settings.filenames,
|
2018-07-20 16:45:42 +01:00
|
|
|
}
|
2017-10-29 19:11:01 +00:00
|
|
|
|
2019-05-23 09:06:34 +01:00
|
|
|
def indexfpath(self, subpath=None, fname=settings.filenames.html):
|
2018-11-04 23:27:53 +00:00
|
|
|
if subpath:
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.dpath, subpath, fname)
|
2018-11-04 23:27:53 +00:00
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(self.dpath, fname)
|
2018-10-10 22:36:48 +01:00
|
|
|
|
|
|
|
async def render_feed(self, xmlformat):
|
2019-06-25 22:48:04 +01:00
|
|
|
if "json" == xmlformat:
|
2019-05-29 20:34:47 +01:00
|
|
|
await self.render_json()
|
|
|
|
return
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info('rendering category "%s" %s feed', self.name, xmlformat)
|
2019-05-23 09:06:34 +01:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
start = 0
|
2019-02-07 19:27:15 +00:00
|
|
|
end = int(settings.pagination)
|
2017-11-10 15:56:45 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
fg = FeedGenerator()
|
2018-11-04 23:27:53 +00:00
|
|
|
fg.id(self.feedurl)
|
2018-07-20 16:45:42 +01:00
|
|
|
fg.title(self.title)
|
2019-06-25 22:48:04 +01:00
|
|
|
fg.author({"name": settings.author.name, "email": settings.author.email})
|
|
|
|
fg.logo("%s/favicon.png" % settings.site.url)
|
|
|
|
fg.updated(arrow.get(self.mtime).to("utc").datetime)
|
2019-05-23 09:06:34 +01:00
|
|
|
fg.description(settings.site.headline)
|
2017-10-27 10:29:33 +01:00
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
for k in reversed(self.sortedkeys[start:end]):
|
|
|
|
post = self[k]
|
2018-07-20 16:45:42 +01:00
|
|
|
fe = fg.add_entry()
|
2018-10-10 22:36:48 +01:00
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
fe.id(post.url)
|
|
|
|
fe.title(post.title)
|
2019-06-25 22:48:04 +01:00
|
|
|
fe.author({"name": settings.author.name, "email": settings.author.email})
|
|
|
|
fe.category(
|
|
|
|
{
|
|
|
|
"term": post.category,
|
|
|
|
"label": post.category,
|
|
|
|
"scheme": "%s/%s/%s/"
|
|
|
|
% (settings.site.url, settings.paths.category, post.category),
|
|
|
|
}
|
|
|
|
)
|
2018-10-10 22:36:48 +01:00
|
|
|
|
2019-02-07 19:27:15 +00:00
|
|
|
fe.published(post.published.datetime)
|
|
|
|
fe.updated(arrow.get(post.dt).datetime)
|
2018-10-10 22:36:48 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
fe.rights(
|
|
|
|
"%s %s %s"
|
|
|
|
% (
|
|
|
|
post.licence.upper(),
|
|
|
|
settings.author.name,
|
|
|
|
post.published.format("YYYY"),
|
|
|
|
)
|
|
|
|
)
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if xmlformat == "rss":
|
2019-02-07 19:27:15 +00:00
|
|
|
fe.link(href=post.url)
|
2019-06-25 22:48:04 +01:00
|
|
|
fe.content(post.html_content, type="CDATA")
|
2019-02-07 19:27:15 +00:00
|
|
|
if post.is_photo:
|
2018-10-10 22:36:48 +01:00
|
|
|
fe.enclosure(
|
2019-02-07 19:27:15 +00:00
|
|
|
post.photo.href,
|
|
|
|
"%d" % post.photo.mime_size,
|
|
|
|
post.photo.mime_type,
|
2018-10-10 22:36:48 +01:00
|
|
|
)
|
2019-06-25 22:48:04 +01:00
|
|
|
elif xmlformat == "atom":
|
|
|
|
fe.link(href=post.url, rel="alternate", type="text/html")
|
|
|
|
fe.content(src=post.url, type="text/html")
|
2019-02-07 19:27:15 +00:00
|
|
|
fe.summary(post.summary)
|
2018-10-10 22:36:48 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if xmlformat == "rss":
|
2018-11-04 23:27:53 +00:00
|
|
|
fg.link(href=self.feedurl)
|
2019-05-23 09:06:34 +01:00
|
|
|
writepath(self.feedpath(settings.filenames.rss), fg.rss_str(pretty=True))
|
2019-06-25 22:48:04 +01:00
|
|
|
elif xmlformat == "atom":
|
|
|
|
fg.link(href=self.feedurl, rel="self")
|
|
|
|
fg.link(href=settings.meta.get("hub"), rel="hub")
|
2019-05-23 09:06:34 +01:00
|
|
|
writepath(self.feedpath(settings.filenames.atom), fg.atom_str(pretty=True))
|
2017-10-28 19:08:40 +01:00
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
async def render_json(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info('rendering category "%s" JSON feed', self.name)
|
2019-03-22 15:49:24 +00:00
|
|
|
|
|
|
|
js = {
|
|
|
|
"version": "https://jsonfeed.org/version/1",
|
|
|
|
"title": self.title,
|
|
|
|
"home_page_url": settings.site.url,
|
2019-05-23 09:06:34 +01:00
|
|
|
"feed_url": "%s%s" % (self.url, settings.filenames.json),
|
2019-03-22 15:49:24 +00:00
|
|
|
"author": {
|
|
|
|
"name": settings.author.name,
|
|
|
|
"url": settings.author.url,
|
|
|
|
"avatar": settings.author.image,
|
|
|
|
},
|
2019-06-25 22:48:04 +01:00
|
|
|
"items": [],
|
2019-03-22 15:49:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
for k in reversed(self.sortedkeys[0 : int(settings.pagination)]):
|
2019-03-22 15:49:24 +00:00
|
|
|
post = self[k]
|
|
|
|
pjs = {
|
|
|
|
"id": post.url,
|
|
|
|
"content_text": post.txt_content,
|
|
|
|
"content_html": post.html_content,
|
|
|
|
"url": post.url,
|
|
|
|
"date_published": str(post.published),
|
|
|
|
}
|
|
|
|
if len(post.summary):
|
|
|
|
pjs.update({"summary": post.txt_summary})
|
|
|
|
if post.is_photo:
|
2019-06-25 22:48:04 +01:00
|
|
|
pjs.update(
|
|
|
|
{
|
|
|
|
"attachment": {
|
|
|
|
"url": post.photo.href,
|
|
|
|
"mime_type": post.photo.mime_type,
|
|
|
|
"size_in_bytes": "%d" % post.photo.mime_size,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2019-03-22 15:49:24 +00:00
|
|
|
js["items"].append(pjs)
|
|
|
|
writepath(
|
2019-05-23 09:06:34 +01:00
|
|
|
self.feedpath(settings.filenames.json),
|
2019-06-25 22:48:04 +01:00
|
|
|
json.dumps(js, indent=4, ensure_ascii=False),
|
2019-03-22 15:49:24 +00:00
|
|
|
)
|
|
|
|
|
2018-11-04 12:57:51 +00:00
|
|
|
async def render_flat(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("rendering flat archive for %s", self.name)
|
|
|
|
r = J2.get_template(self.template).render(self.tmplvars(self.get_posts()))
|
2018-11-04 23:27:53 +00:00
|
|
|
writepath(self.indexfpath(), r)
|
2017-05-23 11:14:47 +01:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
async def render_gopher(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
lines = ["%s - %s" % (self.name, settings.site.name), "", ""]
|
2019-02-25 22:40:01 +00:00
|
|
|
for post in self.get_posts():
|
|
|
|
line = "0%s\t/%s/%s\t%s\t70" % (
|
|
|
|
post.headline,
|
|
|
|
post.name,
|
2019-05-23 09:06:34 +01:00
|
|
|
settings.filenames.txt,
|
2019-06-25 22:48:04 +01:00
|
|
|
settings.site.name,
|
2019-02-25 22:40:01 +00:00
|
|
|
)
|
|
|
|
lines.append(line)
|
2019-06-25 22:48:04 +01:00
|
|
|
if len(post.description):
|
2019-03-22 15:49:24 +00:00
|
|
|
lines.extend(str(PandocHTML2TXT(post.description)).split("\n"))
|
2019-06-25 22:48:04 +01:00
|
|
|
if isinstance(post["image"], list):
|
|
|
|
for img in post["image"]:
|
2019-02-25 22:40:01 +00:00
|
|
|
line = "I%s\t/%s/%s\t%s\t70" % (
|
|
|
|
img.headline,
|
|
|
|
post.name,
|
|
|
|
img.name,
|
2019-06-25 22:48:04 +01:00
|
|
|
settings.site.name,
|
2019-02-25 22:40:01 +00:00
|
|
|
)
|
|
|
|
lines.append(line)
|
2019-06-25 22:48:04 +01:00
|
|
|
lines.append("")
|
|
|
|
writepath(self.indexfpath(fname=settings.filenames.gopher), "\r\n".join(lines))
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
async def render_archives(self):
|
2019-01-31 21:23:16 +00:00
|
|
|
for year in self.years.keys():
|
|
|
|
if year == self.newest_year:
|
2018-11-04 23:27:53 +00:00
|
|
|
fpath = self.indexfpath()
|
2019-02-16 00:14:12 +00:00
|
|
|
tyear = None
|
2018-11-04 12:57:51 +00:00
|
|
|
else:
|
2019-01-31 21:23:16 +00:00
|
|
|
fpath = self.indexfpath("%d" % (year))
|
2019-02-16 00:14:12 +00:00
|
|
|
tyear = year
|
2019-06-25 22:48:04 +01:00
|
|
|
y = arrow.get("%d" % year, self.trange).to("utc")
|
|
|
|
tsmin = y.floor("year").timestamp
|
|
|
|
tsmax = y.ceil("year").timestamp
|
2019-01-31 21:23:16 +00:00
|
|
|
start = len(self.sortedkeys)
|
|
|
|
end = 0
|
|
|
|
|
|
|
|
for index, value in enumerate(self.sortedkeys):
|
|
|
|
if value <= tsmax and index < start:
|
|
|
|
start = index
|
|
|
|
if value >= tsmin and index > end:
|
|
|
|
end = index
|
|
|
|
|
|
|
|
if self.is_uptodate(fpath, self[self.sortedkeys[start]].dt):
|
2019-02-25 22:40:01 +00:00
|
|
|
logger.info("%s / %d is up to date", self.name, year)
|
2018-11-04 23:27:53 +00:00
|
|
|
else:
|
2019-01-31 21:23:16 +00:00
|
|
|
logger.info("updating %s / %d", self.name, year)
|
|
|
|
logger.info("getting posts from %d to %d", start, end)
|
2018-11-04 23:27:53 +00:00
|
|
|
r = J2.get_template(self.template).render(
|
|
|
|
self.tmplvars(
|
2019-01-31 21:23:16 +00:00
|
|
|
# I don't know why end needs the +1, but without that
|
|
|
|
# some posts disappear
|
|
|
|
# TODO figure this out...
|
2019-02-25 22:40:01 +00:00
|
|
|
self.get_posts(start, end + 1),
|
2019-06-25 22:48:04 +01:00
|
|
|
tyear,
|
2018-11-04 23:27:53 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
writepath(fpath, r)
|
2018-07-22 14:52:32 +01:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
async def render_feeds(self):
|
2019-05-23 09:06:34 +01:00
|
|
|
m = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"rss": self.feedpath(settings.filenames.rss),
|
|
|
|
"atom": self.feedpath(settings.filenames.atom),
|
|
|
|
"json": self.feedpath(settings.filenames.json),
|
2019-05-23 09:06:34 +01:00
|
|
|
}
|
|
|
|
for ft, path in m.items():
|
|
|
|
if not self.is_uptodate(path, self.newest()):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("%s outdated, generating new", ft)
|
2019-05-23 09:06:34 +01:00
|
|
|
await self.render_feed(ft)
|
2019-03-22 15:49:24 +00:00
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
async def render(self):
|
|
|
|
await self.render_feeds()
|
2019-02-25 22:40:01 +00:00
|
|
|
if not self.is_uptodate(self.indexfpath(), self.newest()):
|
|
|
|
await self.render_gopher()
|
2019-02-07 19:27:15 +00:00
|
|
|
if not self.is_paginated:
|
2019-01-15 21:28:58 +00:00
|
|
|
if not self.is_uptodate(self.indexfpath(), self.newest()):
|
2018-11-10 20:49:13 +00:00
|
|
|
await self.render_flat()
|
|
|
|
else:
|
|
|
|
await self.render_archives()
|
|
|
|
|
|
|
|
|
2018-07-23 11:04:48 +01:00
|
|
|
class Sitemap(dict):
|
|
|
|
@property
|
|
|
|
def mtime(self):
|
|
|
|
r = 0
|
|
|
|
if os.path.exists(self.renderfile):
|
2019-02-16 00:14:12 +00:00
|
|
|
r = mtime(self.renderfile)
|
2018-07-23 11:04:48 +01:00
|
|
|
return r
|
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
def append(self, post):
|
|
|
|
self[post.url] = post.mtime
|
|
|
|
|
2018-07-23 11:04:48 +01:00
|
|
|
@property
|
|
|
|
def renderfile(self):
|
2019-06-25 22:48:04 +01:00
|
|
|
return os.path.join(settings.paths.get("build"), settings.filenames.sitemap)
|
2018-07-23 11:04:48 +01:00
|
|
|
|
Back To Pandoc
So, Python Markdown is a bottomless pit of horrors, including crippling parsing bugs,
random out of nowhere, lack of features. It's definitely much faster, than
Pandoc, but Pandoc doesn't go full retard where there's a regex in a fenced code block,
that happens to be a regex for markdown elements.
Also added some ugly post string replacements to make Pandoc fenced code output work
with Prism:
instead of the Pandoc <pre class="codelang"><code>, Prism wants
<pre><code class="language-codelang>, so I added a regex sub, because it's 00:32.
2018-08-04 00:28:55 +01:00
|
|
|
async def render(self):
|
2019-04-09 21:34:03 +01:00
|
|
|
if len(self) > 0:
|
|
|
|
if self.mtime >= sorted(self.values())[-1]:
|
|
|
|
return
|
2019-06-25 22:48:04 +01:00
|
|
|
with open(self.renderfile, "wt") as f:
|
2019-04-09 21:34:03 +01:00
|
|
|
f.write("\n".join(sorted(self.keys())))
|
2018-07-22 08:48:47 +01:00
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
class WebmentionIO(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.params = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"token": "%s" % (keys.webmentionio.get("token")),
|
|
|
|
"since": "%s" % str(self.since),
|
|
|
|
"domain": "%s" % (keys.webmentionio.get("domain")),
|
2018-12-27 19:48:06 +00:00
|
|
|
}
|
2019-06-25 22:48:04 +01:00
|
|
|
self.url = "https://webmention.io/api/mentions"
|
2018-08-08 09:42:42 +01:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
@property
|
|
|
|
def since(self):
|
|
|
|
newest = 0
|
2019-06-25 22:48:04 +01:00
|
|
|
content = settings.paths.get("content")
|
|
|
|
for e in glob.glob(os.path.join(content, "*", "*", "*.md")):
|
2019-05-23 09:06:34 +01:00
|
|
|
if os.path.basename(e) == settings.filenames.md:
|
2018-12-27 19:48:06 +00:00
|
|
|
continue
|
|
|
|
# filenames are like [received epoch]-[slugified source url].md
|
|
|
|
try:
|
2019-06-25 22:48:04 +01:00
|
|
|
mtime = int(os.path.basename(e).split("-")[0])
|
2018-12-27 19:48:06 +00:00
|
|
|
except Exception as exc:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("int conversation failed: %s, file was: %s", exc, e)
|
2018-12-27 19:48:06 +00:00
|
|
|
continue
|
|
|
|
if mtime > newest:
|
|
|
|
newest = mtime
|
2019-02-25 22:40:01 +00:00
|
|
|
return arrow.get(newest + 1)
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
def makecomment(self, webmention):
|
2019-06-25 22:48:04 +01:00
|
|
|
if "published_ts" in webmention.get("data"):
|
|
|
|
maybe = webmention.get("data").get("published")
|
|
|
|
if not maybe or maybe == "None":
|
|
|
|
dt = arrow.get(webmention.get("verified_date"))
|
2018-12-27 19:48:06 +00:00
|
|
|
else:
|
2019-06-25 22:48:04 +01:00
|
|
|
dt = arrow.get(webmention.get("data").get("published"))
|
2018-08-08 09:42:42 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
slug = os.path.split(urlparse(webmention.get("target")).path.lstrip("/"))[0]
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
# ignore selfpings
|
2019-06-25 22:48:04 +01:00
|
|
|
if slug == settings.site.get("name"):
|
2018-12-27 19:48:06 +00:00
|
|
|
return
|
2018-07-22 17:59:26 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
fdir = glob.glob(os.path.join(settings.paths.get("content"), "*", slug))
|
2018-12-27 19:48:06 +00:00
|
|
|
if not len(fdir):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("couldn't find post for incoming webmention: %s", webmention)
|
2018-12-27 19:48:06 +00:00
|
|
|
return
|
|
|
|
elif len(fdir) > 1:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("multiple posts found for incoming webmention: %s", webmention)
|
2018-12-27 19:48:06 +00:00
|
|
|
return
|
2018-07-22 17:59:26 +01:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
fdir = fdir.pop()
|
|
|
|
fpath = os.path.join(
|
2019-06-25 22:48:04 +01:00
|
|
|
fdir, "%d-%s.md" % (dt.timestamp, url2slug(webmention.get("source")))
|
2018-12-27 19:48:06 +00:00
|
|
|
)
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
author = webmention.get("data", {}).get("author", None)
|
2019-01-15 21:28:58 +00:00
|
|
|
if not author:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("missing author info on webmention; skipping")
|
2019-01-15 21:28:58 +00:00
|
|
|
return
|
2019-01-05 11:55:40 +00:00
|
|
|
meta = {
|
2019-06-25 22:48:04 +01:00
|
|
|
"author": {
|
|
|
|
"name": author.get("name", ""),
|
|
|
|
"url": author.get("url", ""),
|
|
|
|
"photo": author.get("photo", ""),
|
2019-01-05 11:55:40 +00:00
|
|
|
},
|
2019-06-25 22:48:04 +01:00
|
|
|
"date": str(dt),
|
|
|
|
"source": webmention.get("source"),
|
|
|
|
"target": webmention.get("target"),
|
|
|
|
"type": webmention.get("activity").get("type", "webmention"),
|
2018-11-10 20:49:13 +00:00
|
|
|
}
|
2019-01-05 11:55:40 +00:00
|
|
|
|
2019-04-29 09:33:21 +01:00
|
|
|
try:
|
2019-06-25 22:48:04 +01:00
|
|
|
txt = webmention.get("data").get("content", "").strip()
|
2019-04-29 09:33:21 +01:00
|
|
|
except Exception as e:
|
2019-06-25 22:48:04 +01:00
|
|
|
txt = ""
|
2019-04-29 09:33:21 +01:00
|
|
|
pass
|
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
r = "---\n%s\n---\n\n%s\n" % (utfyamldump(meta), txt)
|
2019-01-05 11:55:40 +00:00
|
|
|
writepath(fpath, r)
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
def run(self):
|
|
|
|
webmentions = requests.get(self.url, params=self.params)
|
|
|
|
logger.info("queried webmention.io with: %s", webmentions.url)
|
|
|
|
if webmentions.status_code != requests.codes.ok:
|
|
|
|
return
|
|
|
|
try:
|
|
|
|
mentions = webmentions.json()
|
2019-06-25 22:48:04 +01:00
|
|
|
for webmention in mentions.get("links"):
|
2018-12-27 19:48:06 +00:00
|
|
|
self.makecomment(webmention)
|
|
|
|
except ValueError as e:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("failed to query webmention.io: %s", e)
|
2018-12-27 19:48:06 +00:00
|
|
|
pass
|
2018-08-15 11:02:59 +01:00
|
|
|
|
2019-02-25 22:40:01 +00:00
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
# class GranaryIO(dict):
|
2019-06-25 22:48:04 +01:00
|
|
|
# granary = 'https://granary.io/url'
|
|
|
|
# convert_to = ['as2', 'mf2-json', 'jsonfeed']
|
|
|
|
|
|
|
|
# def __init__(self, source):
|
|
|
|
# self.source = source
|
|
|
|
|
|
|
|
# def run(self):
|
|
|
|
# for c in self.convert_to:
|
|
|
|
# p = {
|
|
|
|
# 'url': self.source,
|
|
|
|
# 'input': html,
|
|
|
|
# 'output': c
|
|
|
|
# }
|
|
|
|
# r = requests.get(self.granary, params=p)
|
|
|
|
# logger.info("queried granary.io for %s for url: %s", c, self.source)
|
|
|
|
# if r.status_code != requests.codes.ok:
|
|
|
|
# continue
|
|
|
|
# try:
|
|
|
|
# self[c] = webmentions.text
|
|
|
|
# except ValueError as e:
|
|
|
|
# logger.error('failed to query granary.io: %s', e)
|
|
|
|
# pass
|
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
|
2019-05-28 13:27:34 +01:00
|
|
|
def dat():
|
|
|
|
for url in settings.site.sameAs:
|
|
|
|
if "dat://" in url:
|
2019-06-25 22:48:04 +01:00
|
|
|
p = os.path.join(settings.paths.build, ".well-known")
|
2019-05-28 13:27:34 +01:00
|
|
|
if not os.path.isdir(p):
|
|
|
|
os.makedirs(p)
|
2019-06-25 22:48:04 +01:00
|
|
|
p = os.path.join(settings.paths.build, ".well-known", "dat")
|
|
|
|
if not os.path.exists(p) or settings.args.get("force"):
|
2019-05-28 13:27:34 +01:00
|
|
|
writepath(p, "%s\nTTL=3600" % (url))
|
|
|
|
|
2019-03-22 15:49:24 +00:00
|
|
|
|
2018-07-23 11:04:48 +01:00
|
|
|
def make():
|
|
|
|
start = int(round(time.time() * 1000))
|
|
|
|
last = 0
|
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
# this needs to be before collecting the 'content' itself
|
2019-06-25 22:48:04 +01:00
|
|
|
if not settings.args.get("offline") and not settings.args.get("noservices"):
|
2018-12-27 19:48:06 +00:00
|
|
|
incoming = WebmentionIO()
|
|
|
|
incoming.run()
|
|
|
|
|
|
|
|
queue = AQ()
|
|
|
|
send = []
|
2019-06-24 13:52:44 +01:00
|
|
|
firsttimepublished = []
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
content = settings.paths.get("content")
|
2018-08-15 11:02:59 +01:00
|
|
|
rules = IndexPHP()
|
2018-08-08 09:42:42 +01:00
|
|
|
|
2018-11-10 20:49:13 +00:00
|
|
|
micropub = MicropubPHP()
|
2018-12-27 19:48:06 +00:00
|
|
|
queue.put(micropub.render())
|
2018-11-10 20:49:13 +00:00
|
|
|
|
2018-08-08 09:42:42 +01:00
|
|
|
webhook = WebhookPHP()
|
2018-12-27 19:48:06 +00:00
|
|
|
queue.put(webhook.render())
|
2018-08-08 09:42:42 +01:00
|
|
|
|
2018-07-23 11:04:48 +01:00
|
|
|
sitemap = Sitemap()
|
|
|
|
search = Search()
|
2018-07-20 16:45:42 +01:00
|
|
|
categories = {}
|
2018-12-27 19:48:06 +00:00
|
|
|
frontposts = Category()
|
2019-06-25 22:48:04 +01:00
|
|
|
home = Home(settings.paths.get("home"))
|
2018-07-22 08:48:47 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
for e in sorted(glob.glob(os.path.join(content, "*", "*", settings.filenames.md))):
|
2018-07-20 16:45:42 +01:00
|
|
|
post = Singular(e)
|
2018-12-27 19:48:06 +00:00
|
|
|
# deal with images, if needed
|
2018-07-20 16:45:42 +01:00
|
|
|
for i in post.images.values():
|
2018-12-27 19:48:06 +00:00
|
|
|
queue.put(i.downsize())
|
2019-04-29 09:33:21 +01:00
|
|
|
if not post.is_future:
|
|
|
|
for i in post.to_ping:
|
|
|
|
send.append(i)
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
# render and arbitrary file copy tasks for this very post
|
|
|
|
queue.put(post.render())
|
2019-07-14 20:48:49 +01:00
|
|
|
queue.put(post.copy_files())
|
2018-08-15 11:02:59 +01:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
# skip draft posts from anything further
|
2018-07-25 13:24:31 +01:00
|
|
|
if post.is_future:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("%s is for the future", post.name)
|
2018-07-25 13:24:31 +01:00
|
|
|
continue
|
2019-06-24 13:52:44 +01:00
|
|
|
elif not os.path.exists(post.renderfile):
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.debug("%s seems to be fist time published", post.name)
|
2019-06-24 13:52:44 +01:00
|
|
|
firsttimepublished.append(post)
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
# add post to search database
|
|
|
|
search.append(post)
|
|
|
|
|
|
|
|
# start populating sitemap
|
|
|
|
sitemap.append(post)
|
|
|
|
|
|
|
|
# populate redirects, if any
|
2018-11-03 09:48:37 +00:00
|
|
|
rules.add_redirect(post.shortslug, post.url)
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
# any category starting with '_' are special: they shouldn't have a
|
|
|
|
# category archive page
|
|
|
|
if post.is_page:
|
2018-08-08 09:42:42 +01:00
|
|
|
continue
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
# populate the category with the post
|
2018-08-08 09:42:42 +01:00
|
|
|
if post.category not in categories:
|
|
|
|
categories[post.category] = Category(post.category)
|
|
|
|
categories[post.category][post.published.timestamp] = post
|
2018-12-27 19:48:06 +00:00
|
|
|
|
|
|
|
# add to front, if allowed
|
2018-08-08 09:42:42 +01:00
|
|
|
if post.is_front:
|
2018-12-27 19:48:06 +00:00
|
|
|
frontposts[post.published.timestamp] = post
|
2018-07-20 16:45:42 +01:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
# commit to search database - this saves quite a few disk writes
|
2018-07-22 11:33:59 +01:00
|
|
|
search.__exit__()
|
2018-11-03 09:48:37 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
# render search and sitemap
|
|
|
|
queue.put(search.render())
|
|
|
|
queue.put(sitemap.render())
|
|
|
|
|
|
|
|
# make gone and redirect arrays for PHP
|
2019-06-25 22:48:04 +01:00
|
|
|
for e in glob.glob(os.path.join(content, "*", "*.del")):
|
2018-11-03 09:48:37 +00:00
|
|
|
post = Gone(e)
|
|
|
|
rules.add_gone(post.source)
|
2019-06-25 22:48:04 +01:00
|
|
|
for e in glob.glob(os.path.join(content, "*", "*.url")):
|
2018-11-03 09:48:37 +00:00
|
|
|
post = Redirect(e)
|
|
|
|
rules.add_redirect(post.source, post.target)
|
2018-12-27 19:48:06 +00:00
|
|
|
# render 404 fallback PHP
|
|
|
|
queue.put(rules.render())
|
2018-11-03 09:48:37 +00:00
|
|
|
|
2018-12-27 19:48:06 +00:00
|
|
|
# render categories
|
2018-07-20 16:45:42 +01:00
|
|
|
for category in categories.values():
|
2019-01-15 21:28:58 +00:00
|
|
|
home.add(category, category.get(category.sortedkeys[0]))
|
2018-12-27 19:48:06 +00:00
|
|
|
queue.put(category.render())
|
|
|
|
|
2019-01-15 21:28:58 +00:00
|
|
|
queue.put(frontposts.render_feeds())
|
|
|
|
queue.put(home.render())
|
2018-12-27 19:48:06 +00:00
|
|
|
# actually run all the render & copy tasks
|
|
|
|
queue.run()
|
|
|
|
|
|
|
|
# copy static files
|
2019-06-25 22:48:04 +01:00
|
|
|
for e in glob.glob(os.path.join(content, "*.*")):
|
|
|
|
if e.endswith(".md"):
|
2019-01-15 21:28:58 +00:00
|
|
|
continue
|
2019-06-25 22:48:04 +01:00
|
|
|
t = os.path.join(settings.paths.get("build"), os.path.basename(e))
|
2019-02-16 00:14:12 +00:00
|
|
|
if os.path.exists(t) and mtime(e) <= mtime(t):
|
2018-07-20 16:45:42 +01:00
|
|
|
continue
|
|
|
|
cp(e, t)
|
2018-03-21 15:42:36 +00:00
|
|
|
|
2018-07-20 16:45:42 +01:00
|
|
|
end = int(round(time.time() * 1000))
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("process took %d ms" % (end - start))
|
2018-09-04 21:58:25 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if not settings.args.get("offline"):
|
2018-12-27 19:48:06 +00:00
|
|
|
# upload site
|
2019-04-10 09:37:24 +01:00
|
|
|
try:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("starting syncing")
|
2019-04-10 09:37:24 +01:00
|
|
|
os.system(
|
2019-06-25 22:48:04 +01:00
|
|
|
"rsync -avuhH --delete-after %s/ %s/"
|
|
|
|
% (
|
|
|
|
settings.paths.get("build"),
|
|
|
|
"%s/%s" % (settings.syncserver, settings.paths.get("remotewww")),
|
2019-04-10 09:37:24 +01:00
|
|
|
)
|
2018-09-04 21:58:25 +01:00
|
|
|
)
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("syncing finished")
|
2019-04-10 09:37:24 +01:00
|
|
|
except Exception as e:
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.error("syncing failed: %s", e)
|
2018-09-04 21:58:25 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
if not settings.args.get("offline") and not settings.args.get("noservices"):
|
|
|
|
logger.info("sending webmentions")
|
2018-12-27 19:48:06 +00:00
|
|
|
for wm in send:
|
|
|
|
queue.put(wm.send())
|
|
|
|
queue.run()
|
2019-06-25 22:48:04 +01:00
|
|
|
logger.info("sending webmentions finished")
|
2018-07-22 14:52:32 +01:00
|
|
|
|
2019-06-24 13:52:44 +01:00
|
|
|
for post in firsttimepublished:
|
2019-06-25 22:43:45 +01:00
|
|
|
queue.put(post.save_memento())
|
|
|
|
queue.put(post.save_to_archiveorg())
|
|
|
|
queue.run()
|
2018-09-04 21:58:25 +01:00
|
|
|
|
2019-06-25 22:48:04 +01:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
2018-07-20 16:45:42 +01:00
|
|
|
make()
|