diff options
author | jwansek <eddie.atten.ea29@gmail.com> | 2021-02-17 20:56:57 +0000 |
---|---|---|
committer | jwansek <eddie.atten.ea29@gmail.com> | 2021-02-17 20:56:57 +0000 |
commit | b076881a8e899c67767eb6c9640f1da68f2d3454 (patch) | |
tree | dddf53dd12a6b919596556a381650137ae3e1fab | |
parent | 58fecf855d151ef0459a43ab9af271a3a4922ad5 (diff) | |
download | eda.gay-b076881a8e899c67767eb6c9640f1da68f2d3454.tar.gz eda.gay-b076881a8e899c67767eb6c9640f1da68f2d3454.zip |
finised parser, finished rendering posts, finished sercving images
-rw-r--r-- | app.py | 72 | ||||
-rw-r--r-- | database.py | 78 | ||||
-rw-r--r-- | parser.py | 114 | ||||
-rw-r--r-- | requirements.txt | 10 | ||||
-rw-r--r-- | services.py | 2 | ||||
-rw-r--r-- | static/images/t30.jpg | bin | 0 -> 235413 bytes | |||
-rw-r--r-- | static/style.css | 7 | ||||
-rw-r--r-- | templates/template.html | 16 | ||||
-rw-r--r-- | templates/thoughts.html | 12 |
9 files changed, 291 insertions, 20 deletions
@@ -1,8 +1,13 @@ +from PIL import Image import configparser import webbrowser +import datetime import database import services +import parser import flask +import os +import io app = flask.Flask(__name__) CONFIG = configparser.ConfigParser() @@ -11,7 +16,7 @@ CONFIG.read("edaweb.conf") def get_template_items(title, db): return { "links": db.get_header_links(), - "image": db.get_image("twitterpic"), + "image": db.get_pfp_image(), "title": title, "articles": db.get_header_articles() } @@ -45,14 +50,73 @@ def serve_services(): pihole = services.get_pihole_stats() ) +@app.route("/thought") +def get_thought(): + thought_id = flask.request.args.get("id", type=int) + with database.Database() as db: + category_name, title, dt, parsed = parser.get_thought_from_id(db, thought_id) + return flask.render_template_string( + parsed, + **get_template_items(title, db), + thought = True, + dt = "published: " + str(dt), + category = category_name, + othercategories = db.get_categories_not(category_name) + ) + +@app.route("/thoughts") +def get_thoughts(): + with database.Database() as db: + all_ = db.get_all_thoughts() + tree = {} + for id_, title, dt, category in all_: + if category not in tree.keys(): + tree[category] = [(id_, title, dt)] + else: + tree[category].append((id_, title, str(dt))) + + return flask.render_template( + "thoughts.html", + **get_template_items("thoughts", db), + tree = tree + ) + +@app.route("/img/<filename>") +def serve_image(filename): + imdirpath = os.path.join(".", "static", "images") + if filename in os.listdir(imdirpath): + try: + w = int(flask.request.args['w']) + h = int(flask.request.args['h']) + except (KeyError, ValueError): + return flask.send_from_directory(imdirpath, filename) + + img = Image.open(os.path.join(imdirpath, filename)) + img.thumbnail((w, h), Image.ANTIALIAS) + io_ = io.BytesIO() + img.save(io_, format='JPEG') + return flask.Response(io_.getvalue(), mimetype='image/jpeg') + + + + else: + flask.abort(404) + + @app.route("/preview") def preview(): - import os if "PREVIEW" in os.environ: with database.Database() as db: - return flask.render_template_string(os.environ["PREVIEW"], **get_template_items(os.environ["PREVIEW_TITLE"], db)) + return flask.render_template_string( + os.environ["PREVIEW"], + **get_template_items(os.environ["PREVIEW_TITLE"], db), + thought = True, + dt = "preview rendered: " + str(datetime.datetime.now()), + category = os.environ["CATEGORY"], + othercategories = db.get_categories_not(os.environ["CATEGORY"]) + ) else: - return "page for internal use only" + flask.abort(404) diff --git a/database.py b/database.py index 93f7538..142d942 100644 --- a/database.py +++ b/database.py @@ -1,12 +1,28 @@ +from dataclasses import dataclass import pymysql +import random import app +@dataclass class Database: + safeLogin:bool = True #automatically login with the user in the config file, who is read only + user:str = None #otherwise, login with the given username and passwd + passwd:str = None + def __enter__(self): - self.__connection = pymysql.connect( - **app.CONFIG["mysql"], - charset = "utf8mb4" - ) + if self.safeLogin: + self.__connection = pymysql.connect( + **app.CONFIG["mysql"], + charset = "utf8mb4" + ) + else: + self.__connection = pymysql.connect( + user = self.user, + passwd = self.passwd, + host = app.CONFIG["mysql"]["host"], + db = app.CONFIG["mysql"]["db"], + charset = "utf8mb4" + ) return self def __exit__(self, type, value, traceback): @@ -22,12 +38,64 @@ class Database: cursor.execute("SELECT alt, url FROM images WHERE imageName = %s;", (imageName, )) return cursor.fetchone() + def get_pfp_image(self): + with self.__connection.cursor() as cursor: + cursor.execute("SELECT alt, url FROM images WHERE pfp_img = 1;") + return random.choice(cursor.fetchall()) + def get_header_articles(self): with self.__connection.cursor() as cursor: cursor.execute("SELECT articleName, link FROM headerArticles;") return cursor.fetchall() + def get_all_categories(self): + with self.__connection.cursor() as cursor: + cursor.execute("SELECT category_name FROM categories;") + return [i[0] for i in cursor.fetchall()] + + def add_category(self, category): + if not category in self.get_all_categories(): + with self.__connection.cursor() as cursor: + cursor.execute("INSERT INTO categories (category_name) VALUES (%s);", (category, )) + + self.__connection.commit() + return True + + return False + + def add_thought(self, category, title, markdown): + with self.__connection.cursor() as cursor: + cursor.execute(""" + INSERT INTO thoughts (category_id, title, markdown_text) + VALUES (( + SELECT category_id FROM categories WHERE category_name = %s + ), %s, %s);""", (category, title, markdown)) + self.__connection.commit() + + def get_thought(self, id_): + with self.__connection.cursor() as cursor: + cursor.execute(""" + SELECT categories.category_name, thoughts.title, thoughts.dt, thoughts.markdown_text + FROM thoughts INNER JOIN categories + ON thoughts.category_id = categories.category_id + WHERE thought_id = %s;""", (id_, )) + return cursor.fetchone() + + def get_categories_not(self, category_name): + with self.__connection.cursor() as cursor: + cursor.execute("SELECT category_name FROM categories WHERE category_name != %s;", (category_name, )) + return [i[0] for i in cursor.fetchall()] + + def get_all_thoughts(self): + with self.__connection.cursor() as cursor: + cursor.execute(""" + SELECT thought_id, title, dt, category_name FROM thoughts + INNER JOIN categories ON thoughts.category_id = categories.category_id; + """) + return cursor.fetchall() + + if __name__ == "__main__": with Database() as db: - print(db.get_image("headerImage"))
\ No newline at end of file + print(db.get_all_thoughts())
\ No newline at end of file @@ -1,6 +1,8 @@ import argparse from urllib.parse import urlparse import webbrowser +import database +import getpass import app import re import os @@ -8,13 +10,22 @@ import os HEADER_INCREMENTER = 1 IMAGE_TYPES = [".png", ".jpg"] +def get_thought_from_id(db, id_): + category_name, title, dt, markdown = db.get_thought(id_) + return category_name, title, dt, parse_text(markdown) + def parse_file(path): with open(path, "r") as f: unformatted = f.read() + return parse_text(unformatted) + +def parse_text(unformatted): formatted = parse_headers(unformatted) formatted = parse_asteriscs(formatted) formatted = parse_links(formatted) + formatted = parse_code(formatted) + formatted = parse_lists(formatted) formatted = add_linebreaks(formatted) return '{% extends "template.html" %}\n{% block content %}\n' + formatted + '\n{% endblock %}' @@ -77,17 +88,71 @@ def parse_links(test_str): return test_str +def parse_code(test_str): + regex = r"(?<!\\)`\w{1,}?`" + # this only matches single words, but escaping is less complicated + matches = re.finditer(regex, test_str, re.MULTILINE) + offset = 0 + + for match in matches: + replacement = "<em class=inlineCode style='font-family: monospace;font-style: normal;'>%s</em>" % match.group()[1:-1] + test_str = test_str[:match.start()+offset] + replacement + test_str[match.end()+offset:] + offset += (len(replacement) - (match.end() - match.start())) + + out = "" + inBlock = 0 + for line in test_str.split("\n"): + if line == "```": + if inBlock % 2 == 0: + out += "<p class=codeBlock style='font-family: monospace;font-style: normal;white-space: pre-wrap;'>\n" + else: + out += "</p>\n" + inBlock += 1 + else: + out += line + "\n" + + return out + +def parse_lists(test_str): + regex = r"^[1-9][.)] .*$|- .*$" + matches = re.finditer(regex, test_str, re.MULTILINE) + offset = 0 + theFirstOne = True + + for match in matches: + if theFirstOne: + if match.group()[0].isdigit(): + listType = "ol" + cutoff = 3 + else: + listType = "ul" + cutoff = 2 + replacement = "<%s>\n<li>%s</li>" % (listType, match.group()[cutoff:]) + theFirstOne = False + else: + if re.match(regex, [i for i in test_str[match.end()+offset:].split("\n") if i != ''][0]) is None: + theFirstOne = True + replacement = "<li>%s</li>\n</%s>" % (match.group()[cutoff:], listType) + else: + replacement = "<li>%s</li>" % match.group()[cutoff:] + test_str = test_str[:match.start()+offset] + replacement + test_str[match.end()+offset:] + offset += (len(replacement) - (match.end() - match.start())) + + return test_str + def add_linebreaks(test_str): return re.sub(r"^$", "<br><br>", test_str, 0, re.MULTILINE) -def preview_markdown(path, title): +def preview_markdown(path, title, category): def startBrowser(): webbrowser.get("firefox").open("http://localhost:5000/preview") del os.environ["PREVIEW"] del os.environ["PREVIEW_TITLE"] + del os.environ["CATEGORY"] os.environ["PREVIEW"] = parse_file(path) os.environ["PREVIEW_TITLE"] = title + os.environ["CATEGORY"] = category import threading threading.Timer(1.25, startBrowser ).start() @@ -97,8 +162,13 @@ def preview_markdown(path, title): def main(): p = argparse.ArgumentParser() p.add_argument( - "-m", "--markdown", + "markdown", help = "Path to a markdown file", + type = str + ) + p.add_argument( + "-u", "--username", + help = "Username to use for the database", required = True, type = str ) @@ -109,15 +179,47 @@ def main(): type = str ) p.add_argument( + "-c", "--category", + help = "Article category", + required = True, + type = str + ) + g = p.add_mutually_exclusive_group(required = True) + g.add_argument( "-p", "--preview", help = "Preview markdown rendering", action='store_true' ) + g.add_argument( + "-s", "--save", + help = "Save markdown to database", + action='store_true' + ) + g.add_argument( + "-e", "--echo", + help = "Print parsed markdown to stdout", + action='store_true' + ) args = vars(p.parse_args()) - if args["preview"]: - preview_markdown(args["markdown"], args["title"]) - else: - print(parse_file(args["markdown"])) + + passwd = getpass.getpass("Enter password for %s@%s: " % (args["username"], app.CONFIG["mysql"]["host"])) + + with database.Database( + safeLogin = False, + user = args["username"], + passwd = passwd + ) as db: + + if args["preview"]: + preview_markdown(args["markdown"], args["title"], args["category"]) + elif args["save"]: + if db.add_category(args["category"]): + print("Added category...") + with open(args["markdown"], "r") as f: + db.add_thought(args["category"], args["title"], f.read()) + print("Added thought...") + else: + print(parse_file(args["markdown"])) if __name__ == "__main__": main()
\ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e86483b..fd89b0f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ -python_qbittorrent==0.4.2 PyMySQL==1.0.2 +python_qbittorrent==0.4.2 Flask==1.1.2 -PiHole_api==2.6 -clutch==0.1a2 -docker_py==1.10.6 +PiHole_api +transmission-clutch +dataclasses +docker pihole==0.1.2 +Pillow==8.1.0 qbittorrent==0.1.6 diff --git a/services.py b/services.py index b592427..32edb5d 100644 --- a/services.py +++ b/services.py @@ -33,7 +33,7 @@ def timeout(func): # this works but Manager() uses an extra thread than Queue() manager = multiprocessing.Manager() returnVan = manager.list() - ti = time.time() + # ti = time.time() def runFunc(q, func): q.append(func()) diff --git a/static/images/t30.jpg b/static/images/t30.jpg Binary files differnew file mode 100644 index 0000000..949d14b --- /dev/null +++ b/static/images/t30.jpg diff --git a/static/style.css b/static/style.css index 7bc2190..7a0bf68 100644 --- a/static/style.css +++ b/static/style.css @@ -90,6 +90,13 @@ article section table td { text-align: right; } +aside { + width: 30%; + padding-left: 15px; + margin-left: 15px; + float: right; +} + .running { background-color: green; padding: 1, 1, 1, 1; diff --git a/templates/template.html b/templates/template.html index 6b0d894..afbeefe 100644 --- a/templates/template.html +++ b/templates/template.html @@ -36,6 +36,22 @@ </a> </div> </header> + <!-- yes, this is stupid asf, using another template would be better (i cant get that to work) --> + {% if thought %} + <aside> + <h4>{{dt}}</h4> + <h5>this category:</h5> + <ul> + <li><b><a href={{'/thoughts#'+category.replace(' ', '_')}}>{{category}}</a></b></li> + </ul> + <h5>other categories:</h5> + {% for category_name in othercategories %} + <ul> + <li><a href={{'/thoughts#'+category_name.replace(' ', '_')}}>{{category_name}}</a></li> + </ul> + {% endfor %} + </aside> + {% endif %} <article> {% block content %} {% endblock %} diff --git a/templates/thoughts.html b/templates/thoughts.html new file mode 100644 index 0000000..8717299 --- /dev/null +++ b/templates/thoughts.html @@ -0,0 +1,12 @@ +{% extends "template.html" %} +{% block content %} + {% for category_name, thoughts in tree.items() %} + <h2 id={{category_name.replace(' ', '_')}}>{{category_name}}</h2> + <dl> + {% for id_, title, dt in thoughts %} + <dt><a href={{'/thought?id=%i' % id_}}>title</a></dt> + <dd>{{dt}}</dd> + {% endfor %} + </dl> + {% endfor %} +{% endblock %}
\ No newline at end of file |