306 lines
11 KiB
Python
306 lines
11 KiB
Python
|
|
import sys, os, subprocess
|
|
|
|
# apply div classes for use with .css
|
|
def make_post(num, timestamp, email, msg):
|
|
output = ""
|
|
fmt = '''
|
|
<div class=\"postcell\" id=\"%i\">
|
|
<div class=\"timestamp\"><a href=#%i>%s</a></div>
|
|
<div class=\"message\">%s</div>
|
|
'''
|
|
if email != None:
|
|
fmt += "<div class=\"reply\"><a href=\"mailto:%s?subject=p%i\">[reply]</a></div></div>"
|
|
output = fmt % (num, num, timestamp, msg, email, num)
|
|
else:
|
|
fmt += "</div>"
|
|
output = fmt % (num, num, timestamp, msg)
|
|
return output
|
|
|
|
def make_gallery(i, w):
|
|
tag = []
|
|
if i == []:
|
|
return tag
|
|
tag.append("<div class=\"gallery\">")
|
|
for index in reversed(i):
|
|
image = w.pop(index)
|
|
template = '''
|
|
<div class=\"panel\">
|
|
<a href=\"%s\"><img src=\"%s\" class=\"embed\"></a>
|
|
</div>
|
|
'''
|
|
tag.append(template % (image, image))
|
|
tag.append("</div>")
|
|
return tag
|
|
|
|
# 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, email):
|
|
class Post:
|
|
def __init__(self, ts, msg):
|
|
self.timestamp = ts # string
|
|
self.message = msg # list
|
|
def markup(self):
|
|
result = 0
|
|
tagged = ""
|
|
# support multiple images (gallery style)
|
|
tags = [] # list of strings
|
|
output = []
|
|
for line in self.message:
|
|
images = [] # list of integers
|
|
words = line.split()
|
|
for i in range(len(words)):
|
|
word = words[i]
|
|
# don't help people click http
|
|
if word.find("src=") == 0 or word.find("href=") == 0:
|
|
continue
|
|
elif word.find("https://") != -1:
|
|
w = escape(word)
|
|
new_word = ("<a href=\"%s\">%s</a>") % (w, w)
|
|
words[i] = new_word
|
|
elif word.find("#") != -1 and len(word) > 1:
|
|
# split by unicode blank character if present
|
|
# allows tagging such as #fanfic|tion
|
|
w = word.split(chr(8206))
|
|
# w[0] is the portion closest to the #
|
|
tags.append(w[0])
|
|
new_word = "<span class=\"hashtag\">%s</span>" % (w[0])
|
|
if len(w) > 1:
|
|
new_word += w[1]
|
|
words[i] = new_word
|
|
elif word.find(".jpg") != -1 or word.find(".png") != -1:
|
|
images.append(i)
|
|
if len(images) > 0:
|
|
gallery = make_gallery(images, words)
|
|
words += gallery
|
|
output.append(" ".join(words))
|
|
return "<br>".join(output), tags
|
|
|
|
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
|
|
|
|
posts = parse_txt(filename)
|
|
taginfos = []
|
|
tagcloud = dict() # (tag, count)
|
|
tagged = dict() # (tag, index of message)
|
|
total = len(posts)
|
|
count = total
|
|
index = count # - 1
|
|
timeline = []
|
|
for post in posts:
|
|
markedup, tags = post.markup()
|
|
count -= 1
|
|
index -= 1
|
|
timeline.append(
|
|
make_post(count, post.timestamp, email, markedup)
|
|
)
|
|
for tag in tags:
|
|
if tagcloud.get(tag) == None:
|
|
tagcloud[tag] = 0
|
|
tagcloud[tag] += 1
|
|
if tagged.get(tag) == None:
|
|
tagged[tag] = []
|
|
tagged[tag].append(index)
|
|
return timeline, tagcloud, tagged
|
|
|
|
def make_tagcloud(d, rell):
|
|
sorted_d = {k: v for k,
|
|
v in sorted(d.items(),
|
|
key=lambda item: -item[1])}
|
|
output = []
|
|
fmt = "<span class=\"hashtag\"><a href=\"%s\">%s(%i)</a></span>"
|
|
#fmt = "<span class=\"hashtag\">%s(%i)</span>"
|
|
for key in d.keys():
|
|
link = rell % key[1:]
|
|
output.append(fmt % (link, key, d[key]))
|
|
return output
|
|
|
|
class Paginator:
|
|
def __init__(self, x, loc="./pages"):
|
|
if x <= 0:
|
|
print("Error: No posts (x=%i" % x, file=sys.stderr)
|
|
raise Exception
|
|
self.TOTAL_POSTS = x
|
|
try:
|
|
setting = "postsperpage.txt"
|
|
if os.path.exists(setting):
|
|
with open(setting, 'r') as f:
|
|
self.PPP = int(f.read())
|
|
else:
|
|
with open(setting, 'w') as f:
|
|
f.write(20)
|
|
except:
|
|
self.PPP = 20
|
|
self.TOTAL_PAGES = int(x/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
|
|
if self.TOTAL_PAGES < 1:
|
|
return "[no pages]"
|
|
if path == None:
|
|
path = self.SUBDIR
|
|
# For page 'n' do not create an anchor tag
|
|
fmt = "<a href=\"%s\">[%i]</a>" #(filename, page number)
|
|
anchors = []
|
|
for i in reversed(range(self.TOTAL_PAGES)):
|
|
if i != current_page:
|
|
x = path + "/" + (self.FILENAME % i)
|
|
anchors.append(fmt % (x, i))
|
|
else:
|
|
anchors.append("<b>[%i]</b>" % i)
|
|
return "\n".join(anchors)
|
|
|
|
# makes one page
|
|
def singlepage(self, template, tagcloud, timeline_, i=None, p=None):
|
|
tc = "\n".join(tagcloud)
|
|
tl = "\n\n".join(timeline_)
|
|
toc = self.toc(i, p)
|
|
return template.format(
|
|
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
|
|
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
|
|
for i in range(start, self.TOTAL_PAGES):
|
|
fn = outfile % i
|
|
with open(fn, 'w') as f:
|
|
self.written.append(fn)
|
|
prev = self.PPP * i
|
|
curr = self.PPP * (i+1)
|
|
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
|
|
|
|
if __name__ == "__main__":
|
|
def get_args():
|
|
argc = len(sys.argv)
|
|
if argc < 3:
|
|
msg = '''This is microblog.py. (%s/3 arguments given; 4 maximum)
|
|
\tpython microblog.py [template] [content] [optional: email]
|
|
'''
|
|
print(msg % argc, file=sys.stderr)
|
|
exit()
|
|
# script = argv[0]
|
|
template = sys.argv[1]
|
|
content = sys.argv[2]
|
|
email = sys.argv[3] if (argc >= 4) else None
|
|
return template, content, email
|
|
|
|
# assume relative path
|
|
def adjust_css(template, level=1):
|
|
prepend = ""
|
|
if level == 1:
|
|
prepend = '.'
|
|
else:
|
|
for i in range(level):
|
|
prepend = ("../%s" % prepend)
|
|
css1 = "./style.css"
|
|
css2 = "./timeline.css"
|
|
#sys.stderr.write(("%s%s" % (prepend, css1) ))
|
|
tpl = template.replace(css1, ("%s%s" % (prepend, css1) ))
|
|
return tpl.replace(css2, ("%s%s" % (prepend, css2) ))
|
|
|
|
# needs review / clean-up
|
|
# ideally relate 'lvl' with sub dir instead of hardcoding
|
|
def writepage(template, timeline, tagcloud, subdir = None):
|
|
html = ""
|
|
with open(template,'r') as f:
|
|
html = f.read()
|
|
count = len(timeline)
|
|
try:
|
|
if subdir == None:
|
|
pagectrl = Paginator(count)
|
|
else:
|
|
pagectrl = Paginator(count, subdir)
|
|
if not os.path.exists(subdir):
|
|
os.mkdir(subdir)
|
|
except:
|
|
print("Error: value <= 0 submitted to paginator constructor",
|
|
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
|
|
tcloud = make_tagcloud(tagcloud, "./tags/%s/latest.html")
|
|
print(pagectrl.singlepage(html, tcloud, latest))
|
|
tcloud = make_tagcloud(tagcloud, "../tags/%s/latest.html")
|
|
pagectrl.paginate(adjust_css(html, lvl), tcloud, timeline, ovr)
|
|
else: # if timelines per tag
|
|
ovr = True
|
|
lvl = 2
|
|
tcloud = make_tagcloud(tagcloud, "../%s/latest.html")
|
|
fn = "%s/latest.html" % subdir
|
|
with open(fn, 'w') as f:
|
|
pagectrl.written.append(fn)
|
|
f.write(
|
|
pagectrl.singlepage(
|
|
adjust_css(html,lvl), tcloud, latest, p="."))
|
|
pagectrl.paginate(
|
|
adjust_css(html, lvl), tcloud, timeline, ovr)
|
|
return pagectrl.written
|
|
|
|
def main():
|
|
tpl, content, email = get_args()
|
|
tl, tc, tg = get_posts(content, email)
|
|
# main timeline
|
|
updated = []
|
|
updated += writepage(tpl, tl, tc)
|
|
# timeline per tag
|
|
for key in tg.keys():
|
|
tagline = []
|
|
for index in tg[key]:
|
|
tagline.append(tl[index])
|
|
# [1:] means to omit hashtag from dir name
|
|
updated += writepage(tpl, tagline, tc, "./tags/%s" % key[1:])
|
|
with open("updatedfiles.txt", 'w') as f:
|
|
for filename in updated:
|
|
print(filename, file=f) # sys.stderr)
|
|
main()
|