Compare commits

...

5 Commits

Author SHA1 Message Date
575bf336f9 squash merge semantic2 2024-07-06 14:22:21 -07:00
Tarry_Dan
bb0d20bd09 Updated to use semantic elements; replaced <center>; added a lang tag
* Semantic html is better for screen readers: main, header, footer, aside
* added a lang tag to html (so screenreaders know what language to read it in)
* center tag replaced with footer (center is depreciated)
2024-07-04 17:58:26 -07:00
Tarry_Dan
7fba705bc6 Post exports as an <article> not a <div>, for better accessibility 2024-07-04 17:56:28 -07:00
2d302bfec9 check-settings doesn't spit errors when invoked w/o args 2024-06-30 07:03:24 -07:00
f23a166c3d squash merge cfg-update 2024-06-29 20:54:55 -07:00
9 changed files with 344 additions and 137 deletions

View File

@ -5,11 +5,16 @@ Simple and stylish text-to-html microblog generator.
## Requirements ## Requirements
python3 dateutil toml curl pycurl make urllib The following python modules are used within the repository.
* `dateutil`, `toml`, `pycurl` are Python modules. toml tomlkit python_dateutil pycurl
* `make` (optional), method for invoking the script.
* `urllib` (optional), for uploading multiple files to neocities (`neouploader.py`). * `tomlkit` (optional), for maintaining the configuration file between updates (`check-settings.py`).
Some Gnu core utilities are expected to be present but can be substituted for other means.
* `make` (optional), to invoke the script using Makefiles
* `date` (optional), to generate timestamps when writing posts
## Usage ## Usage
@ -24,7 +29,7 @@ Using `make` is uptional; it does the following within a new directory:
cp example/timeline.css ./timeline.css cp example/timeline.css ./timeline.css
cp example/default.tpl ./template.tpl cp example/default.tpl ./template.tpl
cp example/demo.txt ./content.txt cp example/demo.txt ./content.txt
python microblog.py ./template.tpl ./content.txt > result.html python src/microblog.py ./template.tpl ./content.txt > result.html
This script generate a text file after operation. This script generate a text file after operation.
@ -64,11 +69,13 @@ Configuration options as understood by the script are tentative and may change i
>This script is throwing KeyError after I ran git pull >This script is throwing KeyError after I ran git pull
In most cases, this means I added new configuration options. You can resolve this error by copying and pasting the missing keys from `example/settings.toml` to `settings.toml`. In most cases, this means I added new configuration options. You can resolve this error by adding missing keys from `example/settings.toml` to `settings.toml`.
The following command shows differences between the files. The following command can check for missing keys and update if needed.
diff settings.toml example/settings.toml python src/check-settings.py
Missing keys if any are initialized to default values from `example/settings.toml`.
## Anything else ## Anything else

View File

@ -1,8 +1,12 @@
all: demo tpl css settings all: demo tpl css settings
python microblog.py ./template.tpl ./content.txt > result.html python src/microblog.py ./template.tpl ./content.txt > result.html
check:
python src/check-settings.py
# first time run only
tpl: tpl:
cp ./example/default.tpl ./template.tpl cp ./example/default.tpl ./template.tpl

View File

@ -1,42 +1,44 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Microblog</title> <title>Microblog</title>
<link href="./style.css" rel="stylesheet" type="text/css" media="all">
<!-- <link href="./style.css" rel="stylesheet" type="text/css" media="all"> -->
<link href="./timeline.css" rel="stylesheet" type="text/css" media="all"> <link href="./timeline.css" rel="stylesheet" type="text/css" media="all">
</head> </head>
<body> <body>
<div class="content">
<header>
<h1>A Microblog in Plain HTML</h1>
</header>
<h1>A Microblog in Plain HTML</h1> <aside class="column profile">
<figure>
<img src="images/avatar.jpg" alt="(Avatar)" class="avatar">
<span>Your Name Here</span>
</figure>
<p>
<a href="mailto:user@host.tld">user@host.tld</a>
</p>
<h2>About Me</h2>
<p>Your self-description here.</p>
<p>{postcount} total posts</p>
<h3>Tags</h3>
<nav>{tags}</nav>
<h3>Pages</h3>
<nav>{pages}</nav>
</aside>
<div class = "row"> <div class = "column"> <main class="timeline">
<div class="profile">
<img src="./images/avatar.jpg" alt="Avatar" class="avatar">
<span class="handle">Your Name Here</span>
<p><span class="email"><a href="mailto:user@host.tld">user@host.tld</a></span></p>
<div class="bio">Description
<h4>{postcount} total posts</h4>
<h3>Tags</h3>
<p>{tags}</p>
<h3>Pages</h3>
<p>{pages}</p>
</div>
</div>
</div>
<div class = "timeline">
{timeline} {timeline}
</div> </main>
</div>
<center> <footer>
<a href="https://notabug.org/likho/microblog.py">microblog.py</a> <a href="https://notabug.org/likho/microblog.py">microblog.py</a>
</center> </footer>
</div>
</body> </body>
</html> </html>

View File

@ -4,6 +4,11 @@ latestpages=["meta.json", "result.html"]
[page] [page]
postsperpage = 20 postsperpage = 20
relative_css=["./style.css", "./timeline.css"] relative_css=["./style.css", "./timeline.css"]
# this would be "latest.html" in earlier versions i.e
# user.domain.tld/microblog/tags/tagname/latest.html
# naming it as index enables paths like so
# user.domain.tld/microblog/tags/tagname
landing_page="index.html"
[post] [post]
accepted_images= ["jpg", "JPG", "png", "PNG"] accepted_images= ["jpg", "JPG", "png", "PNG"]
@ -11,19 +16,24 @@ accepted_images= ["jpg", "JPG", "png", "PNG"]
tag_paragraphs=true tag_paragraphs=true
# apply <p> tags even if a line contains the following # apply <p> tags even if a line contains the following
inline_tags = ["i", "em", "b", "strong","u", "s", "a", "span"] inline_tags = ["i", "em", "b", "strong","u", "s", "a", "span"]
# adds <br> or user defined string between each line date_format="%d %B %Y"
# line_separator="<br>"
format=""" format="""
<div class="postcell" id="{__num__}"> <article id="{__num__}">
<div class="timestamp">{__timestamp__} <h4>
<time>{__timestamp__}</time>
<a href=#{__num__}>(#{__num__})</a> <a href=#{__num__}>(#{__num__})</a>
</div> </h4>
<div class="message">{__msg__}</div> {__msg__}
{__btn__} {__btn__}
</div> </article>
""" """
[post.buttons] [post.buttons]
format="""
<a class="buttons" href="{__url__}">{__label__}</a>
"""
[post.buttons.links]
reply = "mailto:user@host.tld" reply = "mailto:user@host.tld"
test = "https://toml.io/en/v1.0.0#array-of-tables" test = "https://toml.io/en/v1.0.0#array-of-tables"
interact = "https://yoursite.tld/cgi?postid=" interact = "https://yoursite.tld/cgi?postid="
@ -44,20 +54,21 @@ short-bio= "Your self-description. Anything longer than 150 characters is trunca
[webring.following] [webring.following]
list= ["https://likho.neocities.org/microblog/meta.json"] list= ["https://likho.neocities.org/microblog/meta.json"]
date_format = "%Y %b %d"
format=""" format="""
<div class="fill"> <article>
<div class="postcell"> <figure>
<img src="{__avatar__}" alt="Avatar" class="avatar"> <img src="{__avatar__}" alt="Avatar" class="avatar">
<span class="wrapper""> <figcaption>
<div class="handle"> <ul>
<a href="{__url__}">{__handle__}</a> <li><a href="{__url__}" title="microblog of {__handle__}">{__handle__}</a></li>
</div> <li><time>Last Update: {__lastupdated__}</time></li>
<div class="last-updated">Last Update: {__lastupdated__}</div> <li>Posts: {__post_count__}</li>
<span class="post-count">Posts: {__post_count__}</span> </ul>
</span> </figcaption>
<p class="short-bio">{__shortbio__}</p> </figure>
</div> <p class="short-bio">{__shortbio__}</p>
</div> </article>
""" """
# internally link avatars - avoids hotlinks # internally link avatars - avoids hotlinks

View File

@ -1,61 +1,76 @@
body {
max-width:95%;
margin:auto;
}
@media only screen and (min-width: 768px) { @media only screen and (min-width: 768px) {
.column { .column {
float: left; float: left;
width: 32%; width: 30%;
} }
.timeline { .timeline {
float: right; float: right;
width: 67%; width: 67%;
} }
} }
.postcell {
/* POSTING */
/* .postcell */
.timeline article {
border: 1px solid red; border: 1px solid red;
text-align: left; text-align: left;
margin: 0.25em 0 margin: 0.25em 0
} }
.message { .timeline article h4 {
text-align: right;
margin: 0.5em
}
.timeline article h4 ~ * {
margin: 1em 1em 1em 3em; margin: 1em 1em 1em 3em;
white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
} }
.buttons { .buttons {
margin-left: 1em; margin-left: 1em;
margin-bottom:0.5em; margin-bottom:0.5em;
} }
.timestamp {
text-align: right;
margin: 0.5em
}
.hashtag { .hashtag {
color: green; color: green;
font-weight: bold; font-weight: bold;
} }
/* PROFILE */
.column figure {
margin-left: 3%;
}
.avatar { .avatar {
vertical-align: middle; vertical-align: middle;
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.column .profile { .column {
vertical-align: middle; border:1px solid blue;
padding-left: 10px; padding-left: 10px;
padding:1%; padding:1%;
border:1px solid blue;
} }
.column .profile .handle{ .profile .handle{
font-size: 1.1em; font-size: 1.1em;
font-weight: bold; font-weight: bold;
} }
.column .profile .email{ .profile .email{
font-size: 0.8em; font-size: 0.8em;
text-align:left; text-align:left;
text-decoration:none; text-decoration:none;
} }
.column .profile .bio { .profile .bio {
font-size: 0.9em; font-size: 0.9em;
vertical-align: middle; vertical-align: middle;
margin: 1em margin: 1em
} }
/* IMAGES */
.gallery { .gallery {
margin:auto; margin:auto;
display: flex; display: flex;
@ -74,31 +89,41 @@
border: 1px solid #777; border: 1px solid #777;
filter: invert(100%); filter: invert(100%);
} }
.postcell .avatar {
/* WEBRING */
.timeline article figure img {
margin-left:3%; margin-left:3%;
margin-top:2%; margin-top:2%;
height: 4em; height: 4em;
width:auto; width:auto;
vertical-align:top; vertical-align:top;
} }
.postcell .wrapper { .timeline article figure {
margin-top:2%; display:flex;
margin-left:0;
}
.timeline article figcaption {
margin-left: 3%;
display: inline-block; display: inline-block;
font-size: 0.85em;
} }
.postcell .wrapper .last-updated, .timeline article figcaption ul {
.postcell .wrapper .post-count { list-style-type:none;
font-size: 1em; padding-left:0;
color:grey;
} }
.postcell .short-bio{ .timeline article figcaption p {
margin-top:0;
margin-bottom:0;
}
.timeline article .short-bio{
padding-left: 3%; padding-left: 3%;
padding-right: 2%; padding-right: 2%;
font-style: italic; font-style: italic;
word-wrap: break-word; word-wrap: break-word;
} }
/* Clear floats after the columns */
.row:after { footer {
content: ""; text-align:center;
display: table;
clear: both;
} }

13
requirements.txt Normal file
View File

@ -0,0 +1,13 @@
pycurl
# ==7.45.3
# pycurl==7.45.2
python_dateutil
# ==2.9.0.post0
# python_dateutil==2.8.2
toml
# ==0.10.2
tomlkit
# ==0.12.5

136
src/check-settings.py Normal file
View File

@ -0,0 +1,136 @@
import os, argparse
from tomlkit import loads
from tomlkit import dump
def nest_dictionary(d, keys, val):
for key in keys:
d = d.setdefault(key, val)
return d
class MicroblogConfig:
def __init__(self, given_config):
self.is_outdated = False
self.updated = given_config
def compare(self, sref, suser, keylist=[]):
# subtable of ref, subtable of user
updated = self.updated
# nnavigate to table
if keylist != []:
for key in keylist:
sref = sref[key]
for key in keylist:
suser = suser[key]
for key in keylist:
updated = updated[key]
for key in sref:
if key not in suser:
self.is_outdated = True
updated[key] =sref[key]
print("noticed '", key, "' missing from ", keylist)
nest_dictionary(self.updated, keylist, updated)
return
def check(self, r, u): # (reference, user)
for key in r:
if key == "latestpages": continue;
# post and webring have subtables
# webring.profile
# webring.following
# webring.following.internal-avatars
# post.gallery
# post.buttons
try:
self.compare(r, u, [key])
except KeyError:
u[key] = dict()
print("missing top-level table '", key, '\'')
self.compare(r, u, [key])
if key == "webring":
self.compare(r, u, ["webring", "profile"])
self.compare(r, u, ["webring", "following"])
self.compare(r, u, ["webring", "following", "internal-avatars"])
if key == "post":
self.compare(r, u, ["post", "gallery"])
self.compare(r, u, ["post", "buttons"])
pass
def load_files(user_conf_file):
script_dir = os.path.dirname(
os.path.abspath(__file__))
parent_dir = os.path.abspath(
os.path.join(script_dir, os.pardir))
target_folder = "example"
example = os.path.abspath(
os.path.join(parent_dir, target_folder))
ref_file = "%s/%s" % (example, "/settings.toml")
if not os.path.exists(ref_file):
return
ref_conf = dict()
with open(ref_file, 'r') as f:
ref_conf = loads(f.read())
user_conf = dict()
with open(user_conf_file, 'r') as f:
user_conf = loads(f.read())
return ref_conf, user_conf
def multi_prompt(message):
try:
while True:
user_input = int(input(f"{message}").lower())
if user_input < 3:
return user_input
else:
return 0
except KeyboardInterrupt:
print()
except ValueError:
pass
return 0
def get_args():
p = argparse.ArgumentParser()
p.add_argument("--no-prompt", action="store_true", \
help="does not ask what to do if missing keys are detected")
p.add_argument("-c", "--check", type=str,\
help="sets/changes the file to be checked (default: settings.toml)")
args = p.parse_args()
if args.no_prompt:
print("'--no-prompt' set")
if args.check:
print("--check set", args.check)
else:
args.check = "settings.toml"
return args.no_prompt, args.check
def main(is_no_prompt, user_conf_file="settings.toml"):
print("checking ", user_conf_file)
reference, user_edited = load_files(user_conf_file)
mcfg = MicroblogConfig(user_edited)
mcfg.check(reference, user_edited)
if mcfg.is_outdated == False:
print("Your settings file is OK!")
return
message = """
Your settings file is outdated.
Do you want to...
\t 1. save new settings to new file
\t 2. update/overwrite existing settings
\t *. do nothing
"""
response = 0 if is_no_prompt else multi_prompt(message)
out_file = str()
if response == 0:
return
elif response == 1:
out_file = "new.toml"
elif response == 2:
out_file = user_conf_file
with open(out_file, 'w') as f:
dump(mcfg.updated, f)
print("Wrote updated config to ", out_file)
pass
if __name__ == "__main__":
main(*get_args())

View File

@ -3,17 +3,15 @@ import sys, os, traceback
import dateutil.parser import dateutil.parser
from time import strftime, localtime from time import strftime, localtime
# returns html-formatted string def make_buttons(btn_conf, msg_id):
def make_buttons(btn_dict, msg_id): fmt = btn_conf["format"]
buttons = "<div class=\"buttons\">" buttons = str()
fmt = "<a href=\"%s\">[%s]</a>" for key in btn_conf["links"]:
for key in btn_dict: url = btn_conf["links"][key]
url = btn_dict[key]
if url[-1] == '=': if url[-1] == '=':
# then interpret it as a query string
url += str(msg_id) url += str(msg_id)
buttons += fmt % (url,key) buttons += fmt.format(
buttons += "</div>" __url__=url, __label__ = key)
return buttons return buttons
# apply div classes for use with .css # apply div classes for use with .css
@ -132,8 +130,6 @@ def markup(message, config):
ignore = config["inline_tags"] ignore = config["inline_tags"]
parser = My_Html_Parser(ignore) parser = My_Html_Parser(ignore)
sep = "" sep = ""
if "line_separator" in config:
sep = config["line_separator"]
for line in message: for line in message:
images = [] # list of integers images = [] # list of integers
parser.feed(line) parser.feed(line)
@ -172,9 +168,11 @@ class Post:
return int(t.timestamp()) return int(t.timestamp())
# format used for display # format used for display
def get_short_time(self): def get_short_time(self, form):
if form == "":
form = "%y %b %d"
t = dateutil.parser.parse(self.timestamp) t = dateutil.parser.parse(self.timestamp)
return t.strftime("%y %b %d") return t.strftime(form)
def parse_txt(filename): def parse_txt(filename):
content = [] content = []
@ -203,7 +201,7 @@ def parse_txt(filename):
state = 0 state = 0
return posts return posts
def get_posts(posts, config): def get_posts(posts, config, newest = None):
taginfos = [] taginfos = []
tagcloud = dict() # (tag, count) tagcloud = dict() # (tag, count)
tagged = dict() # (tag, index of message) tagged = dict() # (tag, index of message)
@ -211,21 +209,29 @@ def get_posts(posts, config):
count = total count = total
index = count # - 1 index = count # - 1
timeline = [] timeline = []
btns = None df = ""
subset = []
if "date_format" in config:
df = config["date_format"]
for post in posts: for post in posts:
markedup, tags = markup(post.message, config) markedup, tags = markup(post.message, config)
count -= 1 count -= 1
index -= 1 index -= 1
timeline.append( timeline.append(
make_post(count, post.get_short_time(), config, markedup) make_post(count, post.get_short_time(df), config, markedup)
) )
for tag in tags: for tag in tags:
if tagcloud.get(tag) == None: if tagcloud.get(tag) == None:
tagcloud[tag] = 0 tagcloud[tag] = 0
tagcloud[tag] += 1 tagcloud[tag] += 1
if tagged.get(tag) == None: if newest is not None and (total - (1 + count)) < newest:
tagged[tag] = [] subset.append(tag)
tagged[tag].append(index) if newest is None \
or newest is not None and tag in subset:
if tagged.get(tag) == None:
tagged[tag] = []
tagged[tag].append(index)
# print(tagged, file=sys.stderr)
return timeline, tagcloud, tagged return timeline, tagcloud, tagged
def make_tagcloud(d, rell): def make_tagcloud(d, rell):
@ -282,16 +288,14 @@ class Paginator:
def paginate(self, template, tagcloud, timeline, is_tagline=False): def paginate(self, template, tagcloud, timeline, is_tagline=False):
outfile = "%s/%s" % (self.SUBDIR, self.FILENAME) outfile = "%s/%s" % (self.SUBDIR, self.FILENAME)
timeline.reverse() # reorder from oldest to newest l = len(timeline)
start = 0 for i in range(0, self.TOTAL_PAGES):
for i in range(start, self.TOTAL_PAGES):
fn = outfile % i fn = outfile % i
with open(fn, 'w') as f: with open(fn, 'w') as f:
self.written.append(fn) self.written.append(fn)
prev = self.PPP * i prev = l - (self.PPP * i)
curr = self.PPP * (i+1) curr = l - self.PPP * (i+1)
sliced = timeline[prev:curr] sliced = timeline[curr:prev]
sliced.reverse()
f.write(self.singlepage(template, tagcloud, sliced, i, ".")) f.write(self.singlepage(template, tagcloud, sliced, i, "."))
return return
@ -315,19 +319,20 @@ if __name__ == "__main__":
p = argparse.ArgumentParser() p = argparse.ArgumentParser()
p.add_argument("template", help="an html template file") p.add_argument("template", help="an html template file")
p.add_argument("content", help="text file for microblog content") p.add_argument("content", help="text file for microblog content")
p.add_argument("--sort", \ p.add_argument("--sort", action="store_true", \
help="sorts content from oldest to newest" help="sorts content from oldest to newest"
" (this is a separate operation from page generation)", \ " (this is a separate operation from page generation)")
action="store_true") p.add_argument("--skip-fetch", action="store_true", \
p.add_argument("--skip-fetch", \
help="skips fetching profile data from remote sources;" help="skips fetching profile data from remote sources;"
" has no effect if webring is not enabled",\ " has no effect if webring is not enabled")
action="store_true") p.add_argument("--new-posts", type=int, nargs='?',
help="generate pages based only on new entries; " \
"if I wrote 5 new posts then --new-posts=5'")
args = p.parse_args() args = p.parse_args()
if args.sort: if args.sort:
sort(args.content) sort(args.content)
exit() exit()
return args.template, args.content, args.skip_fetch return args.template, args.content, args.skip_fetch, args.new_posts
# assume relative path # assume relative path
def demote_css(template, css_list, level=1): def demote_css(template, css_list, level=1):
@ -342,7 +347,7 @@ if __name__ == "__main__":
tpl = tpl.replace(css, ("%s%s" % (prepend, css) )) tpl = tpl.replace(css, ("%s%s" % (prepend, css) ))
return tpl return tpl
def writepage(template, timeline, tagcloud, config, subdir = None): def writepage(template, timeline, tagcloud, config, subdir = None, paginate = True):
count = len(timeline) count = len(timeline)
html = "" html = ""
with open(template,'r') as f: with open(template,'r') as f:
@ -356,10 +361,11 @@ if __name__ == "__main__":
except Exception as e: except Exception as e:
print("error: ",e, ("(number of posts = %i)" % count), file=sys.stderr) print("error: ",e, ("(number of posts = %i)" % count), file=sys.stderr)
exit() exit()
index = config["landing_page"]
latest = timeline[:pagectrl.PPP] latest = timeline[:pagectrl.PPP]
link_from_top = "./tags/%s/latest.html" link_from_top = "./tags/%s/" + index
link_from_subdir = "../tags/%s/latest.html" link_from_subdir = "../tags/%s/" + index
link_from_tagdir = "../%s/latest.html" link_from_tagdir = "../%s/" + index
cloud = "" cloud = ""
level = 1 level = 1
is_tagline = False is_tagline = False
@ -375,20 +381,20 @@ if __name__ == "__main__":
else: else:
cloud = make_tagcloud(tagcloud, link_from_subdir) cloud = make_tagcloud(tagcloud, link_from_subdir)
demoted = demote_css(html, config["relative_css"], level) demoted = demote_css(html, config["relative_css"], level)
filename = "%s/latest.html" % subdir filename = "%s/%s" % (subdir, index)
with open(filename, 'w') as f: # landing page for tag with open(filename, 'w') as f: # landing page for tag
pagectrl.written.append(filename) pagectrl.written.append(filename)
page = pagectrl.singlepage(demoted, cloud, latest, p=".") page = pagectrl.singlepage(demoted, cloud, latest, p=".")
f.write(page) f.write(page)
pagectrl.paginate( if paginate:
demote_css(html, config["relative_css"], level), pagectrl.paginate(
cloud, timeline, is_tagline) demote_css(html, config["relative_css"], level),
cloud, timeline, is_tagline)
return pagectrl.written return pagectrl.written
import toml import toml
def load_settings(): def load_settings(filename = "settings.toml"):
s = dict() s = dict()
filename = "settings.toml"
if os.path.exists(filename): if os.path.exists(filename):
with open(filename, 'r') as f: with open(filename, 'r') as f:
s = toml.loads(f.read()) s = toml.loads(f.read())
@ -457,7 +463,7 @@ if __name__ == "__main__":
print(e) print(e)
return json_objs return json_objs
def render(profiles, template): def render(profiles, template, date_format):
rendered = [] rendered = []
SHORT_BIO_LIMIT = 150 SHORT_BIO_LIMIT = 150
for profile in profiles: for profile in profiles:
@ -478,7 +484,7 @@ if __name__ == "__main__":
__post_count__ = post_count, __post_count__ = post_count,
__shortbio__= escape(self_desc), __shortbio__= escape(self_desc),
__lastupdated__= strftime( __lastupdated__= strftime(
"%Y %b %d", localtime(epoch_timestamp)) ) date_format, localtime(epoch_timestamp)) )
rendered.append(foo) rendered.append(foo)
except KeyError as e: except KeyError as e:
print("remote profile is missing key: ", e, file=sys.stderr) print("remote profile is missing key: ", e, file=sys.stderr)
@ -519,10 +525,9 @@ if __name__ == "__main__":
try: try:
list_of_json_objs.sort(key=lambda e: e["last-updated"], reverse=True) list_of_json_objs.sort(key=lambda e: e["last-updated"], reverse=True)
except KeyError: pass except KeyError: pass
return render(list_of_json_objs, f_cfg["format"]) return render(list_of_json_objs, f_cfg["format"], f_cfg["date_format"])
def main(): def main(tpl, content, skip_fetch, new_posts):
tpl, content, skip_fetch = get_args()
cfg = load_settings() cfg = load_settings()
if cfg == None: if cfg == None:
print("exit: no settings.toml found.", file=sys.stderr) print("exit: no settings.toml found.", file=sys.stderr)
@ -534,25 +539,31 @@ if __name__ == "__main__":
print("exit: table 'page' absent in settings.toml", file=sys.stderr) print("exit: table 'page' absent in settings.toml", file=sys.stderr)
return return
p = parse_txt(content) p = parse_txt(content)
tl, tc, tg = get_posts(p, cfg["post"]) tl, tc, tg = get_posts(p, cfg["post"], new_posts)
if tl == []: if tl == []:
return return
# main timeline # main timeline
updated = [] updated = []
updated += writepage(tpl, tl, tc, cfg["page"]) updated += writepage(tpl, tl, tc, cfg["page"],
paginate=True if new_posts is None else False)
# timeline per tag # timeline per tag
if tc != dict() and tg != dict(): if tc != dict() and tg != dict():
if not os.path.exists("tags"): if not os.path.exists("tags"):
os.mkdir("tags") os.mkdir("tags")
tl.reverse()
for key in tg.keys(): for key in tg.keys():
tagline = [] tagline = []
for index in tg[key]: for index in tg[key]:
tagline.append(tl[index]) tagline.append(tl[index])
# [1:] means to omit hashtag from dir name # [1:] means to omit hashtag from dir name
wp = True # will paginate
if new_posts is not None \
and len(tagline) > cfg["page"]["postsperpage"]:
wp = False
updated += writepage( updated += writepage(
tpl, tagline, tc, cfg["page"], \ tpl, tagline, tc, cfg["page"], \
subdir="tags/%s" % key[1:] \ subdir="tags/%s" % key[1:], \
) paginate=wp)
if "webring" in cfg: if "webring" in cfg:
if cfg["webring"]["enabled"] == True: if cfg["webring"]["enabled"] == True:
export_profile( export_profile(
@ -565,13 +576,11 @@ if __name__ == "__main__":
with open("updatedfiles.txt", 'w') as f: with open("updatedfiles.txt", 'w') as f:
for filename in updated: for filename in updated:
print(filename, file=f) # sys.stderr) print(filename, file=f) # sys.stderr)
if "latestpage" in cfg:
print(cfg["latestpage"], file=f)
if "latestpages" in cfg: if "latestpages" in cfg:
for page in cfg["latestpages"]: for page in cfg["latestpages"]:
print(page, file=f) print(page, file=f)
try: try:
main() main(*get_args())
except KeyError as e: except KeyError as e:
traceback.print_exc() traceback.print_exc()
print("\n\tA key may be missing from your settings file.", file=sys.stderr) print("\n\tA key may be missing from your settings file.", file=sys.stderr)