diff --git a/nasg.py b/nasg.py
index 60dab97..4789d5a 100644
--- a/nasg.py
+++ b/nasg.py
@@ -398,7 +398,8 @@ class WebImage(object):
self.fpath = fpath
self.parent = parent
self.mtime = mtime(self.fpath)
- self.fname, self.fext = os.path.splitext(os.path.basename(fpath))
+ self.name = os.path.basename(self.fpath)
+ self.fname, self.fext = os.path.splitext(self.name)
self.resized_images = [
(k, self.Resized(self, k))
for k in settings.photo.get("sizes").keys()
@@ -434,7 +435,7 @@ class WebImage(object):
"height": self.displayed.height,
}
),
- "name": os.path.basename(self.fpath),
+ "name": self.name,
"encodingFormat": self.mime_type,
"contentSize": self.mime_size,
"width": self.linked.width,
@@ -1544,114 +1545,36 @@ class WebhookPHP(PHPFile):
class Category(dict):
def __init__(self, name=""):
self.name = name
- self.trange = "YYYY"
def __setitem__(self, key, value):
if key in self:
raise LookupError(
- "key '%s' already exists, colliding posts are: %s vs %s"
- % (key, self[key].fpath, value.fpath)
+ f"key '{key}' already exists, colliding posts are: {self[key].fpath} vs {value.fpath}"
)
dict.__setitem__(self, key, value)
- @property
- def sortedkeys(self):
- return list(sorted(self.keys(), reverse=True))
-
- @property
- def is_paginated(self):
- if self.name in settings.flat:
- return False
- return True
-
@property
def title(self):
if len(self.name):
- return "%s - %s" % (self.name, settings.site.name)
+ return f"{self.name} - {settings.site.name}"
else:
return settings.site.headline
@property
def url(self):
if len(self.name):
- url = "%s/%s/%s/" % (
- settings.site.url,
- settings.paths.category,
- self.name,
- )
+ url = f"{settings.site.url}/{settings.paths.category}/{self.name}/"
else:
- url = "%s/" % (settings.site.url)
+ url = f"{settings.site.url}/"
return url
@property
def feedurl(self):
- return "%s%s/" % (self.url, settings.paths.feed)
+ return f"{self.url}{settings.paths.feed}/"
@property
- def template(self):
- return "%s.j2.html" % (self.__class__.__name__)
-
- @property
- def dpath(self):
- if len(self.name):
- return os.path.join(
- settings.paths.build, settings.paths.category, self.name
- )
- else:
- return settings.paths.build
-
- @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)
- years.update({y: url})
- return years
-
- @property
- def mtime(self):
- if len(self.sortedkeys) > 0:
- return self[self.sortedkeys[0]].published.timestamp
- else:
- return 0
-
- def feedpath(self, fname):
- return os.path.join(self.dpath, settings.paths.feed, fname)
-
- def get_posts(self, start=0, end=-1):
- return [self[k].jsonld for k in self.sortedkeys[start:end]]
-
- def is_uptodate(self, fpath, dt):
- if settings.args.get("force"):
- return False
- if not os.path.exists(fpath):
- return False
- if mtime(fpath) >= dt.timestamp:
- return True
- return False
-
- def newest(self, start=0, end=-1):
- if start == end:
- end = -1
- s = sorted(
- [self[k].dt for k in self.sortedkeys[start:end]],
- reverse=True,
- )
- if len(s) > 0:
- return s[0] # Timestamp in seconds since epoch
- else:
- return 0
+ def sortedkeys(self):
+ return list(sorted(self.keys(), reverse=True))
@property
def ctmplvars(self):
@@ -1662,265 +1585,403 @@ class Category(dict):
"title": self.title,
}
- def tmplvars(self, posts=[], year=None):
- baseurl = self.url
- if year:
- baseurl = "%s%s/" % (baseurl, year)
- return {
- "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,
- },
- "posts": posts,
- "fnames": settings.filenames,
- }
+ @property
+ def renderdir(self):
+ b = settings.paths.build
+ if len(self.name):
+ b = os.path.join(b,settings.paths.category, self.name)
+ return b
- def indexfpath(self, subpath=None, fname=settings.filenames.html):
- if subpath:
- return os.path.join(self.dpath, subpath, fname)
+ @property
+ def newest_year(self):
+ return arrow.get(max(self.keys())).format("YYYY")
+
+ @cached_property
+ def years(self):
+ years = {}
+ for key in list(sorted(self.keys(), reverse=True)):
+ year = arrow.get(int(key)).format("YYYY")
+ if year in years:
+ continue
+ if year == self.newest_year:
+ url = f"{self.url}{settings.filenames.html}"
+ else:
+ url = f"{self.url}{year}/{settings.filenames.html}"
+ years.update({year: url})
+ return years
+
+ async def render(self):
+ await self.XMLFeed(self, "rss").render()
+ await self.XMLFeed(self, "atom").render()
+ await self.JSONFeed(self).render()
+ await self.Gopher(self).render()
+ if self.name in settings.flat:
+ await self.Flat(self).render()
else:
- return os.path.join(self.dpath, fname)
+ for year in sorted(self.years.keys()):
+ await self.Year(self, year).render()
- async def render_feed(self, xmlformat):
- if "json" == xmlformat:
- await self.render_json()
- return
+ class JSONFeed(object):
+ def __init__(self, parent):
+ self.parent = parent
- logger.info(
- 'rendering category "%s" %s feed', self.name, xmlformat
- )
+ @property
+ def mtime(self):
+ return max(list(sorted(self.parent.keys(), reverse=True))[0:settings.pagination])
- start = 0
- end = int(settings.pagination)
+ @property
+ def renderfile(self):
+ return os.path.join(self.parent.renderdir, settings.paths.feed, settings.filenames.json)
- fg = FeedGenerator()
- fg.id(self.feedurl)
- fg.title(self.title)
- fg.author(
- {
- "name": settings.author.name,
- "email": settings.author.email,
+ @property
+ def exists(self):
+ if settings.args.get("force"):
+ return False
+ if not os.path.exists(self.renderfile):
+ return False
+ if mtime(self.renderfile) >= self.mtime:
+ return True
+ return False
+
+ async def render(self):
+ if self.exists:
+ logger.debug("category %s is up to date", self.parent.name)
+ return
+
+ logger.info("rendering JSON feed for category %s", self.parent.name)
+
+ js = {
+ "version": "https://jsonfeed.org/version/1",
+ "title": self.parent.title,
+ "home_page_url": settings.site.url,
+ "feed_url": f"{self.parent.url}{settings.filenames.json}",
+ "author": {
+ "name": settings.author.name,
+ "url": settings.author.url,
+ "avatar": settings.author.image,
+ },
+ "items": [],
}
- )
- fg.logo("%s/favicon.png" % settings.site.url)
- fg.updated(arrow.get(self.mtime).to("utc").datetime)
- fg.description(settings.site.headline)
- for k in reversed(self.sortedkeys[start:end]):
- post = self[k]
- fe = fg.add_entry()
+ for key in list(sorted(self.parent.keys(), reverse=True))[0:settings.pagination]:
+ post = self.parent[key]
+ 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:
+ pjs.update(
+ {
+ "attachment": {
+ "url": post.photo.href,
+ "mime_type": post.photo.mime_type,
+ "size_in_bytes": f"{post.photo.mime_size}"
+ }
+ }
+ )
+ js["items"].append(pjs)
+ writepath(self.renderfile,json.dumps(js, indent=4, ensure_ascii=False))
- fe.id(post.url)
- fe.title(post.title)
- fe.author(
+ class XMLFeed(object):
+ def __init__(self, parent, feedformat="rss"):
+ self.parent = parent
+ self.feedformat = feedformat
+
+ @property
+ def mtime(self):
+ return max(list(sorted(self.parent.keys(), reverse=True))[0:settings.pagination])
+
+ @property
+ def renderfile(self):
+ if "rss" == self.feedformat:
+ fname = settings.filenames.rss
+ elif "atom" == self.feedformat:
+ fname = settings.filenames.atom
+ else:
+ fname = "index.xml"
+ return os.path.join(self.parent.renderdir, settings.paths.feed, fname)
+
+ @property
+ def exists(self):
+ if settings.args.get("force"):
+ return False
+ if not os.path.exists(self.renderfile):
+ return False
+ if mtime(self.renderfile) >= self.mtime:
+ return True
+ return False
+
+ async def render(self):
+ if self.exists:
+ logger.debug("category %s is up to date", self.parent.name)
+ return
+
+ logger.info("rendering %s feed for category %s", self.feedformat, self.parent.name)
+
+ fg = FeedGenerator()
+ fg.id(self.parent.feedurl)
+ fg.title(self.parent.title)
+ fg.logo(settings.site.image)
+ fg.updated(arrow.get(self.mtime).to("utc").datetime)
+ fg.description(settings.site.headline)
+ fg.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,
- ),
- }
- )
+ if self.feedformat == "rss":
+ fg.link(href=self.feedurl)
+ elif self.feedformat == "atom":
+ fg.link(href=self.feedurl, rel="self")
+ fg.link(href=settings.meta.get("hub"), rel="hub")
- fe.published(post.published.datetime)
- fe.updated(arrow.get(post.dt).datetime)
+ for key in list(sorted(self.parent.keys(), reverse=True))[0:settings.pagination]:
+ post = self.parent[key]
+ fe = fg.add_entry()
- fe.rights(
- "%s %s %s"
- % (
- post.licence.upper(),
- settings.author.name,
- post.published.format("YYYY"),
- )
- )
-
- if xmlformat == "rss":
- fe.link(href=post.url)
- fe.content(post.html_content, type="CDATA")
- if post.is_photo:
- fe.enclosure(
- post.photo.href,
- "%d" % post.photo.mime_size,
- post.photo.mime_type,
- )
- elif xmlformat == "atom":
- fe.link(
- href=post.url, rel="alternate", type="text/html"
- )
- fe.content(src=post.url, type="text/html")
- fe.summary(post.summary)
-
- if xmlformat == "rss":
- fg.link(href=self.feedurl)
- writepath(
- self.feedpath(settings.filenames.rss),
- fg.rss_str(pretty=True),
- )
- elif xmlformat == "atom":
- fg.link(href=self.feedurl, rel="self")
- fg.link(href=settings.meta.get("hub"), rel="hub")
- writepath(
- self.feedpath(settings.filenames.atom),
- fg.atom_str(pretty=True),
- )
-
- async def render_json(self):
- logger.info('rendering category "%s" JSON feed', self.name)
-
- js = {
- "version": "https://jsonfeed.org/version/1",
- "title": self.title,
- "home_page_url": settings.site.url,
- "feed_url": "%s%s" % (self.url, settings.filenames.json),
- "author": {
- "name": settings.author.name,
- "url": settings.author.url,
- "avatar": settings.author.image,
- },
- "items": [],
- }
-
- for k in reversed(
- self.sortedkeys[0 : int(settings.pagination)]
- ):
- 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:
- pjs.update(
+ fe.id(post.url)
+ fe.title(post.title)
+ fe.author(
{
- "attachment": {
- "url": post.photo.href,
- "mime_type": post.photo.mime_type,
- "size_in_bytes": "%d"
- % post.photo.mime_size,
- }
+ "name": settings.author.name,
+ "email": settings.author.email,
}
)
- js["items"].append(pjs)
- writepath(
- self.feedpath(settings.filenames.json),
- json.dumps(js, indent=4, ensure_ascii=False),
- )
-
- async def render_flat(self):
- logger.info("rendering flat archive for %s", self.name)
- r = J2.get_template(self.template).render(
- self.tmplvars(self.get_posts())
- )
- writepath(self.indexfpath(), r)
-
- async def render_gopher(self):
- lines = ["%s - %s" % (self.name, settings.site.name), "", ""]
- for post in self.get_posts():
- line = "0%s\t/%s/%s\t%s\t70" % (
- post.headline,
- post.name,
- settings.filenames.txt,
- settings.site.name,
- )
- lines.append(line)
- if len(post.description):
- lines.extend(
- str(PandocHTML2TXT(post.description)).split("\n")
+ fe.category(
+ {
+ "term": post.category,
+ "label": post.category,
+ "scheme": f"{settings.site.url}/{settings.paths.category}/{post.category}/",
+ }
)
- if isinstance(post["image"], list):
- for img in post["image"]:
+
+ fe.published(post.published.datetime)
+ fe.updated(arrow.get(post.dt).datetime)
+
+ fe.rights(
+ "%s %s %s"
+ % (
+ post.licence.upper(),
+ settings.author.name,
+ post.published.format("YYYY"),
+ )
+ )
+
+ if self.feedformat == "rss":
+ fe.link(href=post.url)
+ fe.content(post.html_content, type="CDATA")
+ if post.is_photo:
+ fe.enclosure(
+ post.photo.href,
+ "%d" % post.photo.mime_size,
+ post.photo.mime_type,
+ )
+ elif self.feedformat == "atom":
+ fe.link(
+ href=post.url, rel="alternate", type="text/html"
+ )
+ fe.content(src=post.url, type="text/html")
+ fe.summary(post.summary)
+
+ writepath(self.renderfile, fg.atom_str(pretty=True))
+
+
+ class Year(object):
+ def __init__(self, parent, year=str(arrow.utcnow().format("YYYY"))):
+ self.parent = parent
+ self.year = str(year)
+
+ @cached_property
+ def keys(self):
+ year = arrow.get(self.year, "YYYY").to("utc")
+ keys = []
+ for key in list(sorted(self.parent.keys(), reverse=True)):
+ ts = arrow.get(int(key))
+ if ts <= year.ceil("year") and ts >= year.floor("year"):
+ keys.append(int(key))
+ return keys
+
+ @property
+ def posttmplvars(self):
+ return [self.parent[key].jsonld for key in self.keys]
+
+ @property
+ def mtime(self):
+ return max(self.keys)
+
+ @property
+ def renderfile(self):
+ if self.year == self.parent.newest_year:
+ return os.path.join(self.parent.renderdir, settings.filenames.html)
+ else:
+ return os.path.join(self.parent.renderdir, self.year, settings.filenames.html)
+
+ @property
+ def baseurl(self):
+ if self.year == self.parent.newest_year:
+ return self.parent.url
+ else:
+ return f"{self.parent.url}{self.year}/"
+
+ @property
+ def template(self):
+ return "%s.j2.html" % (self.__class__.__name__)
+
+ @property
+ def exists(self):
+ if settings.args.get("force"):
+ return False
+ if not os.path.exists(self.renderfile):
+ return False
+ if mtime(self.renderfile) >= self.mtime:
+ return True
+ return False
+
+ @property
+ def tmplvars(self):
+ return {
+ "baseurl": self.baseurl,
+ "site": settings.site,
+ "menu": settings.menu,
+ "meta": settings.meta,
+ "fnames": settings.filenames,
+ "category": {
+ "name": self.parent.name,
+ "url": self.parent.url,
+ "feed": self.parent.feedurl,
+ "title": self.parent.title,
+ "paginated": True,
+ "years": self.parent.years,
+ "year": self.year
+ },
+ "posts": self.posttmplvars
+ }
+
+ async def render(self):
+ if self.exists:
+ logger.debug("category %s is up to date", self.parent.name)
+ return
+ logger.info("rendering year %s for category %s", self.year, self.parent.name)
+ r = J2.get_template(self.template).render(self.tmplvars)
+ writepath(self.renderfile, r)
+ del(r)
+
+ class Flat(object):
+ def __init__(self, parent):
+ self.parent = parent
+
+ @property
+ def posttmplvars(self):
+ return [
+ self.parent[key].jsonld
+ for key in list(sorted(self.parent.keys(), reverse=True))
+ ]
+
+ @property
+ def mtime(self):
+ return max(self.parent.keys())
+
+ @property
+ def renderfile(self):
+ return os.path.join(self.parent.renderdir, settings.filenames.html)
+
+ @property
+ def template(self):
+ return "%s.j2.html" % (self.__class__.__name__)
+
+ @property
+ def exists(self):
+ if settings.args.get("force"):
+ return False
+ if not os.path.exists(self.renderfile):
+ return False
+ if mtime(self.renderfile) >= self.mtime:
+ return True
+ return False
+
+ @property
+ def tmplvars(self):
+ return {
+ "baseurl": self.parent.url,
+ "site": settings.site,
+ "menu": settings.menu,
+ "meta": settings.meta,
+ "fnames": settings.filenames,
+ "category": {
+ "name": self.parent.name,
+ "url": self.parent.url,
+ "feed": self.parent.feedurl,
+ "title": self.parent.title,
+ },
+ "posts": self.posttmplvars
+ }
+
+ async def render(self):
+ if self.exists:
+ logger.debug("category %s is up to date", self.parent.name)
+ return
+ logger.info("rendering category %s", self.parent.name)
+ r = J2.get_template(self.template).render(self.tmplvars)
+ writepath(self.renderfile, r)
+ del(r)
+
+ class Gopher(object):
+ def __init__(self, parent):
+ self.parent = parent
+
+ @property
+ def mtime(self):
+ return max(self.parent.keys())
+
+ @property
+ def exists(self):
+ if settings.args.get("force"):
+ return False
+ if not os.path.exists(self.renderfile):
+ return False
+ if mtime(self.renderfile) >= self.mtime:
+ return True
+ return False
+
+ @property
+ def renderfile(self):
+ return os.path.join(self.parent.renderdir, settings.filenames.gopher)
+
+ async def render(self):
+ if self.exists:
+ logger.debug("category %s is up to date", self.parent.name)
+ return
+
+ lines = ["%s - %s" % (self.parent.name, settings.site.name), "", ""]
+ for post in [
+ self.parent[key]
+ for key in list(sorted(self.parent.keys(), reverse=True))
+ ]:
+ line = "0%s\t/%s/%s\t%s\t70" % (
+ post.title,
+ post.name,
+ settings.filenames.txt,
+ settings.site.name,
+ )
+ lines.append(line)
+ if len(post.txt_summary):
+ lines.extend(post.txt_summary.split("\n"))
+ for img in post.images.values():
line = "I%s\t/%s/%s\t%s\t70" % (
- img.headline,
+ img.title,
post.name,
img.name,
settings.site.name,
)
lines.append(line)
- lines.append("")
- writepath(
- self.indexfpath(fname=settings.filenames.gopher),
- "\r\n".join(lines),
- )
-
- async def render_archives(self):
- for year in self.years.keys():
- if year == self.newest_year:
- fpath = self.indexfpath()
- tyear = None
- else:
- fpath = self.indexfpath("%d" % (year))
- tyear = year
- y = arrow.get("%d" % year, self.trange).to("utc")
- tsmin = y.floor("year").timestamp
- tsmax = y.ceil("year").timestamp
- 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):
- logger.info("%s / %d is up to date", self.name, year)
- else:
- logger.info("updating %s / %d", self.name, year)
- logger.info("getting posts from %d to %d", start, end)
- r = J2.get_template(self.template).render(
- self.tmplvars(
- # I don't know why end needs the +1, but without that
- # some posts disappear
- # TODO figure this out...
- self.get_posts(start, end + 1),
- tyear,
- )
- )
- writepath(fpath, r)
-
- async def render_feeds(self):
- m = {
- "rss": self.feedpath(settings.filenames.rss),
- "atom": self.feedpath(settings.filenames.atom),
- "json": self.feedpath(settings.filenames.json),
- }
- for ft, path in m.items():
- if not self.is_uptodate(path, self.newest()):
- logger.info("%s outdated, generating new", ft)
- await self.render_feed(ft)
-
- async def render(self):
- await self.render_feeds()
- if not self.is_uptodate(self.indexfpath(), self.newest()):
- await self.render_gopher()
- if not self.is_paginated:
- if not self.is_uptodate(self.indexfpath(), self.newest()):
- await self.render_flat()
- else:
- await self.render_archives()
-
+ lines.append("")
+ writepath(self.renderfile, "\r\n".join(lines))
class Sitemap(dict):
@property
@@ -2273,7 +2334,7 @@ def make():
home.add(category, category.get(category.sortedkeys[0]))
queue.put(category.render())
- queue.put(frontposts.render_feeds())
+ #queue.put(frontposts.render_feeds())
queue.put(home.render())
# actually run all the render & copy tasks
queue.run()
diff --git a/templates/Flat.j2.html b/templates/Flat.j2.html
new file mode 100644
index 0000000..41367fd
--- /dev/null
+++ b/templates/Flat.j2.html
@@ -0,0 +1,33 @@
+{% extends "base.j2.html" %}
+{% block lang %}{% endblock %}
+
+{% block title %}{{ category.title }}{% endblock %}
+{% block meta %}
+
+
+
+
+
+{% endblock %}
+
+{% block content %}
+
+
+{% set year = [0] %}
+{% for post in posts %}
+ {% set _year = year.pop() %}
+ {% if _year != post.copyrightYear %}
+ {% if not loop.first %}
+
+ {% endif %}
+
+ {{ post.copyrightYear }}
+ {% endif %}
+ {% set _ = year.append(post.copyrightYear)%}
+ {% include 'meta-article.j2.html' %}
+ {% if loop.last %}
+
+ {% endif %}
+{% endfor %}
+
+{% endblock %}
diff --git a/templates/Category.j2.html b/templates/Year.j2.html
similarity index 100%
rename from templates/Category.j2.html
rename to templates/Year.j2.html