|
@@ -1,5 +1,6 @@
|
|
|
|
|
|
-import sys, os, subprocess
|
|
|
+import sys, os, traceback
|
|
|
+import dateutil.parser
|
|
|
|
|
|
# returns html-formatted string
|
|
|
def make_buttons(btn_dict, msg_id):
|
|
@@ -16,34 +17,41 @@ def make_buttons(btn_dict, msg_id):
|
|
|
|
|
|
# apply div classes for use with .css
|
|
|
def make_post(num, timestamp, conf, msg):
|
|
|
- fmt = '''
|
|
|
-<div class=\"postcell\" id=\"%i\">
|
|
|
- <div class=\"timestamp\">%s<a href=#%i>(#%i)</a></div>
|
|
|
- <div class=\"message\">%s</div>
|
|
|
-'''
|
|
|
+ fmt = conf["format"]
|
|
|
if "buttons" in conf:
|
|
|
b = make_buttons(conf["buttons"], num)
|
|
|
- fmt += b
|
|
|
- fmt += "</div>"
|
|
|
- return fmt % (num, timestamp, num, num, msg)
|
|
|
+ else:
|
|
|
+ b = ""
|
|
|
+ return fmt.format(
|
|
|
+ __timestamp__=timestamp, __num__=num, __msg__=msg, __btn__=b)
|
|
|
|
|
|
-def make_gallery(i, w):
|
|
|
+def make_gallery(indices, w, conf=None):
|
|
|
tag = []
|
|
|
- if i == []:
|
|
|
+ if indices == []:
|
|
|
return tag
|
|
|
- tag.append("<div class=\"gallery\">")
|
|
|
- for index in reversed(i):
|
|
|
- image = w.pop(index)
|
|
|
- template = '''
|
|
|
+ template = '''
|
|
|
<div class=\"panel\">
|
|
|
<a href=\"%s\"><img src=\"%s\" class=\"embed\"></a>
|
|
|
</div>
|
|
|
'''
|
|
|
+ tag.append("<div class=\"gallery\">")
|
|
|
+ for index in reversed(indices):
|
|
|
+ image = w.pop(index)
|
|
|
+ is_path = image[0] == '.' or image[0] == '/'
|
|
|
+ if conf and not is_path:
|
|
|
+ thumb = "%s/%s" % (conf["thumbnail_dir"], image)
|
|
|
+ full = "%s/%s" % (conf["fullsize_dir"], image)
|
|
|
+ tag.append(template % (full,thumb))
|
|
|
+ continue
|
|
|
+ elif not conf and not is_path:
|
|
|
+ msg = ("Warning: no path defined for image %s!" % image)
|
|
|
+ print(msg,file=sys.stderr)
|
|
|
+ else:
|
|
|
+ pass
|
|
|
tag.append(template % (image, image))
|
|
|
tag.append("</div>")
|
|
|
return tag
|
|
|
|
|
|
-
|
|
|
def markup(message, config):
|
|
|
def is_image(s, image_formats):
|
|
|
l = s.rsplit('.', maxsplit=1)
|
|
@@ -96,7 +104,8 @@ def markup(message, config):
|
|
|
images.append(i)
|
|
|
if len(images) > 0:
|
|
|
# function invokes pop() which modifies list 'words'
|
|
|
- gallery = make_gallery(images, words)
|
|
|
+ gc = config["gallery"] if "gallery" in config else None
|
|
|
+ gallery = make_gallery(images, words, gc)
|
|
|
if ptags and len(words) > 0:
|
|
|
words.insert(0,"<p>")
|
|
|
words.append("</p>")
|
|
@@ -109,42 +118,49 @@ def markup(message, config):
|
|
|
|
|
|
# apply basic HTML formatting - only div class here is gallery
|
|
|
from html import escape
|
|
|
-# iterate through posts and get information about them
|
|
|
-def get_posts(filename, config):
|
|
|
- class Post:
|
|
|
- def __init__(self, ts, msg):
|
|
|
- self.timestamp = ts # string
|
|
|
- self.message = msg # list
|
|
|
+class Post:
|
|
|
+ def __init__(self, ts, msg):
|
|
|
+ self.timestamp = ts.strip() # string
|
|
|
+ self.message = msg # list
|
|
|
|
|
|
- def parse_txt(filename):
|
|
|
- content = []
|
|
|
- with open(filename, 'r') as f:
|
|
|
- content = f.readlines()
|
|
|
- posts = [] # list of posts - same order as file
|
|
|
- message = [] # list of lines
|
|
|
- # {-1 = init;; 0 = timestamp is next, 1 = message is next}
|
|
|
- state = -1
|
|
|
- timestamp = ""
|
|
|
- for line in content:
|
|
|
- if state == -1:
|
|
|
- state = 0
|
|
|
- continue
|
|
|
- elif state == 0:
|
|
|
- cmd = ['date', '-d', line, '+%y %b %d']
|
|
|
- result = subprocess.run(cmd, stdout=subprocess.PIPE)
|
|
|
- timestamp = result.stdout.decode('utf-8')
|
|
|
- state = 1
|
|
|
- elif state == 1:
|
|
|
- if len(line) > 1:
|
|
|
- message.append(line)
|
|
|
- else:
|
|
|
- p = Post(timestamp, message)
|
|
|
- posts.append(p)
|
|
|
- # reset
|
|
|
- message = []
|
|
|
- state = 0
|
|
|
- return posts
|
|
|
+ # format used for sorting
|
|
|
+ def get_epoch_time(self):
|
|
|
+ t = dateutil.parser.parse(self.timestamp)
|
|
|
+ return int(t.timestamp())
|
|
|
|
|
|
+ # format used for display
|
|
|
+ def get_short_time(self):
|
|
|
+ t = dateutil.parser.parse(self.timestamp)
|
|
|
+ return t.strftime("%y %b %d")
|
|
|
+
|
|
|
+def parse_txt(filename):
|
|
|
+ content = []
|
|
|
+ with open(filename, 'r') as f:
|
|
|
+ content = f.readlines()
|
|
|
+ posts = [] # list of posts - same order as file
|
|
|
+ message = [] # list of lines
|
|
|
+ # {-1 = init;; 0 = timestamp is next, 1 = message is next}
|
|
|
+ state = -1
|
|
|
+ timestamp = ""
|
|
|
+ for line in content:
|
|
|
+ if state == -1:
|
|
|
+ state = 0
|
|
|
+ continue
|
|
|
+ elif state == 0:
|
|
|
+ timestamp = line
|
|
|
+ state = 1
|
|
|
+ elif state == 1:
|
|
|
+ if len(line) > 1:
|
|
|
+ message.append(line)
|
|
|
+ else:
|
|
|
+ p = Post(timestamp, message)
|
|
|
+ posts.append(p)
|
|
|
+ # reset
|
|
|
+ message = []
|
|
|
+ state = 0
|
|
|
+ return posts
|
|
|
+
|
|
|
+def get_posts(filename, config):
|
|
|
posts = parse_txt(filename)
|
|
|
taginfos = []
|
|
|
tagcloud = dict() # (tag, count)
|
|
@@ -159,7 +175,7 @@ def get_posts(filename, config):
|
|
|
count -= 1
|
|
|
index -= 1
|
|
|
timeline.append(
|
|
|
- make_post(count, post.timestamp, config, markedup)
|
|
|
+ make_post(count, post.get_short_time(), config, markedup)
|
|
|
)
|
|
|
for tag in tags:
|
|
|
if tagcloud.get(tag) == None:
|
|
@@ -183,22 +199,18 @@ def make_tagcloud(d, rell):
|
|
|
return output
|
|
|
|
|
|
class Paginator:
|
|
|
- def __init__(self, x, ppp, loc="pages"):
|
|
|
- if x <= 0:
|
|
|
- print("Error: No posts (x=%i" % x, file=sys.stderr)
|
|
|
+ def __init__(self, post_count, ppp, loc=None):
|
|
|
+ if post_count <= 0:
|
|
|
raise Exception
|
|
|
- self.TOTAL_POSTS = x
|
|
|
+ if not loc:
|
|
|
+ loc = "pages"
|
|
|
+ if loc and not os.path.exists(loc):
|
|
|
+ os.mkdir(loc)
|
|
|
+ self.TOTAL_POSTS = post_count
|
|
|
self.PPP = ppp
|
|
|
- self.TOTAL_PAGES = int(x/self.PPP)
|
|
|
+ self.TOTAL_PAGES = int(post_count/self.PPP)
|
|
|
self.SUBDIR = loc
|
|
|
- if not os.path.exists(loc):
|
|
|
- os.makedirs(loc)
|
|
|
self.FILENAME = "%i.html"
|
|
|
- try:
|
|
|
- with open ("lastfullpage.txt", 'r') as f:
|
|
|
- self.lastfullpage = int(f.read())
|
|
|
- except : #possible exceptions FileNotFoundError, ValueError
|
|
|
- self.lastfullpage = 0
|
|
|
self.written = []
|
|
|
|
|
|
def toc(self, current_page=None, path=None): #style 1
|
|
@@ -226,14 +238,10 @@ class Paginator:
|
|
|
postcount=self.TOTAL_POSTS, tags=tc, pages=toc, timeline=tl
|
|
|
)
|
|
|
|
|
|
- def paginate(self, template, tagcloud, timeline, override=False):
|
|
|
- # override boolean currently reprsents whether or not
|
|
|
- # it is a main timeline or a tagline being paginated
|
|
|
- ## effort-saving feature does not work for taglines currently
|
|
|
+ def paginate(self, template, tagcloud, timeline, is_tagline=False):
|
|
|
outfile = "%s/%s" % (self.SUBDIR, self.FILENAME)
|
|
|
- #print(outfile, file=sys.stderr)
|
|
|
timeline.reverse() # reorder from oldest to newest
|
|
|
- start = 0 if override else self.lastfullpage
|
|
|
+ start = 0
|
|
|
for i in range(start, self.TOTAL_PAGES):
|
|
|
fn = outfile % i
|
|
|
with open(fn, 'w') as f:
|
|
@@ -243,27 +251,40 @@ class Paginator:
|
|
|
sliced = timeline[prev:curr]
|
|
|
sliced.reverse()
|
|
|
f.write(self.singlepage(template, tagcloud, sliced, i, "."))
|
|
|
- if not override:
|
|
|
- with open("lastfullpage.txt", 'w') as f:
|
|
|
- f.write(str(self.TOTAL_PAGES))
|
|
|
return
|
|
|
|
|
|
+import argparse
|
|
|
if __name__ == "__main__":
|
|
|
+ def sort(filename):
|
|
|
+ def export(new_content, new_filename):
|
|
|
+ with open(new_filename, 'w') as f:
|
|
|
+ print(file=f)
|
|
|
+ for post in new_content:
|
|
|
+ print(post.timestamp, file=f)
|
|
|
+ print("".join(post.message), file=f)
|
|
|
+ return
|
|
|
+ posts = parse_txt(filename)
|
|
|
+ posts.sort(key=lambda e: e.get_epoch_time())
|
|
|
+ outfile = ("%s.sorted" % filename)
|
|
|
+ print("Sorted text written to ", outfile)
|
|
|
+ export(reversed(posts), outfile)
|
|
|
+
|
|
|
def get_args():
|
|
|
- argc = len(sys.argv)
|
|
|
- if argc < 3:
|
|
|
- msg = '''This is microblog.py. (%s/3 arguments given)
|
|
|
-\tpython microblog.py [template] [content]
|
|
|
-'''
|
|
|
- print(msg % argc, file=sys.stderr)
|
|
|
+ p = argparse.ArgumentParser()
|
|
|
+ p.add_argument("template", help="an html template file")
|
|
|
+ p.add_argument("content", help="text file for microblog content")
|
|
|
+ p.add_argument("--sort", \
|
|
|
+ help="sorts content from oldest to newest"
|
|
|
+ " (this is a separate operation from page generation)", \
|
|
|
+ action="store_true")
|
|
|
+ args = p.parse_args()
|
|
|
+ if args.sort:
|
|
|
+ sort(args.content)
|
|
|
exit()
|
|
|
- # script = argv[0]
|
|
|
- template = sys.argv[1]
|
|
|
- content = sys.argv[2]
|
|
|
- return template, content
|
|
|
+ return args.template, args.content
|
|
|
|
|
|
# assume relative path
|
|
|
- def demote_css(template, cssl, level=1):
|
|
|
+ def demote_css(template, css_list, level=1):
|
|
|
prepend = ""
|
|
|
if level == 1:
|
|
|
prepend = '.'
|
|
@@ -271,7 +292,7 @@ if __name__ == "__main__":
|
|
|
for i in range(level):
|
|
|
prepend = ("../%s" % prepend)
|
|
|
tpl = template
|
|
|
- for css in cssl:
|
|
|
+ for css in css_list:
|
|
|
tpl = tpl.replace(css, ("%s%s" % (prepend, css) ))
|
|
|
return tpl
|
|
|
|
|
@@ -281,30 +302,28 @@ if __name__ == "__main__":
|
|
|
html = ""
|
|
|
with open(template,'r') as f:
|
|
|
html = f.read()
|
|
|
- count = len(timeline)
|
|
|
try:
|
|
|
- p = config["postsperpage"]
|
|
|
- if subdir == None:
|
|
|
- pagectrl = Paginator(count, p)
|
|
|
- else:
|
|
|
- pagectrl = Paginator(count, p, subdir)
|
|
|
- if not os.path.exists(subdir):
|
|
|
- os.mkdir(subdir)
|
|
|
- except:
|
|
|
- print("Error: value <= 0 submitted to paginator constructor",
|
|
|
- file=sys.stderr)
|
|
|
+ count = len(timeline)
|
|
|
+ p = config["postsperpage"]
|
|
|
+ pagectrl = Paginator(count, p, subdir)
|
|
|
+ except ZeroDivisionError as e:
|
|
|
+ print("error: ",e, ". check 'postsperpage' in config", file=sys.stderr)
|
|
|
+ exit()
|
|
|
+ except Exception as e:
|
|
|
+ print("error: ",e, ("(number of posts = %i)" % count), file=sys.stderr)
|
|
|
exit()
|
|
|
latest = timeline if count <= pagectrl.PPP else timeline[:pagectrl.PPP]
|
|
|
- lvl = 1
|
|
|
if subdir == None: # if top level page
|
|
|
- ovr = False
|
|
|
+ lvl = 1
|
|
|
tcloud = make_tagcloud(tagcloud, "./tags/%s/latest.html")
|
|
|
print(pagectrl.singlepage(html, tcloud, latest))
|
|
|
tcloud = make_tagcloud(tagcloud, "../tags/%s/latest.html")
|
|
|
pagectrl.paginate(
|
|
|
- demote_css(html, config["relative_css"], lvl), tcloud, timeline, ovr)
|
|
|
+ demote_css(html, config["relative_css"], lvl),
|
|
|
+ tcloud, timeline
|
|
|
+ )
|
|
|
else: # if timelines per tag
|
|
|
- ovr = True
|
|
|
+ is_tagline = True
|
|
|
lvl = 2
|
|
|
newhtml = demote_css(html, config["relative_css"], lvl)
|
|
|
tcloud = make_tagcloud(tagcloud, "../%s/latest.html")
|
|
@@ -312,10 +331,9 @@ if __name__ == "__main__":
|
|
|
with open(fn, 'w') as f:
|
|
|
pagectrl.written.append(fn)
|
|
|
f.write(
|
|
|
- pagectrl.singlepage(
|
|
|
- newhtml, tcloud, latest, p="."))
|
|
|
- pagectrl.paginate(
|
|
|
- newhtml, tcloud, timeline, ovr)
|
|
|
+ pagectrl.singlepage(newhtml, tcloud, latest, p=".")
|
|
|
+ )
|
|
|
+ pagectrl.paginate(newhtml, tcloud, timeline, is_tagline)
|
|
|
return pagectrl.written
|
|
|
|
|
|
import toml
|
|
@@ -331,7 +349,6 @@ if __name__ == "__main__":
|
|
|
|
|
|
def main():
|
|
|
tpl, content = get_args()
|
|
|
- # read settings file
|
|
|
cfg = load_settings()
|
|
|
if cfg == None:
|
|
|
print("exit: no settings.toml found.", file=sys.stderr)
|
|
@@ -343,10 +360,15 @@ if __name__ == "__main__":
|
|
|
print("exit: table 'page' absent in settings.toml", file=sys.stderr)
|
|
|
return
|
|
|
tl, tc, tg = get_posts(content, cfg["post"])
|
|
|
+ if tl == []:
|
|
|
+ return
|
|
|
# main timeline
|
|
|
updated = []
|
|
|
updated += writepage(tpl, tl, tc, cfg["page"])
|
|
|
# timeline per tag
|
|
|
+ if tc != dict() and tg != dict():
|
|
|
+ if not os.path.exists("tags"):
|
|
|
+ os.mkdir("tags")
|
|
|
for key in tg.keys():
|
|
|
tagline = []
|
|
|
for index in tg[key]:
|
|
@@ -361,4 +383,13 @@ if __name__ == "__main__":
|
|
|
print(filename, file=f) # sys.stderr)
|
|
|
if "latestpage" in cfg:
|
|
|
print(cfg["latestpage"], file=f)
|
|
|
- main()
|
|
|
+ try:
|
|
|
+ main()
|
|
|
+ except KeyError as e:
|
|
|
+ traceback.print_exc()
|
|
|
+ print("\n\tA key may be missing from your settings file.", file=sys.stderr)
|
|
|
+ except dateutil.parser._parser.ParserError as e:
|
|
|
+ traceback.print_exc()
|
|
|
+ print("\n\tFailed to interpret a date from string..",
|
|
|
+ "\n\tYour file of posts may be malformed.",
|
|
|
+ "\n\tCheck if your file starts with a line break.", file=sys.stderr)
|