removed enclosure from rss; re-added initial steps for sending webmentions for replies; minor cleanups
This commit is contained in:
parent
0fc792fe00
commit
3bef504be1
6 changed files with 110 additions and 55 deletions
27
README.md
27
README.md
|
@ -1,17 +1,36 @@
|
||||||
# NASG (Not Another Static Generator)
|
# NASG (Not Another Static Generator...)
|
||||||
|
|
||||||
This is a tiny static site generator, written in Python, to scratch my own itches.
|
This is a tiny static site generator, written in Python, to scratch my own itches.
|
||||||
It is most probably not suitable for anyone else.
|
It is most probably not suitable for anyone else, but feel free to use it for ideas. Keep in mind that the project is licenced under GPL.
|
||||||
|
|
||||||
## Why not [insert static generator here]?
|
## Why not [insert static generator here]?
|
||||||
|
|
||||||
- DRY -Don't Repeat Yourself - is good, so instead of sidefiles for images, I'm using XMP metadata, which most of the ones availabe don't handle well;
|
- DRY - Don't Repeat Yourself - is good, so instead of sidefiles for images, I'm using XMP metadata, which most of the ones availabe don't handle well;
|
||||||
- writing a proper plugin to existing generators - Pelican, Nicola, etc - might have taken longer and I wanted to extend my Python knowledge
|
- writing plugins to existing generators - Pelican, Nicola, etc - might have taken longer and I wanted to extend my Python knowledge
|
||||||
- I wanted to use the best available utilities for some tasks, like `Pandoc` and `exiftool` instead of Python libraries trying to achive the same
|
- I wanted to use the best available utilities for some tasks, like `Pandoc` and `exiftool` instead of Python libraries trying to achive the same
|
||||||
- I needed to handle webmentions and comments
|
- I needed to handle webmentions and comments
|
||||||
|
|
||||||
Don't expect anything fancy: my Python Fu has much to learn.
|
Don't expect anything fancy: my Python Fu has much to learn.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
### External dependencies
|
||||||
|
|
||||||
|
PHP is in order to use [XRay](https://github.com/aaronpk/XRay/)
|
||||||
|
|
||||||
|
```
|
||||||
|
apt-get install pandoc exiftool php7.0-bcmath php7.0-bz2 php7.0-cli php7.0-common php7.0-curl php7.0-gd php7.0-imap php7.0-intl php7.0-json php7.0-mbstring php7.0-mcrypt php7.0-mysql php7.0-odbc php7.0-opcache php7.0-readline php7.0-sqlite3 php7.0-xml php7.0-zip python3 python3-pip python3-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Get XRay:
|
||||||
|
```
|
||||||
|
mkdir /usr/local/lib/php
|
||||||
|
cd /usr/local/lib/php
|
||||||
|
wget https://github.com/aaronpk/XRay/releases/download/v1.3.1/xray-app.zip
|
||||||
|
unzip xray-app.zip
|
||||||
|
rm xray-app.zip
|
||||||
|
```
|
||||||
|
|
||||||
## How content is organized
|
## How content is organized
|
||||||
|
|
||||||
The directory structure of the "source" is something like this:
|
The directory structure of the "source" is something like this:
|
||||||
|
|
|
@ -1,3 +1 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
__version__ = '2.0.0'
|
|
||||||
|
|
69
nasg.py
69
nasg.py
|
@ -13,7 +13,6 @@ import asyncio
|
||||||
from math import ceil
|
from math import ceil
|
||||||
import csv
|
import csv
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import magic
|
|
||||||
|
|
||||||
import frontmatter
|
import frontmatter
|
||||||
import arrow
|
import arrow
|
||||||
|
@ -191,7 +190,6 @@ class Category(NoDupeContainer):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title(self):
|
def title(self):
|
||||||
# TODO proper title
|
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -311,26 +309,37 @@ class Singular(object):
|
||||||
|
|
||||||
|
|
||||||
def init_extras(self):
|
def init_extras(self):
|
||||||
self.process_webmentions()
|
self.receive_webmentions()
|
||||||
c = self.comments
|
c = self.comments
|
||||||
|
|
||||||
|
# note: due to SQLite locking, this will not be async for now
|
||||||
# TODO this should be async
|
def receive_webmentions(self):
|
||||||
def process_webmentions(self):
|
|
||||||
wdb = shared.WebmentionQueue()
|
wdb = shared.WebmentionQueue()
|
||||||
queued = wdb.get_queued(self.url)
|
queued = wdb.get_queued(self.url)
|
||||||
for incoming in queued:
|
for incoming in queued:
|
||||||
wm = Webmention(
|
wm = Webmention(
|
||||||
incoming.get('id'),
|
|
||||||
incoming.get('source'),
|
incoming.get('source'),
|
||||||
incoming.get('target'),
|
incoming.get('target'),
|
||||||
incoming.get('dt')
|
incoming.get('dt')
|
||||||
)
|
)
|
||||||
wm.run()
|
wm.receive()
|
||||||
|
|
||||||
wdb.entry_done(incoming.get('id'))
|
wdb.entry_done(incoming.get('id'))
|
||||||
wdb.finish()
|
wdb.finish()
|
||||||
|
|
||||||
|
# note: due to SQLite locking, this will not be async for now
|
||||||
|
def send_webmentions(self):
|
||||||
|
if not self.is_reply:
|
||||||
|
return
|
||||||
|
wdb = shared.WebmentionQueue()
|
||||||
|
id = wdb.queue(self.url, self.is_reply)
|
||||||
|
wm = Webmention(
|
||||||
|
self.url,
|
||||||
|
self.is_reply
|
||||||
|
)
|
||||||
|
wm.send()
|
||||||
|
wdb.entry_done(id)
|
||||||
|
wdb.finish()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def redirects(self):
|
def redirects(self):
|
||||||
r = self.meta.get('redirect', [])
|
r = self.meta.get('redirect', [])
|
||||||
|
@ -523,9 +532,6 @@ class Singular(object):
|
||||||
im.title = title
|
im.title = title
|
||||||
im.cssclass = css
|
im.cssclass = css
|
||||||
body = body.replace(shortcode, str(im))
|
body = body.replace(shortcode, str(im))
|
||||||
|
|
||||||
# TODO if multiple meta images, inline all except the first
|
|
||||||
# which will be added at the HTML stage or as enclosure to the feed
|
|
||||||
return body
|
return body
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -558,16 +564,6 @@ class Singular(object):
|
||||||
def shortslug(self):
|
def shortslug(self):
|
||||||
return shared.baseN(self.pubtime)
|
return shared.baseN(self.pubtime)
|
||||||
|
|
||||||
@property
|
|
||||||
def enclosure(self):
|
|
||||||
if not self.photo:
|
|
||||||
return {}
|
|
||||||
return {
|
|
||||||
'length': os.path.getsize(self.photo.fpath),
|
|
||||||
'url': self.photo.href,
|
|
||||||
'mime': magic.Magic(mime=True).from_file(self.photo.fpath),
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tmplvars(self):
|
def tmplvars(self):
|
||||||
# very simple caching because we might use this 4 times:
|
# very simple caching because we might use this 4 times:
|
||||||
|
@ -584,13 +580,11 @@ class Singular(object):
|
||||||
'slug': self.fname,
|
'slug': self.fname,
|
||||||
'shortslug': self.shortslug,
|
'shortslug': self.shortslug,
|
||||||
'licence': self.licence,
|
'licence': self.licence,
|
||||||
#'sourceurl': self.sourceurl,
|
|
||||||
'is_reply': self.is_reply,
|
'is_reply': self.is_reply,
|
||||||
'age': int(self.published.format('YYYY')) - int(arrow.utcnow().format('YYYY')),
|
'age': int(self.published.format('YYYY')) - int(arrow.utcnow().format('YYYY')),
|
||||||
'summary': self.summary,
|
'summary': self.summary,
|
||||||
'replies': self.replies,
|
'replies': self.replies,
|
||||||
'reactions': self.reactions,
|
'reactions': self.reactions,
|
||||||
'enclosure': self.enclosure,
|
|
||||||
}
|
}
|
||||||
return self._tmplvars
|
return self._tmplvars
|
||||||
|
|
||||||
|
@ -1049,10 +1043,9 @@ class Comment(object):
|
||||||
|
|
||||||
|
|
||||||
class Webmention(object):
|
class Webmention(object):
|
||||||
def __init__ (self, id, source, target, dt):
|
def __init__ (self, source, target, dt=arrow.utcnow().timestamp):
|
||||||
self.source = source
|
self.source = source
|
||||||
self.target = target
|
self.target = target
|
||||||
self.id = id
|
|
||||||
self.dt = arrow.get(dt).to('utc')
|
self.dt = arrow.get(dt).to('utc')
|
||||||
logging.info(
|
logging.info(
|
||||||
"processing webmention %s => %s",
|
"processing webmention %s => %s",
|
||||||
|
@ -1071,7 +1064,26 @@ class Webmention(object):
|
||||||
f.write(frontmatter.dumps(fm))
|
f.write(frontmatter.dumps(fm))
|
||||||
return
|
return
|
||||||
|
|
||||||
def run(self):
|
def send(self):
|
||||||
|
rels = shared.XRay(self.source).set_discover().parse()
|
||||||
|
endpoint = False
|
||||||
|
if 'rels' not in rels:
|
||||||
|
return
|
||||||
|
for k in rels.get('rels').keys():
|
||||||
|
if 'webmention' in k:
|
||||||
|
endpoint = rels.get('rels').get(k)
|
||||||
|
break
|
||||||
|
if not endpoint:
|
||||||
|
return
|
||||||
|
requests.post(
|
||||||
|
self.target,
|
||||||
|
data = {
|
||||||
|
'source': self.source,
|
||||||
|
'target': self.target
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def receive(self):
|
||||||
self._fetch()
|
self._fetch()
|
||||||
if 'data' not in self._source:
|
if 'data' not in self._source:
|
||||||
return
|
return
|
||||||
|
@ -1131,7 +1143,6 @@ class Webmention(object):
|
||||||
self.fname
|
self.fname
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def setup():
|
def setup():
|
||||||
""" parse input parameters and add them as params section to config """
|
""" parse input parameters and add them as params section to config """
|
||||||
parser = argparse.ArgumentParser(description='Parameters for NASG')
|
parser = argparse.ArgumentParser(description='Parameters for NASG')
|
||||||
|
@ -1281,8 +1292,6 @@ def build():
|
||||||
tasks.append(task)
|
tasks.append(task)
|
||||||
|
|
||||||
# TODO: send webmentions to any url
|
# TODO: send webmentions to any url
|
||||||
# TODO: comments
|
|
||||||
# TODO: ping websub?
|
|
||||||
|
|
||||||
# do all the things!
|
# do all the things!
|
||||||
w = asyncio.wait(tasks)
|
w = asyncio.wait(tasks)
|
||||||
|
|
17
setup.py
17
setup.py
|
@ -1,16 +1,25 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
from . import __version__
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
version=__version__,
|
version='2.0.0',
|
||||||
name="nasg",
|
name="nasg",
|
||||||
author="Peter Molnar",
|
author="Peter Molnar",
|
||||||
author_email="hello@petermolnar.eu",
|
author_email="hello@petermolnar.eu",
|
||||||
description="Not Another Static Generator - a static generator",
|
description="Not Another Static Generator - a static generator",
|
||||||
long_description=open('README.md').read(),
|
long_description=open('README.md').read(),
|
||||||
packages=['nasg'],
|
packages=['nasg'],
|
||||||
install_requires=['arrow', 'Jinja2', 'langdetect', 'requests', 'requests-oauthlib', 'sanic', 'unicode-slugify', 'Wand', 'emoji', 'html5lib', 'BeautifulSoup'],
|
install_requires=[
|
||||||
|
'arrow',
|
||||||
|
'Jinja2',
|
||||||
|
'langdetect',
|
||||||
|
'requests',
|
||||||
|
'requests-oauthlib',
|
||||||
|
'sanic',
|
||||||
|
'unicode-slugify',
|
||||||
|
'Wand',
|
||||||
|
'emoji',
|
||||||
|
],
|
||||||
url='https://github.com/petermolnar/nasg',
|
url='https://github.com/petermolnar/nasg',
|
||||||
license=open('LICENCE').read(),
|
license=open('./LICENSE').read(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
)
|
)
|
||||||
|
|
43
shared.py
43
shared.py
|
@ -7,7 +7,6 @@ import subprocess
|
||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
|
@ -28,21 +27,48 @@ class CMDLine(object):
|
||||||
|
|
||||||
|
|
||||||
class XRay(CMDLine):
|
class XRay(CMDLine):
|
||||||
xraypath = '/usr/local/lib/php/xray'
|
cmd_prefix = 'chdir("/usr/local/lib/php/xray"); include("vendor/autoload.php"); $xray = new p3k\XRay();'
|
||||||
|
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
super().__init__('php')
|
super().__init__('php')
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.target = ''
|
||||||
def parse(self):
|
self.cmd = (
|
||||||
cmd = (
|
|
||||||
self.executable,
|
self.executable,
|
||||||
'-r',
|
'-r',
|
||||||
'''chdir("%s"); include("vendor/autoload.php"); $xray = new p3k\XRay(); echo(json_encode($xray->parse("%s")));''' % (self.xraypath, self.url)
|
'%s; echo(json_encode($xray->parse("%s")));' % (
|
||||||
|
self.cmd_prefix,
|
||||||
|
self.url
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_receive(self, target):
|
||||||
|
self.cmd = (
|
||||||
|
self.executable,
|
||||||
|
'-r',
|
||||||
|
'%s; echo(json_encode($xray->parse("%s")));' % (
|
||||||
|
self.cmd_prefix,
|
||||||
|
self.url,
|
||||||
|
target
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_discover(self):
|
||||||
|
self.cmd = (
|
||||||
|
self.executable,
|
||||||
|
'-r',
|
||||||
|
'%s; echo(json_encode($xray->rels("%s")));' % (
|
||||||
|
self.cmd_prefix,
|
||||||
|
self.url,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
logging.debug('pulling %s with XRay', self.url)
|
logging.debug('pulling %s with XRay', self.url)
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
cmd,
|
self.cmd,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
|
@ -54,7 +80,6 @@ class XRay(CMDLine):
|
||||||
|
|
||||||
return json.loads(stdout.decode('utf-8').strip())
|
return json.loads(stdout.decode('utf-8').strip())
|
||||||
|
|
||||||
|
|
||||||
class Pandoc(CMDLine):
|
class Pandoc(CMDLine):
|
||||||
""" Pandoc command line call with piped in- and output """
|
""" Pandoc command line call with piped in- and output """
|
||||||
|
|
||||||
|
@ -439,7 +464,9 @@ class WebmentionQueue(object):
|
||||||
target
|
target
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
r = cursor.lastrowid
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
|
return r
|
||||||
|
|
||||||
def get_queued(self, fname=None):
|
def get_queued(self, fname=None):
|
||||||
logging.debug('getting queued webmentions for %s', fname)
|
logging.debug('getting queued webmentions for %s', fname)
|
||||||
|
|
|
@ -19,13 +19,6 @@
|
||||||
{%- if post.tags %}{% for tname in post.tags %}
|
{%- if post.tags %}{% for tname in post.tags %}
|
||||||
<category>{{ tname }}></category>
|
<category>{{ tname }}></category>
|
||||||
{% endfor %}{% endif -%}
|
{% endfor %}{% endif -%}
|
||||||
{%- if post.rssenclosure %}
|
|
||||||
<enclosure
|
|
||||||
url="{{ post.enclosure.url }}"
|
|
||||||
type="{{ post.enclosure.mime }}"
|
|
||||||
length="{{ post.enclosure.size }}"
|
|
||||||
/>
|
|
||||||
{% endif -%}
|
|
||||||
</item>
|
</item>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</channel>
|
</channel>
|
||||||
|
|
Loading…
Reference in a new issue