From b076881a8e899c67767eb6c9640f1da68f2d3454 Mon Sep 17 00:00:00 2001 From: jwansek Date: Wed, 17 Feb 2021 20:56:57 +0000 Subject: finised parser, finished rendering posts, finished sercving images --- app.py | 72 ++++++++++++++++++++++++++++-- database.py | 78 ++++++++++++++++++++++++++++++--- parser.py | 114 +++++++++++++++++++++++++++++++++++++++++++++--- requirements.txt | 10 +++-- services.py | 2 +- static/images/t30.jpg | Bin 0 -> 235413 bytes static/style.css | 7 +++ templates/template.html | 16 +++++++ templates/thoughts.html | 12 +++++ 9 files changed, 291 insertions(+), 20 deletions(-) create mode 100644 static/images/t30.jpg create mode 100644 templates/thoughts.html diff --git a/app.py b/app.py index e100689..24a6c64 100644 --- a/app.py +++ b/app.py @@ -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/") +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 diff --git a/parser.py b/parser.py index 8677342..cce0cb0 100644 --- a/parser.py +++ b/parser.py @@ -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"(?%s" % 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 += "

\n" + else: + out += "

\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
  • %s
  • " % (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 = "
  • %s
  • \n" % (match.group()[cutoff:], listType) + else: + replacement = "
  • %s
  • " % 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"^$", "

    ", 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 new file mode 100644 index 0000000..949d14b Binary files /dev/null and b/static/images/t30.jpg differ 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 @@ + + {% if thought %} + + {% endif %}
    {% 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() %} +

    {{category_name}}

    +
    + {% for id_, title, dt in thoughts %} +
    title
    +
    {{dt}}
    + {% endfor %} +
    + {% endfor %} +{% endblock %} \ No newline at end of file -- cgit v1.2.3