diff --git a/README.md b/README.md
index ac6513b..4017cc4 100644
--- a/README.md
+++ b/README.md
@@ -5,19 +5,22 @@ Simple and stylish text-to-html microblog generator.
## Requirements
- python3 date make curl pycurl urllib
+ python3 date make toml curl pycurl urllib
-`date` is `date` from GNU Core Utilities. `make` is only used to demonstrate the examples in `example/`. The latter three are optional; `curl`, `pycurl` and `urllib` are only used for the uploader script.
+* `date` is `date` from GNU Core Utilities.
+* `toml` is a Python module.
+* `make` (optional), method for invoking the script.
+* `curl`, `pycurl` and `urllib` (optional), for uploading multiple files to neocities (`neouploader.py`).
### Usage
Send three arguments minimum to `python`. The fourth argument for an e-mail address is optional.
- python microblog.py ./template.html ./content.txt user@mailhost.tld
+ python microblog.py ./template.html ./content.txt
The resulting web page is outputted from standard output. Therefore:
- python microblog.py ./template.html ./content.txt user@mailhost.tld > result.html
+ python microblog.py ./template.html ./content.txt > result.html
Use a Makefile (or another script) to simplify invocation.
@@ -25,10 +28,13 @@ Use a Makefile (or another script) to simplify invocation.
This script generates three text files after operation.
-* `postsperpage.txt`, holds an integer for the number of posts to render per page (default: 20). This is a configuration file that is created if it does not exist).
* `lastfullpage.txt`, holds an integer for the last page rendered by the paginator.
* `updatedfiles.txt`, a list of files updated by the script for use in automated uploads.
+## Configuration
+
+Settings are read from `settings.toml`. See `example/settings.toml`.
+
### Writing Content
See `example/demo.txt`.
diff --git a/example/settings.toml b/example/settings.toml
new file mode 100644
index 0000000..151a206
--- /dev/null
+++ b/example/settings.toml
@@ -0,0 +1,13 @@
+latestpage="result.html"
+
+[page]
+postsperpage = 20
+relative_css=["./style.css", "./timeline.css"]
+
+[post]
+accepted_images= ["jpg", "JPG", "png", "PNG"]
+buttons = {reply = "mailto:user@host.tld", test = "https://toml.io/en/v1.0.0#array-of-tables", interact="https://yoursite.tld/cgi?postid="}
+# true = add
tags to each line.
+tag_paragraphs=true
+# adds
or user defined string between each line
+# line_separator="
"
diff --git a/example/timeline.css b/example/timeline.css
index b503684..2db28a2 100644
--- a/example/timeline.css
+++ b/example/timeline.css
@@ -1,12 +1,15 @@
-.column {
- float: left;
- width: 30%;
-/* background-color: #1a1a1a; */
-}
-.timeline {
- float: right;
- width: 67%;
-/* background-color: #1a1a1a; */
+
+@media only screen and (min-width: 768px) {
+ .column {
+ float: left;
+ width: 30%;
+ /* background-color: #1a1a1a; */
+ }
+ .timeline {
+ float: right;
+ width: 67%;
+ /* background-color: #1a1a1a; */
+ }
}
.postcell {
border: 1px solid gray;
@@ -18,8 +21,9 @@
white-space: pre-wrap;
word-wrap: break-word;
}
-.reply {
- margin: 0.5em
+.buttons {
+ margin-left: 1em;
+ margin-bottom:0.5em;
}
.timestamp {
text-align: right;
diff --git a/microblog.py b/microblog.py
index 47d800c..7f8d0b0 100644
--- a/microblog.py
+++ b/microblog.py
@@ -1,21 +1,31 @@
import sys, os, subprocess
+# returns html-formatted string
+def make_buttons(btn_dict, msg_id):
+ buttons = ""
+ return buttons
+
# apply div classes for use with .css
-def make_post(num, timestamp, email, msg):
- output = ""
+def make_post(num, timestamp, conf, msg):
fmt = '''
-
+
%s
'''
- if email != None:
- fmt += "
"
- output = fmt % (num, num, timestamp, msg, email, num)
- else:
- fmt += ""
- output = fmt % (num, num, timestamp, msg)
- return output
+ if "buttons" in conf:
+ b = make_buttons(conf["buttons"], num)
+ fmt += b
+ fmt += ""
+ return fmt % (num, timestamp, num, num, msg)
def make_gallery(i, w):
tag = []
@@ -33,49 +43,78 @@ def make_gallery(i, w):
tag.append("")
return tag
+
+def markup(message, config):
+ def is_image(s, image_formats):
+ l = s.rsplit('.', maxsplit=1)
+ if len(l) < 2:
+ return False
+ # Python 3.10.5
+ # example result that had to be filtered:
+ # string: started.
+ # result: ['started', '']
+ if l[1] == str(''):
+ return False
+ #print(s, l, file=sys.stderr)
+ if l[1] in image_formats:
+ return True
+ return False
+
+ result = 0
+ tagged = ""
+ # support multiple images (gallery style)
+ tags = [] # list of strings
+ output = []
+ gallery = []
+ ptags = config["tag_paragraphs"]
+ sep = ""
+ if "line_separator" in config:
+ sep = config["line_separator"]
+ for line in 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 = ("%s") % (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 = "%s" % (w[0])
+ if len(w) > 1:
+ new_word += w[1]
+ words[i] = new_word
+ elif is_image(word, config["accepted_images"]):
+ images.append(i)
+ if len(images) > 0:
+ # function invokes pop() which modifies list 'words'
+ gallery = make_gallery(images, words)
+ if ptags and len(words) > 0:
+ words.insert(0,"")
+ words.append("
")
+ output.append(" ".join(words))
+ # avoid paragraph with an image gallery
+ if len(gallery) > 0:
+ output.append("".join(gallery))
+ gallery = []
+ return sep.join(output), tags
+
# 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):
+def get_posts(filename, config):
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 = ("%s") % (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 = "%s" % (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 "
".join(output), tags
def parse_txt(filename):
content = []
@@ -114,12 +153,13 @@ def get_posts(filename, email):
count = total
index = count # - 1
timeline = []
+ btns = None
for post in posts:
- markedup, tags = post.markup()
+ markedup, tags = markup(post.message, config)
count -= 1
index -= 1
timeline.append(
- make_post(count, post.timestamp, email, markedup)
+ make_post(count, post.timestamp, config, markedup)
)
for tag in tags:
if tagcloud.get(tag) == None:
@@ -143,21 +183,12 @@ def make_tagcloud(d, rell):
return output
class Paginator:
- def __init__(self, x, loc="./pages"):
+ def __init__(self, x, ppp, 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.PPP = ppp
self.TOTAL_PAGES = int(x/self.PPP)
self.SUBDIR = loc
if not os.path.exists(loc):
@@ -221,43 +252,42 @@ 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]
+ msg = '''This is microblog.py. (%s/3 arguments given)
+\tpython microblog.py [template] [content]
'''
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
+ return template, content
# assume relative path
- def adjust_css(template, level=1):
+ def demote_css(template, cssl, 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) ))
+ tpl = template
+ for css in cssl:
+ tpl = tpl.replace(css, ("%s%s" % (prepend, css) ))
+ return tpl
# needs review / clean-up
# ideally relate 'lvl' with sub dir instead of hardcoding
- def writepage(template, timeline, tagcloud, subdir = None):
+ def writepage(template, timeline, tagcloud, config, subdir = None):
html = ""
with open(template,'r') as f:
html = f.read()
count = len(timeline)
try:
+ p = config["postsperpage"]
if subdir == None:
- pagectrl = Paginator(count)
+ pagectrl = Paginator(count, p)
else:
- pagectrl = Paginator(count, subdir)
+ pagectrl = Paginator(count, p, subdir)
if not os.path.exists(subdir):
os.mkdir(subdir)
except:
@@ -271,35 +301,64 @@ if __name__ == "__main__":
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)
+ pagectrl.paginate(
+ demote_css(html, config["relative_css"], lvl), tcloud, timeline, ovr)
else: # if timelines per tag
ovr = True
lvl = 2
+ newhtml = demote_css(html, config["relative_css"], lvl)
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="."))
+ newhtml, tcloud, latest, p="."))
pagectrl.paginate(
- adjust_css(html, lvl), tcloud, timeline, ovr)
+ newhtml, tcloud, timeline, ovr)
return pagectrl.written
+ import toml
+ def load_settings():
+ s = dict()
+ filename = "settings.toml"
+ if os.path.exists(filename):
+ with open(filename, 'r') as f:
+ s = toml.loads(f.read())
+ else:
+ s = None
+ return s
+
def main():
- tpl, content, email = get_args()
- tl, tc, tg = get_posts(content, email)
+ tpl, content = get_args()
+ # read settings file
+ cfg = load_settings()
+ if cfg == None:
+ print("exit: no settings.toml found.", file=sys.stderr)
+ return
+ if "post" not in cfg:
+ print("exit: table 'post' absent in settings.toml", file=sys.stderr)
+ return
+ if "page" not in cfg:
+ print("exit: table 'page' absent in settings.toml", file=sys.stderr)
+ return
+ tl, tc, tg = get_posts(content, cfg["post"])
# main timeline
updated = []
- updated += writepage(tpl, tl, tc)
+ updated += writepage(tpl, tl, tc, cfg["page"])
# 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:])
+ updated += writepage(
+ tpl, tagline, tc, cfg["page"], \
+ subdir="tags/%s" % key[1:] \
+ )
with open("updatedfiles.txt", 'w') as f:
for filename in updated:
print(filename, file=f) # sys.stderr)
+ if "latestpage" in cfg:
+ print(cfg["latestpage"], file=f)
main()
diff --git a/neouploader.py b/neouploader.py
index 1922a84..c4350c1 100644
--- a/neouploader.py
+++ b/neouploader.py
@@ -1,7 +1,7 @@
import sys, subprocess, getpass, pycurl, urllib.parse
if __name__ == "__main__":
- def api_upload(endpoint, dest_fmt = "/microblog%s%s"):
+ def api_upload(endpoint, dest_fmt = "/microblog/%s"):
pages = []
with open("updatedfiles.txt") as f:
pages = f.readlines()
@@ -10,11 +10,11 @@ if __name__ == "__main__":
c.setopt(c.POST, 1)
for page in pages:
p = page.strip('\n')
- i = p.rfind('/')
+ #i = p.rfind('/')
# folder = p[1:i]
# file = p[i]
- destination = dest_fmt % (p[1:i], p[i:])
- source = p[2:] # omit './'
+ destination = dest_fmt % p
+ source = p
print("sending @%s to %s" % (source, destination))
exists = True
try:
@@ -40,10 +40,9 @@ if __name__ == "__main__":
except KeyboardInterrupt:
print("Aborted.")
return
- finally:
- if len(pw) == 0:
- print("Empty input. Exiting.")
- return
+ if len(pw) == 0:
+ print("Empty input. Exiting.")
+ return
p = urllib.parse.quote(pw, safe='')
target = "https://%s:%s@neocities.org/api/upload" % (sys.argv[1], p)
del pw