WikiNote3/wikinote/__init__.py

296 lines
7.8 KiB
Python

# WikiNote3
# Copyright © 2020, 2022 Lee Yingtong Li (RunasSudo)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from .markup import WNMarkdown
import flask
from werkzeug.security import safe_join
import os
import pickle
import xml.etree.ElementTree as ET
app = flask.Flask(__name__, template_folder='jinja2')
if os.path.exists('index.pickle'):
try:
with open('index.pickle', 'rb') as f:
index = pickle.load(f)
except:
print('Error loading index.pickle')
import traceback; traceback.print_exc()
def get_children(path):
def child_name(c):
return os.path.splitext(c)[0]
children = []
if os.path.isdir(safe_join('./data/pages', path)):
for child in os.listdir(safe_join('./data/pages', path)):
child_path = safe_join('./data/pages', path, child)
if child.startswith('_'):
continue
if os.path.islink(child_path):
children.append((child_name(child), child_name(os.path.split(os.path.realpath(child_path))[1])))
else:
children.append((child_name(child), None))
children.sort(key=lambda x: x[0][1:] if x[0].startswith('(') else x[0])
return children
@app.route('/')
def index_page():
children = get_children('')
if not os.path.exists('./data/pages/Home.md'):
return flask.render_template('page_404.html', page={
'path': '',
'title': 'Home',
'children': children
})
with(open(fname, 'r')) as f:
page_source = f.read()
md = WNMarkdown()
page_content = md.convert(page_source)
return flask.render_template('page_rendered.html', page={
'path': '',
'title': 'Home',
'content': page_content,
'toc': md.toc_tokens,
'meta': md.meta,
'children': children
})
@app.route('/page/<path:path>')
def page_view(path):
fname = safe_join('./data/pages', path) + '.md'
if os.path.islink(fname):
redir_page = '/'.join(path.split('/')[:-1]) + '/' + os.path.splitext(os.readlink(fname))[0]
return flask.redirect(flask.url_for('page_view', path=redir_page))
children = get_children(path)
if not os.path.exists(fname):
return flask.render_template('page_404.html', page={
'path': path,
'title': path.split('/')[-1],
'children': children
})
with(open(fname, 'r')) as f:
page_source = f.read()
md = WNMarkdown()
#page_content = md.convert(page_source)
root = md.parse(page_source)
# Add expand/collapse buttons to h2
for elem in root:
if elem.tag == 'h2':
elem.text += ' '
btn = ET.SubElement(elem, 'button')
btn.text = '±'
btn.set('class', 'expcol')
btn.set('onclick', 'onClickExpCol(this);')
# Handle collapsed sections
if root == '':
page_content = ''
else:
if 'collapsed' in flask.request.args:
for elem in root:
if elem.tag == 'section':
elem.set('class', 'collapsed')
page_content = md.serialise(root)
return flask.render_template('page_rendered.html', page={
'path': path,
'title': md.meta['title'] if 'title' in md.meta else path.split('/')[-1],
'content': page_content,
'toc': md.toc_tokens,
'meta': md.meta,
'children': children,
'xrefs': index['xrefs'].get(path, [])
}, collapsed='collapsed' in flask.request.args)
@app.route('/preview/<path:path>')
def page_preview(path):
fname = safe_join('./data/pages', path) + '.md'
if not os.path.exists(fname):
return ''
with(open(fname, 'r')) as f:
page_source = f.read()
md = WNMarkdown()
page_root = md.parse(page_source)
# Delete all but first section
for section in list(page_root[1:]):
page_root.remove(section)
# Delete all but introductory text
for elem in list(page_root[0][1:]):
page_root[0].remove(elem)
# Delete footnotes
def walk_tree(elem):
last_child = None
for child in list(elem):
if child.get('class') == 'footnote-ref':
if child.tail:
# Combine tail text
if last_child is not None:
last_child.tail += child.tail
else:
elem.text += child.tail
elem.remove(child)
walk_tree(child)
else:
walk_tree(child)
last_child = child
walk_tree(page_root[0])
page_content = md.serialise(page_root)
return page_content
@app.route('/image/<name>')
def image_view(name):
fname = safe_join(os.getcwd(), './data/images', name[0].upper(), name)
return flask.send_file(fname)
@app.route('/image/<name>/about')
def image_about(name):
fname = safe_join(os.getcwd(), './data/images', name[0].upper(), os.path.splitext(name)[0] + '.md')
with(open(fname, 'r')) as f:
page_source = f.read()
md = WNMarkdown()
page_content = md.convert(page_source)
return flask.render_template('image_about.html', page={
'path': name,
'title': name,
'content': page_content,
'meta': md.meta
})
@app.route('/tag/')
def tags_list():
return flask.render_template('tags_list.html', tags=sorted(index['tags'].keys()))
@app.route('/tag/<name>')
def tag_view(name):
if name not in index['tags']:
return flask.render_template('tag_404.html', name=name)
pages = index['tags'][name]
pages.sort(key=lambda p: p['kind'])
pages.sort(key=lambda p: p['path'])
return flask.render_template('tag_view.html', name=name, pages=pages)
@app.cli.command('index')
def cli_index():
app.config['SERVER_NAME'] = 'localhost'
with app.app_context():
tags = {}
xrefs = {}
base_path = './data/pages'
for dirpath, dirnames, filenames in os.walk(base_path):
for fname in filenames:
if fname.endswith('.md') and not os.path.islink(safe_join(dirpath, fname)):
page_path = dirpath[len(base_path)+1:] + '/' + fname[:-3]
with(open(safe_join(dirpath, fname), 'r')) as f:
page_source = f.read()
md = WNMarkdown()
md.convert(page_source)
for tag in md.meta.get('tags', []):
if tag not in tags:
tags[tag] = []
tags[tag].append({'kind': 'page', 'path': page_path})
for ref in md.meta.get('refs', []):
fname_ref = safe_join('./data/pages', ref) + '.md'
if os.path.islink(fname_ref):
ref = '/'.join(ref.split('/')[:-1]) + '/' + os.path.splitext(os.readlink(fname_ref))[0]
if ref not in xrefs:
xrefs[ref] = []
if page_path not in xrefs[ref]:
xrefs[ref].append(page_path)
base_path = './data/images'
for dirpath, dirnames, filenames in os.walk(base_path):
for fname in filenames:
if fname.endswith('.md'):
continue
md_path = safe_join(dirpath, os.path.splitext(fname)[0] + '.md')
if os.path.exists(md_path):
with(open(md_path, 'r')) as f:
page_source = f.read()
md = WNMarkdown()
md.convert(page_source)
for tag in md.meta.get('tags', []):
if tag not in tags:
tags[tag] = []
tags[tag].append({'kind': 'image', 'path': fname})
with open('index.pickle', 'wb') as f:
pickle.dump({
'tags': tags,
'xrefs': xrefs
}, f)
@app.cli.command('redlinks')
def cli_redlinks():
app.config['SERVER_NAME'] = 'localhost'
redlinks = set()
with app.app_context():
base_path = './data/pages'
for dirpath, dirnames, filenames in os.walk(base_path):
for fname in filenames:
if fname.endswith('.md'):
page_path = dirpath[len(base_path)+1:] + '/' + fname[:-3]
with(open(safe_join(dirpath, fname), 'r')) as f:
page_source = f.read()
md = WNMarkdown()
md.convert(page_source)
for redlink in md.meta.get('redlinks', []):
redlinks.add(redlink)
redlinks = sorted(list(redlinks))
for redlink in redlinks:
print(redlink)