Merge branch 'dev' into main

This commit is contained in:
Flancian 2020-11-14 20:16:04 +01:00
commit 504325eeb4
10 changed files with 237 additions and 30 deletions

View file

@ -22,18 +22,25 @@ bp = Blueprint('agora', __name__)
@bp.route('/') @bp.route('/')
def index(): 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') @bp.route('/help')
def help(): def help():
current_app.logger.warning('Not implemented.') 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 <a href="https://flancia.org/go/agora">code</a>.' return 'If I had implemented help already, here you\'d see documentation on all URL endpoints. For now, please refer to the <a href="https://flancia.org/go/agora">code</a>.'
@bp.route('/notes') # alias for now
@bp.route('/nodes') @bp.route('/nodes')
def nodes(): def nodes():
current_app.logger.warning('Config: %s' % config.AGORA_PATH) return render_template('nodes.html', nodes=db.all_nodes(include_journals=False))
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') @bp.route('/journals')
def journals(): def journals():
@ -44,11 +51,11 @@ def today():
today = datetime.datetime.now().date() today = datetime.datetime.now().date()
return redirect("https://anagora.org/node/%s" % today.strftime("%Y-%m-%d")) return redirect("https://anagora.org/node/%s" % today.strftime("%Y-%m-%d"))
@bp.route('/u/<username>') @bp.route('/u/<user>')
@bp.route('/user/<username>') @bp.route('/user/<user>')
def user(username): @bp.route('/@<user>')
current_app.logger.warning('Not implemented.') def user(user):
return 'If I had implemented reading user profiles already, here you would see user named "%s".' % escape(username) return render_template('subnodes.html', subnodes=db.subnodes_by_user(user))
@bp.route('/garden/<garden>') @bp.route('/garden/<garden>')
def garden(garden): def garden(garden):
@ -58,7 +65,11 @@ def garden(garden):
@bp.route('/node/<node>') @bp.route('/node/<node>')
@bp.route('/wikilink/<node>') # alias for now @bp.route('/wikilink/<node>') # alias for now
def wikilink(node): 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/<path: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/<user>/<asset>') @bp.route('/asset/<user>/<asset>')
def asset(user, asset): def asset(user, asset):

View file

@ -1,3 +1,7 @@
import os import os
import getpass import getpass
AGORA_PATH = os.path.join('/home', getpass.getuser(), 'agora') AGORA_PATH = os.path.join('/home', getpass.getuser(), 'agora')
# With trailing slash.
URL_BASE = "https://anagora.org/"

122
app/db.py
View file

@ -16,22 +16,84 @@ import glob
import re import re
import os import os
from . import config from . import config
from collections import defaultdict
from operator import attrgetter from operator import attrgetter
RE_WIKILINKS = re.compile('\[\[(.*?)\]\]') 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: 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 = []
def size(self):
return len(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): 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.wikilink = path_to_wikilink(path)
self.url = '/node/' + self.wikilink self.user = path_to_user(path)
with open(path) as f: with open(path) as f:
self.content = f.read() self.content = f.read()
self.outlinks = content_to_outlinks(self.content) 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 + '/', '') 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): def path_to_wikilink(path):
return os.path.splitext(os.path.basename(path))[0] return os.path.splitext(os.path.basename(path))[0]
@ -43,19 +105,63 @@ def content_to_outlinks(content):
else: else:
return [] return []
def all_nodes(): def all_subnodes():
l = sorted([f for f in glob.glob(os.path.join(config.AGORA_PATH, '**/*.md'), recursive=True)]) subnodes = [Subnode(f) for f in glob.glob(os.path.join(config.AGORA_PATH, '**/*.md'), recursive=True)]
return [Node(f) for f in l] return sorted(subnodes, key=lambda x: x.uri.lower())
def all_nodes(include_journals=True):
# first we fetch all subnodes, put them in a dict {wikilink -> [subnode]}.
# hack hack -- there's something in itertools better than this.
wikilink_to_subnodes = defaultdict(list)
for subnode in all_subnodes():
wikilink_to_subnodes[subnode.wikilink].append(subnode)
# then we iterate over its values and construct nodes for each list of subnodes.
nodes = []
for wikilink in wikilink_to_subnodes:
node = Node(wikilink)
node.subnodes = wikilink_to_subnodes[wikilink]
nodes.append(node)
# remove journals if so desired.
if not include_journals:
nodes = [node for node in nodes if not re.search('[0-9]+?-[0-9]+?-[0-9]+?', node.wikilink)]
# TODO: experiment with other ranking.
# return sorted(nodes, key=lambda x: -x.size())
return sorted(nodes, key=lambda x: x.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=lambda x: x.uri.lower())
def all_journals(): def all_journals():
# hack hack. # hack hack.
l = sorted([f for f in glob.glob(os.path.join(config.AGORA_PATH, '**/????-??-??.md'), recursive=True)]) nodes = all_nodes()
return sorted([Node(f) for f in l], key=attrgetter('wikilink'), reverse=True) nodes = [node for node in nodes if re.search('[0-9]+?-[0-9]+?-[0-9]+?', node.wikilink)]
return sorted(nodes, key=attrgetter('wikilink'), reverse=True)
def nodes_by_wikilink(wikilink): def nodes_by_wikilink(wikilink):
nodes = [node for node in all_nodes() if node.wikilink == wikilink] nodes = [node for node in all_nodes() if node.wikilink == wikilink]
return nodes 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): def nodes_by_outlink(wikilink):
nodes = [node for node in all_nodes() if wikilink in node.outlinks] nodes = [node for node in all_nodes() if wikilink in node.outlinks]
return nodes return nodes
def subnodes_by_outlink(wikilink):
subnodes = [subnode for subnode in all_subnodes() if wikilink in subnode.outlinks]
return subnodes

View file

@ -29,7 +29,10 @@
<a href="/"><img src="/static/img/agora.png" style="vertical-align: top" width="19" height="19"></a> | <a href="/"><img src="/static/img/agora.png" style="vertical-align: top" width="19" height="19"></a> |
</span> </span>
<span align=left> <span align=left>
<a href="/nodes">/nodes</a> | <a href="/journals">/journals</a> <a href="/nodes">/nodes</a>
| <a href="/subnodes">/subnodes</a>
| <a href="/journals">/journals</a>
| <a href="/users">/users</a>
</span> </span>
<span style="vertical-align: bottom; float:right"> <span style="vertical-align: bottom; float:right">
<a href="/node/agora-help">help</a> <a href="/node/agora-help">help</a>

View file

@ -22,7 +22,7 @@ This is the <strong>Agora v0.5.1</strong>.<br />
<br /> <br />
This site is very much under construction, but feel free to look around: This site is very much under construction, but feel free to look around:
<ul> <ul>
<li><a href="{{nodes}}">{{nodes}}</a> lists all nodes in this Agora; a node is the set of all subnodes with a given [[wikilink]] (title/topic/entity).</li> <li><a href="{{nodes}}">{{nodes}}</a> lists all nodes in this Agora; a node is the set of all subnodes with a given title, or otherwise about the same entity. Subnodes can come from a variety of sources.</li>
<li><a href="{{subnodes}}">{{subnodes}}</a> lists all subnodes in this Agora; currently these are mostly notes as volunteered by users.</li> <li><a href="{{subnodes}}">{{subnodes}}</a> lists all subnodes in this Agora; currently these are mostly notes as volunteered by users.</li>
<li><a href="{{journals}}">{{journals}}</a> displays all journal entries (these are notes matching YYYY-MM-DD).</li> <li><a href="{{journals}}">{{journals}}</a> displays all journal entries (these are notes matching YYYY-MM-DD).</li>
<li><a href="{{users}}">{{users}}</a> displays all users in this Agora, click through to see their subnodes.</li> <li><a href="{{users}}">{{users}}</a> displays all users in this Agora, click through to see their subnodes.</li>
@ -31,5 +31,5 @@ This site is very much under construction, but feel free to look around:
The [[<a href="/node/wikilink">wikilink</a>]] is the heart of the Agora. <a href="/node/foo">/node/foo</a> and <a href="/wikilink/foo">/wikilink/foo</a> will both render every node that responds to wikilink [[<a href="/node/foo">foo</a>]]. For example: [[<a href="/node/agora-help">agora-help</a>]]. The [[<a href="/node/wikilink">wikilink</a>]] is the heart of the Agora. <a href="/node/foo">/node/foo</a> and <a href="/wikilink/foo">/wikilink/foo</a> will both render every node that responds to wikilink [[<a href="/node/foo">foo</a>]]. For example: [[<a href="/node/agora-help">agora-help</a>]].
<br /><br /> <br /><br />
For more information, please visit the <a href="https://flancia.org/go/agora">Agora repository.</a> For more information, please visit the <a href="https://flancia.org/go/agora">Agora repository.</a> If you're interested in knowing what's coming, please refer to <a href="https://anagora.org/node/agora-plan">Agora plan</a>.
{% endblock %} {% endblock %}

View file

@ -16,22 +16,24 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
{% if not nodes %} {% if not subnodes %}
No nodes found for '{{wikilink}}'. No node found for '{{wikilink}}'.
<br /><br /> <br /><br />
If you <a href="https://flancia.org/go/agora">contributed a node</a> named '{{wikilink}}' to the Agora, here is where your node would be :) If you <a href="https://flancia.org/go/agora">contributed a subnode</a> named '{{wikilink}}' to the Agora, this node would not be empty :)
<br /><br /> <br /><br />
Go back to <a href="/nodes">/nodes</a>? Go back to <a href="/nodes">/nodes</a>?
{% endif %} {% endif %}
{% for node in nodes %} {% for subnode in subnodes %}
{{ node.dir }}: <a href="{{node.url}}">{{node.wikilink}}</a><br /> {{ subnode.uri }}: <a href="{{subnode.url}}">{{subnode.wikilink}}</a><br />
{{ node.content|markdown|linkify|safe }} {{ subnode.content|markdown|linkify|safe }}
<hr /> <hr />
{% endfor %} {% endfor %}
<br />
<em>Backlinks:</em><br /> <em>Backlinks:</em><br />
{% for node in backlinks %} {% for subnode in backlinks %}
{{ node.dir }}: <a href="{{node.url}}">{{node.wikilink}}</a><br /> {{ subnode.uri }}: <a href="{{subnode.url}}">{{subnode.wikilink}}</a><br />
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View file

@ -18,6 +18,6 @@
<h1> Nodes </h1> <h1> Nodes </h1>
{% block content %} {% block content %}
{% for node in nodes %} {% for node in nodes %}
{{ node.dir }}: <a href="{{node.url}}">{{node.wikilink}}</a><br /> <a href="{{node.url}}">{{node.wikilink}}</a> ({{node.size()}})<br />
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,35 @@
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
{% extends "base.html" %}
{% block content %}
{% if not subnode %}
No subnode found matching '{{subnode}}'.
<br /><br />
Go back to <a href="/nodes">/nodes</a>?
{% endif %}
{{ subnode.uri }}: <a href="{{subnode.url}}">{{subnode.wikilink}}</a><br />
{{ subnode.content|markdown|linkify|safe }}
<hr />
<br />
<em>Backlinks:</em><br />
{% for subnode in backlinks %}
{{ subnode.uri }}: <a href="{{subnode.url}}">{{subnode.wikilink}}</a><br />
{% endfor %}
{% endblock %}

View file

@ -0,0 +1,23 @@
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
{% extends "base.html" %}
<h1> Subnodes </h1>
{% block content %}
{% for subnode in subnodes %}
(user: {{subnode.user}}) {{ subnode.uri }}: <a href="{{subnode.url}}">{{subnode.wikilink}}</a><br />
{% endfor %}
{% endblock %}

23
app/templates/users.html Normal file
View file

@ -0,0 +1,23 @@
<!--
Copyright 2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
{% extends "base.html" %}
<h1> Users </h1>
{% block content %}
{% for user in users %}
<a href="{{user.url}}">{{user.uri}}</a><br />
{% endfor %}
{% endblock %}