owl/owl.py

163 lines
4.8 KiB
Python
Raw Permalink Normal View History

2021-03-28 13:21:31 +03:00
__version__ = '1.1'
2021-03-23 23:11:57 +03:00
import os
import re
from functools import wraps
from datetime import timedelta
import pygments
2021-03-28 13:21:31 +03:00
from markdown import Markdown
2021-03-23 23:11:57 +03:00
from flask import Flask
from flask import request
from flask import session
from flask import redirect
from flask import render_template
from flask import send_from_directory
from flask_bcrypt import Bcrypt
from config import Config
app = Flask(__name__)
app.config.from_object(Config)
app.permanent_session_lifetime = timedelta(hours=24)
bcrypt = Bcrypt(app)
def read_file(filepath: str) -> str:
try:
with open(filepath, 'r') as file:
return file.read()
except IOError:
return 'Error: Cannot read file: {}'.format(filepath)
2021-03-28 13:21:31 +03:00
def get_path(path: str) -> str:
return os.path.join(app.config['MARKDOWN_ROOT'], path)
2021-03-23 23:11:57 +03:00
def render_html(filepath: str) -> str:
2021-03-28 13:21:31 +03:00
html = Markdown(
extensions = app.config['MARKDOWN_EXTRAS'],
extension_configs = app.config['MARKDOWN_EXTRAS_CONFIGS']
)
return html.convert(
read_file(get_path(filepath))
)
2021-03-23 23:11:57 +03:00
def parse_title_from_markdown(filepath: str) -> str:
# This function parses article title from first level heading.
# It returns the occurrence of the first heading, and there
# can be nothing before it except empty lines and spaces.
2021-03-28 13:21:31 +03:00
article = read_file(get_path(filepath))
2021-03-23 23:11:57 +03:00
pattern = re.compile(r'^\s*#\s.*')
if pattern.search(article):
return pattern.search(article).group().strip()[2:]
else:
2021-03-28 13:21:31 +03:00
return 'Error: Cannot parse title from file: {}'.format(filepath)
2021-03-23 23:11:57 +03:00
def parse_content_links(filepath: str) -> list:
# This function returns a list of links from a Markdown file.
# Only links contained in the list (ul ol li) are parsed.
r = re.compile(r'(.*(-|\+|\*|\d).?\[.*\])(\(.*\))', re.MULTILINE)
links = []
2021-03-28 13:21:31 +03:00
for tpl in r.findall(read_file(get_path(filepath))):
2021-03-23 23:11:57 +03:00
for item in tpl:
if re.match(r'\(.*\)', item):
if item == '(/)':
item = '/' # This is a crutch for fixing the root url
# which for some reason continues to contain
# parentheses after re.match(r'').
if not item[1:-1].endswith('/'):
item = item[1:-1] + '/'
links.append(item)
return links
def check_password(password: str) -> bool:
2021-03-28 13:21:31 +03:00
if os.path.exists(app.config['PASSWORD_FILE']):
pw_hash = read_file(app.config['PASSWORD_FILE'])
2021-03-23 23:11:57 +03:00
return bcrypt.check_password_hash(pw_hash, password)
else:
return False
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.j2'), 404
@app.context_processor
def utility_processor():
def get_len(list: list) -> int:
return len(list)
def get_title(path: str) -> str:
return parse_title_from_markdown(path[:-1] + '.md')
return dict(get_title = get_title, get_len = get_len)
def login_required(func):
@wraps(func)
def wrap(*args, **kwargs):
if app.config['SIGN_IN']:
if 'logged_in' in session:
return func(*args, **kwargs)
else:
return redirect('/signin/')
else:
return func(*args, **kwargs)
return wrap
@app.route('/signin/', methods = ['GET', 'POST'])
def signin():
if request.method == 'POST':
if check_password(request.form['password']):
session['logged_in'] = True
return redirect('/', 302)
else:
return render_template('signin.j2', wrong_pw = True)
else:
return render_template('signin.j2')
@app.route('/signout/')
@login_required
def signout():
session.pop('logged_in', None)
return redirect('/signin/')
@app.route('/')
@login_required
def index():
return render_template(
'index.j2',
title = parse_title_from_markdown('HOME.md'),
article = render_html('HOME.md'),
contents = render_html('CONTENTS.md'),
current_path = '/',
links = parse_content_links('CONTENTS.md')
)
@app.route('/<path:path>/')
@login_required
def get_article(path):
2021-03-28 13:21:31 +03:00
if os.path.exists(get_path(path) + '.md'):
2021-03-23 23:11:57 +03:00
return render_template(
'index.j2',
title = parse_title_from_markdown(path + '.md'),
article = render_html(path + '.md'),
contents = render_html('CONTENTS.md'),
current_path = request.path,
links = parse_content_links('CONTENTS.md')
)
else:
return page_not_found(404)
@app.route('/<path:path>.md')
@login_required
def download_article(path):
2021-03-28 13:21:31 +03:00
if os.path.exists(get_path(path) + '.md') \
2021-03-23 23:11:57 +03:00
and app.config['MARKDOWN_DOWLOADS']:
return send_from_directory(
app.config['MARKDOWN_ROOT'],
path + '.md'
)
else:
return page_not_found(404)
if __name__ == '__main__':
app.run()