From fc75e469c0adcd2fae332dd6bdcadc11b5b35382 Mon Sep 17 00:00:00 2001 From: Flancian <0@flancia.org> Date: Tue, 10 Nov 2020 22:48:20 +0100 Subject: [PATCH] Some good progress, but also lightly broken code :) --- app/agora.py | 31 ++++-- app/config.py | 4 + app/db.py | 99 +++++++++++++++++-- app/templates/base.html | 5 +- app/templates/index.html | 5 +- ...nodes_rendered.html => node_rendered.html} | 18 ++-- app/templates/nodes.html | 5 +- app/templates/subnode_rendered.html | 35 +++++++ app/templates/subnodes.html | 23 +++++ app/templates/users.html | 23 +++++ 10 files changed, 219 insertions(+), 29 deletions(-) rename app/templates/{nodes_rendered.html => node_rendered.html} (62%) create mode 100644 app/templates/subnode_rendered.html create mode 100644 app/templates/subnodes.html create mode 100644 app/templates/users.html diff --git a/app/agora.py b/app/agora.py index 299635c..948ddb1 100644 --- a/app/agora.py +++ b/app/agora.py @@ -22,33 +22,40 @@ bp = Blueprint('agora', __name__) @bp.route('/') def index(): - return render_template('index.html', help=url_for('agora.help'), nodes=url_for('agora.nodes'), journals=url_for('agora.journals')) + return render_template('index.html', help=url_for('agora.help'), nodes=url_for('agora.nodes'), subnodes=url_for('agora.subnodes'), users=url_for('agora.users'), journals=url_for('agora.journals')) @bp.route('/help') def help(): current_app.logger.warning('Not implemented.') return 'If I had implemented help already, here you\'d see documentation on all URL endpoints. For now, please refer to the code.' -@bp.route('/notes') # alias for now @bp.route('/nodes') def nodes(): - current_app.logger.warning('Config: %s' % config.AGORA_PATH) + # current_app.logger.warning('Config: %s' % config.AGORA_PATH) return render_template('nodes.html', nodes=db.all_nodes()) +@bp.route('/notes') # alias +@bp.route('/subnodes') +def subnodes(): + return render_template('subnodes.html', subnodes=db.all_subnodes()) + +@bp.route('/users') +def users(): + return render_template('users.html', users=db.all_users()) + @bp.route('/journals') def journals(): - return render_template('nodes.html', nodes=db.all_journals()) + return render_template('subnodes.html', nodes=db.all_journals()) @bp.route('/today') def today(): today = datetime.datetime.now().date() return redirect("https://anagora.org/node/%s" % today.strftime("%Y-%m-%d")) -@bp.route('/u/') -@bp.route('/user/') -def user(username): - current_app.logger.warning('Not implemented.') - return 'If I had implemented reading user profiles already, here you would see user named "%s".' % escape(username) +@bp.route('/u/') +@bp.route('/user/') +def user(user): + return render_template('subnodes.html', nodes=db.subnodes_by_user(user)) @bp.route('/garden/') def garden(garden): @@ -58,7 +65,11 @@ def garden(garden): @bp.route('/node/') @bp.route('/wikilink/') # alias for now def wikilink(node): - return render_template('nodes_rendered.html', wikilink=node, nodes=db.nodes_by_wikilink(node), backlinks=db.nodes_by_outlink(node)) + return render_template('node_rendered.html', wikilink=node, subnodes=db.subnodes_by_wikilink(node), backlinks=db.subnodes_by_outlink(node)) + +@bp.route('/subnode/') +def subnode(subnode): + return render_template('subnode_rendered.html', subnode=db.subnode_by_uri(subnode), backlinks=db.subnodes_by_outlink(subnode)) @bp.route('/asset//') def asset(user, asset): diff --git a/app/config.py b/app/config.py index ce4bbae..257c0c9 100644 --- a/app/config.py +++ b/app/config.py @@ -1,3 +1,7 @@ import os import getpass + AGORA_PATH = os.path.join('/home', getpass.getuser(), 'agora') + +# With trailing slash. +URL_BASE = "https://anagora.org/" diff --git a/app/db.py b/app/db.py index 9153fdf..ce07ca0 100644 --- a/app/db.py +++ b/app/db.py @@ -20,18 +20,76 @@ from operator import attrgetter RE_WIKILINKS = re.compile('\[\[(.*?)\]\]') +# URIs are ids. +# - In the case of nodes, their [[wikilink]]. +# - Example: 'foo', meaning the node that is rendered when you click on [[foo]] somewhere. +# - In the case of subnodes, a relative path within the Agora. +# - Example: 'garden/flancian/README.md', meaning an actual file called README.md. +# - Note the example subnode above gets rendered in node [[README]], so fetching node with uri README would yield it (and others). + +# TODO: implement. +class Graph: + def __init__(self): + # [[wikilink]] -> Node + self.nodes = {} + # node -> [n0, ..., nn] such that node has outlinks to the target list. + self.edges = {} + def addsubnode(self, subnode): + if subnode.wikilink in self.nodes: + G.nodes[subnode.wikilink].subnodes.append(subnode) + else: + G.nodes[subnode.wikilink] = Node(subnode.wikilink) + +G = Graph() + class Node: + """Nodes map 1:1 to wikilinks. + They resolve to a series of subnodes when being rendered (see below). + It maps to a particular file in the Agora repository, stored (relative to + the Agora root) in the attribute 'uri'.""" + def __init__(self, wikilink): + # Use a node's URI as its identifier. + # Subnodes are attached to the node matching their wikilink. + # i.e. if two users contribute subnodes titled [[foo]], they both show up when querying node [[foo]]. + self.wikilink = wikilink + self.uri = wikilink + self.url = '/node/' + self.uri + self.subnodes = [] + +class Subnode: + """A subnode is a note or media resource volunteered by a user of the Agora. + It maps to a particular file in the Agora repository, stored (relative to + the Agora root) in the attribute 'uri'.""" def __init__(self, path): - self.dir = path_to_url(path) + # Use a subnode's URI as its identifier. + self.uri = path_to_uri(path) + self.url = '/subnode/' + path_to_uri(path) + # Subnodes are attached to the node matching their wikilink. + # i.e. if two users contribute subnodes titled [[foo]], they both show up when querying node [[foo]]. self.wikilink = path_to_wikilink(path) - self.url = '/node/' + self.wikilink + self.user = path_to_user(path) with open(path) as f: self.content = f.read() self.outlinks = content_to_outlinks(self.content) + self.node = self.wikilink + # Initiate node for wikilink if this is the first subnode, append otherwise. + G.addsubnode(self) -def path_to_url(path): +class User: + def __init__(self, user): + self.uri = user + self.url = '/user/' + self.uri + +def path_to_uri(path): return path.replace(config.AGORA_PATH + '/', '') +def path_to_user(path): + m = re.search('garden/(.+?)/', path) + if m: + return m.group(1) + else: + return 'agora' + def path_to_wikilink(path): return os.path.splitext(os.path.basename(path))[0] @@ -43,19 +101,46 @@ def content_to_outlinks(content): else: return [] -def all_nodes(): +def all_subnodes(): l = sorted([f for f in glob.glob(os.path.join(config.AGORA_PATH, '**/*.md'), recursive=True)]) - return [Node(f) for f in l] + return [Subnode(f) for f in l] + +def all_nodes(): + # hack hack. + l = all_subnodes() + return sorted([Node(sn.wikilink) for sn in l], key=attrgetter('wikilink')) + +def all_users(): + # hack hack. + users = os.listdir(os.path.join(config.AGORA_PATH, 'garden')) + return sorted([User(u) for u in users], key=attrgetter('uri')) def all_journals(): # hack hack. - l = sorted([f for f in glob.glob(os.path.join(config.AGORA_PATH, '**/????-??-??.md'), recursive=True)]) - return sorted([Node(f) for f in l], key=attrgetter('wikilink'), reverse=True) + l = all_subnodes() + # return sorted([sn for sn in l], key=attrgetter('wikilink'), reverse=True) + return sorted([f for f in glob.glob(os.path.join(config.AGORA_PATH, '**/????-??-??.md'), recursive=True)]) def nodes_by_wikilink(wikilink): nodes = [node for node in all_nodes() if node.wikilink == wikilink] return nodes +def subnodes_by_wikilink(wikilink): + subnodes = [subnode for subnode in all_subnodes() if subnode.wikilink == wikilink] + return subnodes + +def subnodes_by_user(user): + subnodes = [subnode for subnode in all_subnodes() if subnode.user == user] + return subnodes + +def subnode_by_uri(uri): + subnode = [subnode for subnode in all_subnodes() if subnode.uri == uri][0] + return subnode + def nodes_by_outlink(wikilink): nodes = [node for node in all_nodes() if wikilink in node.outlinks] return nodes + +def subnodes_by_outlink(wikilink): + subnodes = [subnode for subnode in all_subnodes() if wikilink in subnode.outlinks] + return subnodes diff --git a/app/templates/base.html b/app/templates/base.html index 4dc373f..e2252df 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -29,7 +29,10 @@ | - /nodes | /journals + /nodes + | /subnodes + | /journals + | /users help diff --git a/app/templates/index.html b/app/templates/index.html index bc4a903..2f7e861 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -18,12 +18,13 @@ {% block content %} Ahoy, matey!

-This is the Agora v0.5a.
+This is the Agora v0.5b.

This site is very much under construction, but feel free to look around:
    -
  • {{nodes}} lists all nodes in this Agora; currently these are mostly notes as volunteered by users.
  • +
  • {{nodes}} lists all nodes in this Agora, created out of aggregating subnodes (mostly notes currently) volunteered by users.
  • {{journals}} displays all journal entries.
  • +
  • {{users}} displays all users.
The [[wikilink]] is the heart of the Agora. /node/foo and /wikilink/foo will both render every node that responds to wikilink [[foo]]. For example: [[agora-help]]. diff --git a/app/templates/nodes_rendered.html b/app/templates/node_rendered.html similarity index 62% rename from app/templates/nodes_rendered.html rename to app/templates/node_rendered.html index 23c0049..8560f2d 100644 --- a/app/templates/nodes_rendered.html +++ b/app/templates/node_rendered.html @@ -16,22 +16,24 @@ {% extends "base.html" %} {% block content %} -{% if not nodes %} -No nodes found for '{{wikilink}}'. +{% if not subnodes %} +No node found for '{{wikilink}}'.

-If you contributed a node named '{{wikilink}}' to the Agora, here is where your node would be :) +If you contributed a subnode named '{{wikilink}}' to the Agora, this node would not be empty :)

Go back to /nodes? {% endif %} -{% for node in nodes %} -{{ node.dir }}: {{node.wikilink}}
-{{ node.content|markdown|linkify|safe }} +{% for subnode in subnodes %} +{{ subnode.uri }}: {{subnode.wikilink}}
+{{ subnode.content|markdown|linkify|safe }}
{% endfor %} + +
Backlinks:
-{% for node in backlinks %} -{{ node.dir }}: {{node.wikilink}}
+{% for subnode in backlinks %} +{{ subnode.uri }}: {{subnode.wikilink}}
{% endfor %} {% endblock %} diff --git a/app/templates/nodes.html b/app/templates/nodes.html index a457811..ae2a98c 100644 --- a/app/templates/nodes.html +++ b/app/templates/nodes.html @@ -18,6 +18,9 @@

Nodes

{% block content %} {% for node in nodes %} -{{ node.dir }}: {{node.wikilink}}
+{% for subnode in node.subnodes %}} +{% subnode.user %} +{% endfor %} +{{node.wikilink}}
{% endfor %} {% endblock %} diff --git a/app/templates/subnode_rendered.html b/app/templates/subnode_rendered.html new file mode 100644 index 0000000..634ed34 --- /dev/null +++ b/app/templates/subnode_rendered.html @@ -0,0 +1,35 @@ + + +{% extends "base.html" %} +{% block content %} +{% if not subnode %} +No subnode found matching '{{subnode}}'. +

+Go back to /nodes? +{% endif %} + +{{ subnode.uri }}: {{subnode.wikilink}}
+{{ subnode.content|markdown|linkify|safe }} +
+ +
+Backlinks:
+{% for subnode in backlinks %} +{{ subnode.uri }}: {{subnode.wikilink}}
+{% endfor %} + +{% endblock %} diff --git a/app/templates/subnodes.html b/app/templates/subnodes.html new file mode 100644 index 0000000..f639ef5 --- /dev/null +++ b/app/templates/subnodes.html @@ -0,0 +1,23 @@ + + +{% extends "base.html" %} +

Subnodes

+{% block content %} +{% for subnode in subnodes %} +(user: {{subnode.user}}) {{ subnode.uri }}: {{subnode.wikilink}}
+{% endfor %} +{% endblock %} diff --git a/app/templates/users.html b/app/templates/users.html new file mode 100644 index 0000000..9f758a5 --- /dev/null +++ b/app/templates/users.html @@ -0,0 +1,23 @@ + + +{% extends "base.html" %} +

Users

+{% block content %} +{% for user in users %} +{{user.uri}}
+{% endfor %} +{% endblock %}