From bb1bdee4c916dffc7096e6fb2cf2fa4f19500e42 Mon Sep 17 00:00:00 2001 From: Tim Van Baak Date: Mon, 29 May 2023 18:19:33 -0700 Subject: [PATCH] Init Flask app --- .gitignore | 5 +- intake/app.py | 66 ++++++++++++ intake/cli.py | 38 +++++-- intake/source.py | 7 +- intake/templates/feed.jinja2 | 193 +++++++++++++++++++++++++++++++++++ 5 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 intake/app.py create mode 100644 intake/templates/feed.jinja2 diff --git a/.gitignore b/.gitignore index 855bdb5..13a0ce3 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,7 @@ cython_debug/ nixos.qcow2 # nix-build -result \ No newline at end of file +result + +# test sources +tests/**/*.item \ No newline at end of file diff --git a/intake/app.py b/intake/app.py new file mode 100644 index 0000000..eb2944e --- /dev/null +++ b/intake/app.py @@ -0,0 +1,66 @@ +from datetime import datetime +from pathlib import Path +import os + +from flask import Flask, render_template, request, jsonify, abort, redirect, url_for + +from intake.source import LocalSource + +# Globals +app = Flask(__name__) + + +def intake_data_dir() -> Path: + if intake_data := os.environ.get("INTAKE_DATA"): + return Path(intake_data) + if xdg_data_home := os.environ.get("XDG_DATA_HOME"): + return Path(xdg_data_home) / "intake" + if home := os.environ.get("HOME"): + return Path(home) / ".local" / "share" / "intake" + raise Exception("No intake data directory defined") + + +def item_sort_key(item): + item_date = item.get("time", item.get("created", 0)) + return (item_date, item["id"]) + + +@app.template_filter("datetimeformat") +def datetimeformat(value): + if not value: + return "" + dt = datetime.fromtimestamp(value) + return dt.strftime("%Y-%m-%d %H:%M:%S") + + +@app.route("/") +def root(): + return "hello, world" + + +@app.route("/source/") +def source_feed(source_name): + """ + Feed view for a single source. + """ + source = LocalSource(intake_data_dir(), source_name) + + # Get all items + # TODO: support paging parameters + all_items = list(source.get_all_items()) + all_items.sort(key=item_sort_key) + + return render_template( + "feed.jinja2", + items=all_items, + mdeac=[ + {"source": item["source"], "itemid": item["id"]} + for item in all_items + if "id" in item + ], + ) + + +def wsgi(): + # init_default_logging() + return app diff --git a/intake/cli.py b/intake/cli.py index 754a366..d8d5e20 100644 --- a/intake/cli.py +++ b/intake/cli.py @@ -6,8 +6,8 @@ import os.path import subprocess import sys -from .source import fetch_items, LocalSource, update_items -from .types import InvalidConfigException, SourceUpdateException +from intake.source import fetch_items, LocalSource, update_items +from intake.types import InvalidConfigException, SourceUpdateException def intake_data_dir() -> Path: @@ -76,10 +76,9 @@ def cmd_update(cmd_args): parser.add_argument( "--dry-run", action="store_true", - help="Instead of updating the source, print the fetched items" + help="Instead of updating the source, print the fetched items", ) args = parser.parse_args(cmd_args) - ret = 0 source = LocalSource(Path(args.base), args.source) try: @@ -92,13 +91,38 @@ def cmd_update(cmd_args): except InvalidConfigException as ex: print("Could not fetch", args.source) print(ex) - ret = 1 + return 1 except SourceUpdateException as ex: print("Error updating source", args.source) print(ex) - ret = 1 + return 1 - return ret + return 0 + + +def cmd_run(cmd_args): + """Run the default Flask server.""" + parser = argparse.ArgumentParser( + prog="intake run", + description=cmd_run.__doc__, + ) + parser.add_argument( + "--base", + default=intake_data_dir(), + help="Path to the intake data directory containing source directories", + ) + parser.add_argument("--debug", action="store_true") + parser.add_argument("--port", type=int, default=5000) + args = parser.parse_args(cmd_args) + + try: + from intake.app import app + + app.run(port=args.port, debug=args.debug) + return 0 + except Exception as ex: + print(ex) + return 1 def cmd_help(_): diff --git a/intake/source.py b/intake/source.py index 755f477..804fc2d 100755 --- a/intake/source.py +++ b/intake/source.py @@ -7,7 +7,7 @@ import os import os.path import time -from .types import InvalidConfigException, SourceUpdateException +from intake.types import InvalidConfigException, SourceUpdateException class LocalSource: @@ -68,6 +68,11 @@ class LocalSource: def delete_item(self, item_id) -> None: os.remove(self.get_item_path(item_id)) + def get_all_items(self) -> List[dict]: + for filepath in self.source_path.iterdir(): + if filepath.name.endswith(".item"): + yield json.loads(filepath.read_text(encoding="utf8")) + def read_stdout(process: Popen, outs: list): """ diff --git a/intake/templates/feed.jinja2 b/intake/templates/feed.jinja2 new file mode 100644 index 0000000..28fc330 --- /dev/null +++ b/intake/templates/feed.jinja2 @@ -0,0 +1,193 @@ + + + +Intake{% if items %} ({{ items|length - 1 }}){% endif %} + + + + + +
+{# #} +{% if items %} +{% for item in items %} +
+ {% if item.id %} + + {% endif %} + {% if item.id %} + + {% endif %} + {% if item.link %} + + {% endif %} + + {# The item title is a clickable if there is body content #} + {% if item.body or item.callback %} +
+ {{item.title}} + {% if item.body %} +

{{item.body|safe}}

+ {% endif %} + {% if item.callback %} +

+ {% endif %} +
+ {% else %} + {{item.title}}
+ {% endif %} + + {# author/time footer line #} + {% if item.author or item.time %} + + {% if item.author %}{{item.author}}{% endif %} + {% if item.time %}{{item.time|datetimeformat}}{% endif %} +
+ {% endif %} + + {# source/id/created footer line #} + {% if item.source or item.id or item.created %} + + {% if item.source %}{{item.source}}{% endif %} + {% if item.id %}{{item.id}}{% endif %} + {% if item.created %}{{item.created|datetimeformat}}{% endif %} + {% if item.ttl %}L{% endif %}{% if item.ttd %}D{% endif %}{% if item.tts %}S{% endif %} + + {% endif %} + +
+{% endfor %} + +{% if items %} +
+
+Feed Management +
+ +
+
+
+{% endif %} + +{# if items #} +{% else %} +
+Feed is empty +
+{% endif %} + +
+ +