'
+
+
+def xanapage_span_html(
+ num: int,
+ text: str,
+ url: str,
+ start: int,
+ length: int,
+ highlight: bool = True,
+ censor: bool = False,
+) -> str:
+ """Generate HTML to represent a span."""
cls = []
if highlight:
- cls = ['xanapagetransclusion', 'transclusion']
- html_class = ' class="{}"'.format(' '.join(cls)) if cls else ''
+ cls = ["xanapagetransclusion", "transclusion"]
+ html_class = f""" class="{' '.join(cls)}""" if cls else ""
- html = '{}'.format(num, html_class, escape(url), start, length, text)
+ html = (
+ f'{text}'
+ )
if censor:
- return '' + html + ''
+ return '' + html + ""
else:
return html
-def parse_sourcedoc_facet(facet):
- leg = facet[0]
- prefix = 'sourcedoc: '
- assert leg.startswith(prefix)
- return leg[len(prefix):]
-def parse_xanapage_facet(facet):
+def parse_sourcedoc_facet(facet: list[str]) -> str:
+ """Parse sourcedoc facet."""
leg = facet[0]
- prefix = 'xanapage: '
+ prefix = "sourcedoc: "
assert leg.startswith(prefix)
- return leg[len(prefix):]
+ return leg[len(prefix) :]
-def parse_link(link_text):
+
+def parse_xanapage_facet(facet: list[str]) -> str:
+ """Parse xanapage facet."""
+ leg = facet[0]
+ prefix = "xanapage: "
+ assert leg.startswith(prefix)
+ return leg[len(prefix) :]
+
+
+def parse_link(link_text: str) -> dict[str, str | None | list[list[str]]]:
link_type = None
- expect = 'link_type'
+ expect = "link_type"
facets = []
for line in link_text.splitlines():
- line = re_comment.sub('', line).strip()
+ line = re_comment.sub("", line).strip()
if not line:
continue
- if expect == 'link_type':
- if line.startswith('type='):
+ if expect == "link_type":
+ if line.startswith("type="):
link_type = line[5:]
- expect = 'facets'
+ expect = "facets"
continue
- if expect != 'facets':
+ if expect != "facets":
# print("unrecognized:", line)
continue
m = re_facet.match(line)
if m:
- legs = []
+ legs: list[str] = []
facets.append(legs)
if m.group(1):
line = m.group(1)
else:
continue
- if legs and legs[-1] == 'span:' and line.startswith('http'):
- legs[-1] += ' ' + line
+ if legs and legs[-1] == "span:" and line.startswith("http"):
+ legs[-1] += " " + line
else:
legs.append(line.strip())
- return {'type': link_type, 'facets': facets}
-
-
+ return {"type": link_type, "facets": facets}
diff --git a/sourcing/span.py b/sourcing/span.py
index 5d0a6e4..9c3b164 100644
--- a/sourcing/span.py
+++ b/sourcing/span.py
@@ -1,21 +1,35 @@
+"""Span."""
+
+import typing
+
import attr
+import attr._make
-def greater_than_zero(instance, attribute, value):
+
+def greater_than_zero(instance: "Span", attribute: typing.Any, value: int) -> None:
+ """Value is greater than zero."""
if value <= 0:
- raise ValueError('must be greater than 0')
+ raise ValueError("must be greater than 0")
-def is_positive(instance, attribute, value):
+
+def is_positive(instance: "Span", attribute: typing.Any, value: int) -> None:
+ """Value is positive."""
if value < 0:
- raise ValueError('must be positive')
+ raise ValueError("must be positive")
+
@attr.s
class Span:
- url: int = attr.ib()
+ """Span."""
+
+ url: str = attr.ib()
start: int = attr.ib(validator=is_positive)
length: int = attr.ib(validator=greater_than_zero)
def end(self) -> int:
+ """End position of span."""
return self.start + self.length
def for_edl(self) -> str:
- return f'{self.url},start={self.start},length={self.length}'
+ """Generate URL parameters for EDL."""
+ return f"{self.url},start={self.start},length={self.length}"
diff --git a/sourcing/static/bootstrap b/sourcing/static/bootstrap
index fe0f86b..12ef71f 120000
--- a/sourcing/static/bootstrap
+++ b/sourcing/static/bootstrap
@@ -1 +1 @@
-/usr/share/javascript/bootstrap
\ No newline at end of file
+/usr/share/javascript/bootstrap4
\ No newline at end of file
diff --git a/sourcing/url.py b/sourcing/url.py
index a4f733b..4a1f693 100644
--- a/sourcing/url.py
+++ b/sourcing/url.py
@@ -1,42 +1,61 @@
-import requests
-from .model import Item
import os.path
import re
+import typing
+
+import requests
+
+from .model import Item
project_dir = os.path.dirname(os.path.dirname(__file__))
-cache_location = os.path.join(project_dir, 'cache')
+cache_location = os.path.join(project_dir, "cache")
-re_colon_slash = re.compile('[/:]+')
+re_colon_slash = re.compile("[/:]+")
-def url_filename(url):
- return re_colon_slash.sub('_', url)
-def get_text(url):
+def url_filename(url: str) -> str:
+ """Generate filename from URL."""
+ return re_colon_slash.sub("_", url)
+
+
+class ExternalText(typing.TypedDict):
+ """Text from external URL."""
+
+ url: str
+ text: str
+ heading: str
+ length: int
+
+
+def get_text(url: str) -> ExternalText:
+ """Get text from URL and return as dict."""
# assume UTF-8
text = get_url(url)
- heading = url.rsplit('/', 1)[-1]
+ heading = url.rsplit("/", 1)[-1]
return {
- 'url': url,
- 'text': text,
- 'heading': heading,
- 'length': len(text),
+ "url": url,
+ "text": text,
+ "heading": heading,
+ "length": len(text),
}
-def get_url(url):
+
+def get_url(url: str) -> str:
+ """Read a URL and return the content."""
item = Item.from_external(url)
if item:
- return item.text
+ return typing.cast(str, item.text)
+
content = requests.get(url).content
- return content.decode(errors='replace')
+ return content.decode(errors="replace")
filename = os.path.join(cache_location, url_filename(url))
if os.path.exists(filename):
- content = open(filename, 'rb').read()
+ content = open(filename, "rb").read()
else:
content = requests.get(url).content
- open(filename, 'wb').write(content)
+ open(filename, "wb").write(content)
- return content.decode(errors='replace')
+ return content.decode(errors="replace")
diff --git a/sourcing/utils.py b/sourcing/utils.py
index 519468f..e6d903a 100644
--- a/sourcing/utils.py
+++ b/sourcing/utils.py
@@ -1,25 +1,29 @@
-import humanize
-from datetime import date, timedelta
+from datetime import date, datetime, timedelta
-def display_datetime(dt):
+import humanize
+
+
+def display_datetime(dt: datetime) -> str:
+ """Render datetime as a string for display."""
if dt is None:
- return 'n/a'
+ return "n/a"
today = date.today()
if today - dt.date() < timedelta(days=1):
return humanize.naturaltime(dt)
else:
- return dt.strftime('%a, %d %b %Y')
+ return dt.strftime("%a, %d %b %Y")
-def nbsp_at_start(line):
- ''' Protect spaces at the start of a string. '''
+
+def nbsp_at_start(line: str) -> str:
+ """Protect spaces at the start of a string."""
space_count = 0
for c in line:
- if c != ' ':
+ if c != " ":
break
space_count += 1
# return Markup(' ') * space_count + line[space_count:]
- return '\u00A0' * space_count + line[space_count:]
+ return "\u00A0" * space_count + line[space_count:]
-def protect_start_spaces(text):
- return '\n'.join(nbsp_at_start(line) for line in text.splitlines())
+def protect_start_spaces(text: str) -> str:
+ return "\n".join(nbsp_at_start(line) for line in text.splitlines())
diff --git a/sourcing/view.py b/sourcing/view.py
index d3d7d25..2b63681 100644
--- a/sourcing/view.py
+++ b/sourcing/view.py
@@ -1,44 +1,63 @@
-from flask import (Blueprint, render_template, request, redirect, flash,
- url_for, abort, jsonify, Response, current_app)
-from flask_login import (login_user, current_user, logout_user,
- login_required, LoginManager)
-from .forms import (LoginForm, SignupForm, AccountSettingsForm,
- UploadSourceDocForm, SourceDocForm, ItemForm,
- ForgotPasswordForm, PasswordForm)
-from .model import User, SourceDoc, Item, XanaPage, XanaLink, Reference
-from .parse import parse_xanapage_facet, parse_link
-from .url import get_url
-from .mail import send_mail
-from .edl import fulfil_edl_with_sources, fulfil_edl, parse_edl, fulfil_edl_with_links
-from .span import Span
-from .edit import apply_edits
-from .database import session
-from .text import iter_lines, add_highlight
-from werkzeug.debug.tbtools import get_current_traceback
-from jinja2 import evalcontextfilter, Markup
-from functools import wraps
-from .utils import nbsp_at_start
-from itsdangerous import URLSafeTimedSerializer
-# from sqlalchemy_continuum import version_class
-
import json
import re
+import typing
+from functools import wraps
+
+import flask
+from flask_login import (
+ LoginManager,
+ current_user,
+ login_required,
+ login_user,
+ logout_user,
+)
+from itsdangerous import URLSafeTimedSerializer
+from werkzeug.wrappers import Response
+
+from .database import session
+from .edit import apply_edits
+from .edl import fulfil_edl, fulfil_edl_with_links, fulfil_edl_with_sources, parse_edl
+from .forms import (
+ AccountSettingsForm,
+ ForgotPasswordForm,
+ ItemForm,
+ LoginForm,
+ PasswordForm,
+ SignupForm,
+ SourceDocForm,
+)
+from .mail import send_mail
+from .model import Item, Reference, SourceDoc, User, XanaLink, XanaPage
+from .parse import parse_link, parse_xanapage_facet
+from .span import Span
+from .text import add_highlight, iter_lines
+from .url import get_url
+from .utils import nbsp_at_start
+
+# from werkzeug.debug.tbtools import get_current_traceback
+# from jinja2 import evalcontextfilter, Markup
+# from sqlalchemy_continuum import version_class
+
login_manager = LoginManager()
-login_manager.login_view = '.login'
-re_paragraph = re.compile(r'(?:\r\n|\r|\n){2,}')
-re_spanpointer = re.compile(r'([A-Za-z0-9]+),start=(\d+),length=(\d+)')
-bp = Blueprint('view', __name__)
+login_manager.login_view = ".login"
+re_paragraph = re.compile(r"(?:\r\n|\r|\n){2,}")
+re_spanpointer = re.compile(r"([A-Za-z0-9]+),start=(\d+),length=(\d+)")
+bp = flask.Blueprint("view", __name__)
-def init_app(app):
+
+def init_app(app: flask.Flask) -> None:
+ """Initialise app."""
login_manager.init_app(app)
app.register_blueprint(bp)
- @app.template_filter()
- @evalcontextfilter
- def newline_html(eval_ctx, value):
- return u'\n\n'.join(Markup(u'') + p.replace('\n', Markup('
')) + Markup(u'
')
- for p in re_paragraph.split(value))
+
+# @app.template_filter()
+# @evalcontextfilter
+# def newline_html(eval_ctx, value):
+# return u'\n\n'.join(Markup(u'') + p.replace('\n', Markup('
')) + Markup(u'
')
+# for p in re_paragraph.split(value))
+
@login_manager.user_loader
def load_user(user_id):
@@ -46,270 +65,314 @@ def load_user(user_id):
# where do we redirect after signup is complete
-view_after_signup = '.home'
+view_after_signup = ".home"
+
@bp.context_processor
def inject_user():
return dict(current_user=current_user)
+
def show_errors(f):
@wraps(f)
def wrapper(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception:
- traceback = get_current_traceback(skip=1, show_hidden_frames=False,
- ignore_system_exceptions=True)
- return traceback.render_full().encode('utf-8', 'replace')
+ traceback = get_current_traceback(
+ skip=1, show_hidden_frames=False, ignore_system_exceptions=True
+ )
+ return traceback.render_full().encode("utf-8", "replace")
+
return wrapper
-@bp.route('/')
-def home():
+
+@bp.route("/")
+def home() -> str:
+ """Home page."""
docs = Item.query.order_by(Item.created)
docs_info = []
for item in docs:
cur = {
- 'user': item.user.username,
- 'id': item.id,
- 'type': item.type,
- 'year': item.created.year,
+ "user": item.user.username,
+ "id": item.id,
+ "type": item.type,
+ "year": item.created.year,
}
- if item.type == 'xanalink':
- cur['link_type'] = item.link_type
+ if item.type == "xanalink":
+ cur["link_type"] = item.link_type
docs_info.append(cur)
users = [item_user.username for item_user in User.query]
- years = sorted({item['year'] for item in docs_info})
+ years = sorted({item["year"] for item in docs_info})
- link_types = {item['link_type'] for item in docs_info if item.get('link_type')}
+ link_types = {item["link_type"] for item in docs_info if item.get("link_type")}
- return render_template('home.html',
- docs=docs,
- link_types=link_types,
- years=years,
- docs_info=docs_info,
- nbsp_at_start=nbsp_at_start,
- users=users)
+ return flask.render_template(
+ "home.html",
+ docs=docs,
+ link_types=link_types,
+ years=years,
+ docs_info=docs_info,
+ nbsp_at_start=nbsp_at_start,
+ users=users,
+ )
-@bp.route('/password_reset', methods=['GET', 'POST'])
+
+@bp.route("/password_reset", methods=["GET", "POST"])
def password_reset():
- site_name = 'perma.pub' # FIXME: move to config
+ site_name = "perma.pub" # FIXME: move to config
form = ForgotPasswordForm()
if not form.validate_on_submit():
- return render_template('auth/password_reset.html', form=form)
- ts = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
+ return flask.render_template("auth/password_reset.html", form=form)
+ ts = URLSafeTimedSerializer(flask.current_app.config["SECRET_KEY"])
user = User.lookup_user_or_email(form.user_or_email.data)
if user:
- token = ts.dumps(user.id, salt='password-reset')
- reset_link = url_for('.reset_with_token', token=token, _external=True)
- reset_mail = render_template('mail/password_reset.txt',
- reset_link=reset_link,
- site_name=site_name,
- user=user)
- subject = 'Password reset on ' + site_name
+ token = ts.dumps(user.id, salt="password-reset")
+ reset_link = flask.url_for(".reset_with_token", token=token, _external=True)
+ reset_mail = flask.render_template(
+ "mail/password_reset.txt",
+ reset_link=reset_link,
+ site_name=site_name,
+ user=user,
+ )
+ subject = "Password reset on " + site_name
send_mail(user, subject, reset_mail)
- return redirect(url_for('.password_reset_sent'))
+ return flask.redirect(flask.url_for(".password_reset_sent"))
-@bp.route('/password_reset/sent', methods=['GET', 'POST'])
+
+@bp.route("/password_reset/sent", methods=["GET", "POST"])
def password_reset_sent():
- return render_template('auth/password_reset_sent.html')
+ return flask.render_template("auth/password_reset_sent.html")
-@bp.route('/reset/', methods=['GET', 'POST'])
+
+@bp.route("/reset/", methods=["GET", "POST"])
def reset_with_token(token):
- ts = URLSafeTimedSerializer(current_app.config["SECRET_KEY"])
+ ts = URLSafeTimedSerializer(flask.current_app.config["SECRET_KEY"])
try:
- user_id = ts.loads(token, salt='password-reset', max_age=86400)
+ user_id = ts.loads(token, salt="password-reset", max_age=86400)
except Exception:
- abort(404)
+ flask.abort(404)
form = PasswordForm()
if not form.validate_on_submit():
- return render_template('auth/password_reset_confirm.html', form=form)
+ return flask.render_template("auth/password_reset_confirm.html", form=form)
user = User.query.get(user_id)
user.set_password(form.password.data)
session.add(user)
session.commit()
- return redirect(url_for('.password_reset_complete'))
+ return flask.redirect(flask.url_for(".password_reset_complete"))
-@bp.route('/reset/done')
-def password_reset_complete():
- return render_template('auth/password_reset_complete.html')
-@bp.route('/source_doc_upload', methods=["POST"])
+@bp.route("/reset/done")
+def password_reset_complete() -> str:
+ return flask.render_template("auth/password_reset_complete.html")
+
+
+@bp.route("/source_doc_upload", methods=["POST"])
@show_errors
def source_doc_upload():
- f = request.files['sourcedoc_file']
+ f = flask.request.files["sourcedoc_file"]
text = f.read()
doc = SourceDoc(text=text, user=current_user, filename=f.filename)
session.add(doc)
session.commit()
- flash('new source document uploaded')
- return redirect(doc.url)
+ flask.flash("new source document uploaded")
+ return flask.redirect(doc.url)
-@bp.route('/about')
-def about():
- return render_template('about.html')
-@bp.route('/contact')
-def contact():
- return render_template('contact.html')
+@bp.route("/about")
+def about() -> str:
+ """About page."""
+ return flask.render_template("about.html")
-def redirect_to_home():
- return redirect(url_for('.home'))
-@bp.route('/login', methods=['GET', 'POST'])
-def login():
- form = LoginForm(next=request.args.get('next'))
+@bp.route("/contact")
+def contact() -> str:
+ """Contact page."""
+ return flask.render_template("contact.html")
+
+
+def redirect_to_home() -> Response:
+ """Redirect to home page."""
+ return flask.redirect(flask.url_for(".home"))
+
+
+@bp.route("/login", methods=["GET", "POST"])
+def login() -> Response | str:
+ """Login page."""
+ form = LoginForm(next=flask.request.args.get("next"))
if form.validate_on_submit():
login_user(form.user, remember=form.remember.data)
- flash('Logged in successfully.')
- return redirect(request.form.get('next') or url_for('.home'))
- return render_template('login.html', form=form)
+ flask.flash("Logged in successfully.")
+ return flask.redirect(flask.request.form.get("next") or flask.url_for(".home"))
+ return flask.render_template("login.html", form=form)
-@bp.route('/logout')
-def logout():
+
+@bp.route("/logout")
+def logout() -> Response:
+ """Logout and redirect to home."""
logout_user()
- flash('You have been logged out.')
+ flask.flash("You have been logged out.")
return redirect_to_home()
-@bp.route('/signup', methods=['GET', 'POST'])
+
+@bp.route("/signup", methods=["GET", "POST"])
def signup():
- if not current_app.config.get('ALLOW_SIGNUP'):
- abort(404)
+ if not flask.current_app.config.get("ALLOW_SIGNUP"):
+ flask.abort(404)
form = SignupForm()
if not form.validate_on_submit():
- return render_template('signup.html', form=form)
+ return flask.render_template("signup.html", form=form)
- user = User(**form.data)
+ data = form.data.copy()
+ del data["csrf_token"]
+ user = User(**data)
session.add(user)
session.commit()
- flash('New account created.')
+ flask.flash("New account created.")
login_user(user)
- return redirect(url_for(view_after_signup))
+ return flask.redirect(flask.url_for(view_after_signup))
-def redirect_to_doc(doc):
- return redirect(url_for('.view_document', hashid=doc.hashid))
-def get_source_doc(username, hashid):
+def redirect_to_doc(doc: Item) -> Response:
+ """Redirect to the given item."""
+ return flask.redirect(flask.url_for(".view_document", hashid=doc.hashid))
+
+
+def get_source_doc(username: str, hashid: str) -> SourceDoc:
+ """Get a source doc that belongs to the given uesr."""
doc = Item.get_by_hashid(hashid)
- if doc and doc.user.username != username:
- doc = None
- return doc if doc else abort(404)
+ if not doc or doc.user.username != username:
+ flask.abort(404)
+ return typing.cast(SourceDoc, doc)
-def get_xanapage(username, hashid):
+
+def get_xanapage(username: str, hashid: str) -> XanaPage:
+ """Get a xanapage that belongs to the given uesr."""
doc = Item.get_by_hashid(hashid)
- if doc and doc.user.username != username:
- doc = None
- return doc if doc and doc.type == 'xanapage' else abort(404)
+ if not doc or doc.type != "xanapage" or doc.user.username != username:
+ flask.abort(404)
+ return typing.cast(XanaPage, doc)
-def get_item(username, hashid):
+
+def get_item(username: str, hashid: str) -> Item:
+ """Get an item with the given hashid, check ownership."""
doc = Item.get_by_hashid(hashid)
- if doc and doc.user.username != username:
- doc = None
- return doc if doc else abort(404)
+ if not doc or doc.user.username != username:
+ flask.abort(404)
+ return doc
-@bp.route('///edl')
-def view_edl(username, hashid):
+
+@bp.route("///edl")
+def view_edl(username: str, hashid: str) -> str:
+ """Show EDL for xanapage."""
item = get_xanapage(username, hashid)
- return render_template('view.html',
- doc=item,
- iter_lines=iter_lines,
- nbsp_at_start=nbsp_at_start)
+ return flask.render_template(
+ "view.html", doc=item, iter_lines=iter_lines, nbsp_at_start=nbsp_at_start
+ )
-@bp.route('///realize')
+
+@bp.route("///realize")
def realize_edl(username, hashid):
item = get_xanapage(username, hashid)
spans = list(fulfil_edl(item.text))
- doc_text = ''.join(span['text'] for span in spans)
+ doc_text = "".join(span["text"] for span in spans)
- return render_template('realize.html',
- doc=doc_text,
- iter_lines=iter_lines,
- item=item,
- nbsp_at_start=nbsp_at_start)
+ return flask.render_template(
+ "realize.html",
+ doc=doc_text,
+ iter_lines=iter_lines,
+ item=item,
+ nbsp_at_start=nbsp_at_start,
+ )
-@bp.route('///raw')
-def view_item_raw(username, hashid):
+
+@bp.route("///raw")
+def view_item_raw(username: str, hashid: str):
return view_item(username, hashid, raw=True)
-def fulfil_xanaflight(item):
+
+def fulfil_xanaflight(item: XanaLink) -> str:
link = item.parse()
- assert link['type'] == 'flight'
- facets = link['facets']
+ assert link["type"] == "flight"
+ facets = link["facets"]
docs = []
edl_list = []
all_links = []
for facet in facets:
xanapage = Item.from_external(parse_xanapage_facet(facet))
- assert xanapage.type == 'xanapage'
+ assert xanapage and xanapage.type == "xanapage" and xanapage.text
edl = parse_edl(xanapage.text)
edl_list.append((xanapage, edl))
- all_links += [parse_link(link['text']) for link in edl['links']]
+ all_links += [parse_link(link["text"]) for link in edl["links"]]
for doc_num, (xanapage, edl) in enumerate(edl_list):
- doc = fulfil_edl_with_links(edl,
- doc_num=doc_num,
- links=all_links,
- hide_all_transclusions=True)
- doc['hashid'] = xanapage.hashid
- del doc['link_count']
+ doc = fulfil_edl_with_links(
+ edl, doc_num=doc_num, links=all_links, hide_all_transclusions=True
+ )
+ doc["hashid"] = xanapage.hashid
+ del doc["link_count"]
docs.append(doc)
- return render_template('view/xanaflight.html',
- item=item,
- link_count=len(all_links),
- docs=docs)
+ return flask.render_template(
+ "view/xanaflight.html", item=item, link_count=len(all_links), docs=docs
+ )
-@bp.route('///fulfil')
+
+@bp.route("///fulfil")
def fulfil(username, hashid):
item = get_item(username, hashid)
- if item.type == 'xanapage':
- return render_template('view/xanapage.html',
- item=item,
- doc=fulfil_edl_with_sources(item.text))
- if item.type == 'xanalink' and item.text.startswith('type=flight'):
+ if item.type == "xanapage":
+ return flask.render_template(
+ "view/xanapage.html", item=item, doc=fulfil_edl_with_sources(item.text)
+ )
+ if item.type == "xanalink" and item.text.startswith("type=flight"):
return fulfil_xanaflight(item)
-@bp.route('///set_title', methods=['POST'])
+
+@bp.route("///set_title", methods=["POST"])
def set_title(username, hashid):
item = get_item(username, hashid)
- has_title = item.has_title
- item.set_title(request.form['title'], current_user)
- flash('title change saved' if has_title else 'title added')
- return redirect(item.url)
+ has_title = item.has_title()
+ item.set_title(flask.request.form["title"], current_user)
+ flask.flash("title change saved" if has_title else "title added")
+ return flask.redirect(item.url)
-@bp.route('///delete', methods=['POST'])
+
+@bp.route("///delete", methods=["POST"])
def delete_item(username, hashid):
item = get_item(username, hashid)
session.delete(item)
session.commit()
- flash('item deleted')
+ flask.flash("item deleted")
return redirect_to_home()
+
def save_new_xanalink(doc1, doc2):
- start1 = request.form['left_start']
- length1 = request.form['left_length']
- start2 = request.form['right_start']
- length2 = request.form['right_length']
+ start1 = flask.request.form["left_start"]
+ length1 = flask.request.form["left_length"]
+ start2 = flask.request.form["right_start"]
+ length2 = flask.request.form["right_length"]
assert length1
assert length2
- span1 = f'{doc1.external_url},start={start1},length={length1}'
- span2 = f'{doc2.external_url},start={start2},length={length2}'
+ span1 = f"{doc1.external_url},start={start1},length={length1}"
+ span2 = f"{doc2.external_url},start={start2},length={length2}"
lines = [
- 'type=',
- 'facet=',
- 'span: ' + span1,
- 'facet=',
- 'span: ' + span2,
+ "type=",
+ "facet=",
+ "span: " + span1,
+ "facet=",
+ "span: " + span2,
]
- text = ''.join(line + '\n' for line in lines)
+ text = "".join(line + "\n" for line in lines)
obj = XanaLink(user=current_user, text=text)
session.add(obj)
@@ -320,127 +383,148 @@ def save_new_xanalink(doc1, doc2):
session.add(ref2)
session.commit()
-@bp.route('/build_links', methods=['GET', 'POST'])
+
+@bp.route("/build_links", methods=["GET", "POST"])
def build_links():
doc1, doc2 = None, None
hashid1, hashid2 = None, None
- if 'doc1' in request.args:
- hashid1 = request.args['doc1']
+ if "doc1" in flask.request.args:
+ hashid1 = flask.request.args["doc1"]
doc1 = Item.get_by_hashid(hashid1)
- if 'doc2' in request.args:
- hashid2 = request.args['doc2']
+ if "doc2" in flask.request.args:
+ hashid2 = flask.request.args["doc2"]
doc2 = Item.get_by_hashid(hashid2)
- if request.method == 'POST':
+ if flask.request.method == "POST":
save_new_xanalink(doc1, doc2)
- return redirect(url_for(request.endpoint, doc1=hashid1, doc2=hashid2))
+ return flask.redirect(
+ flask.url_for(flask.request.endpoint, doc1=hashid1, doc2=hashid2)
+ )
if doc1 and doc2:
- links = list({i for i in doc1.subjects} &
- {i for i in doc2.subjects})
+ links = list({i for i in doc1.subjects} & {i for i in doc2.subjects})
else:
links = []
- return render_template('build_links.html',
- iter_lines=iter_lines,
- nbsp_at_start=nbsp_at_start,
- SourceDoc=SourceDoc,
- hashid1=hashid1,
- hashid2=hashid2,
- doc1=doc1,
- doc2=doc2,
- links=links)
+ return flask.render_template(
+ "build_links.html",
+ iter_lines=iter_lines,
+ nbsp_at_start=nbsp_at_start,
+ SourceDoc=SourceDoc,
+ hashid1=hashid1,
+ hashid2=hashid2,
+ doc1=doc1,
+ doc2=doc2,
+ links=links,
+ )
-@bp.route('//')
-def view_item(username, hashid, raw=False):
- if ',' in hashid:
+
+@bp.route("//")
+def view_item(username: str, hashid: str, raw: bool = False) -> str | Response:
+ """View item."""
+ if "," in hashid:
m = re_spanpointer.match(hashid)
+ assert m
hashid, start, length = m.group(1), int(m.group(2)), int(m.group(3))
item = get_item(username, hashid)
if raw:
- return Response(item.text[start:length + start], mimetype='text/plain')
+ assert item.text is not None
+ return flask.Response(
+ item.text[start : length + start], mimetype="text/plain"
+ )
else:
start, length = None, None
item = get_item(username, hashid)
if raw:
- return Response(item.text, mimetype='text/plain')
+ return flask.Response(item.text, mimetype="text/plain")
- v = request.args.get('v')
+ v = flask.request.args.get("v")
if v:
if not v.isdigit():
- abort(404)
+ flask.abort(404)
try:
version = item.versions[int(v) - 1]
except IndexError:
- abort(404)
+ flask.abort(404)
text = version.text
else:
version = None
text = item.text
- if item.type == 'xanapage':
+ if item.type == "xanapage":
+ assert item.text
spans = list(fulfil_edl(item.text))
- doc_text = ''.join(span['text'] for span in spans)
+ doc_text = "".join(span["text"] for span in spans)
else:
doc_text = None
- return render_template('view.html',
- doc=item,
- doc_text=doc_text,
- version=version,
- text=text,
- span_start=start,
- span_length=length,
- add_highlight=add_highlight,
- nbsp_at_start=nbsp_at_start,
- iter_lines=iter_lines)
+ return flask.render_template(
+ "view.html",
+ doc=item,
+ doc_text=doc_text,
+ version=version,
+ text=text,
+ span_start=start,
+ span_length=length,
+ add_highlight=add_highlight,
+ nbsp_at_start=nbsp_at_start,
+ iter_lines=iter_lines,
+ )
-@bp.route('///history')
-def history(username, hashid):
+
+@bp.route("///history")
+def history(username: str, hashid: str) -> str:
item = get_item(username, hashid)
- return render_template('history.html', doc=item)
+ return flask.render_template("history.html", doc=item)
-@bp.route('///as_xanapage', methods=['POST'])
+
+@bp.route("///as_xanapage", methods=["POST"])
def create_xanapage_from_sourcedoc(username, hashid):
src_doc = get_source_doc(username, hashid)
- edl = 'span: ' + src_doc.entire_span + '\n'
+ edl = "span: " + src_doc.entire_span + "\n"
page = XanaPage(user=current_user, text=edl)
session.add(page)
session.commit()
page.update_references()
- flash('New xanapage created.')
- return redirect(page.url)
+ flask.flash("New xanapage created.")
+ return flask.redirect(page.url)
-@bp.route('///xanaedit', methods=['GET', 'POST'])
+
+@bp.route("///xanaedit", methods=["GET", "POST"])
def xanaedit_item(username, hashid):
- if request.method == 'POST':
+ if flask.request.method == "POST":
save_xanaedit(username, hashid)
- return redirect(url_for('xanaedit_item', username=username, hashid=hashid))
+ return flask.redirect(
+ flask.url_for("xanaedit_item", username=username, hashid=hashid)
+ )
doc = get_xanapage(username, hashid)
spans = list(fulfil_edl(doc.text))
- doc_text = ''.join(span['text'] for span in spans)
+ doc_text = "".join(span["text"] for span in spans)
- return render_template('xanaedit.html', doc=doc, doc_text=doc_text)
+ return flask.render_template("xanaedit.html", doc=doc, doc_text=doc_text)
-def save_xanaedit(username, hashid):
+
+def save_xanaedit(username: str, hashid: str) -> XanaPage:
+ """Save a XanaEdit."""
page = get_xanapage(username, hashid)
+ assert page.text
current_edl = parse_edl(page.text)
- spans = [Span(*span) for span in current_edl['spans']]
- edits = json.loads(request.form['edits'])
- new_text = ''
+ spans = [Span(*span) for span in current_edl["spans"]]
+ edits = json.loads(flask.request.form["edits"])
+ new_text = ""
new_text_pos = 0
for edit in edits:
- if edit['op'] not in ('insert', 'replace'):
+ if edit["op"] not in ("insert", "replace"):
continue
- new_text += edit['new']
- edit['span'] = Span('placeholder', new_text_pos, len(edit['new']))
- new_text_pos += len(edit['new'])
+ new_text += edit["new"]
+ edit["span"] = Span("placeholder", new_text_pos, len(edit["new"]))
+ new_text_pos += len(edit["new"])
spans = apply_edits(spans, edits)
new_src_doc = SourceDoc(user=current_user, text=new_text)
@@ -448,70 +532,78 @@ def save_xanaedit(username, hashid):
session.commit()
for span in spans:
- if span.url == 'placeholder':
+ if span.url == "placeholder":
span.url = new_src_doc.external_url
- new_edl = ''.join(f'span: {span.for_edl()}\n' for span in spans)
+ new_edl = "".join(f"span: {span.for_edl()}\n" for span in spans)
page.text = new_edl
session.commit()
page.update_references()
- flash('Edits saved.')
+ flask.flash("Edits saved.")
return page
-@bp.route('///finish', methods=['POST'])
-def finish_xanaedit(username, hashid):
+@bp.route("///finish", methods=["POST"])
+def finish_xanaedit(username: str, hashid: str) -> Response:
page = save_xanaedit(username, hashid)
- return redirect(page.url)
+ return flask.redirect(page.url)
-@bp.route('///edit', methods=['GET', 'POST'])
+
+@bp.route("///edit", methods=["GET", "POST"])
def edit_item(username, hashid):
obj = get_item(username, hashid)
form = SourceDocForm(obj=obj)
if form.validate_on_submit():
form.populate_obj(obj)
session.commit()
- if obj.type == 'xanapage':
+ if obj.type == "xanapage":
obj.update_references()
- flash('Changes to {} saved.'.format(obj.type))
- return redirect(obj.url)
- return render_template('edit.html', form=form, doc=obj)
+ flask.flash("Changes to {} saved.".format(obj.type))
+ return flask.redirect(obj.url)
+ return flask.render_template("edit.html", form=form, doc=obj)
-@bp.route('/source_doc_text/')
+
+@bp.route("/source_doc_text/")
def source_doc_text(source_doc_id):
doc = SourceDoc.query.get(source_doc_id)
- return render_template('source_doc_text.html', doc=doc, iter_lines=iter_lines)
+ return flask.render_template("source_doc_text.html", doc=doc, iter_lines=iter_lines)
-@bp.route('/settings/account', methods=['GET', 'POST'])
+
+@bp.route("/settings/account", methods=["GET", "POST"])
@login_required
-def account_settings():
+def account_settings() -> str | Response:
+ """Account settings."""
form = AccountSettingsForm(obj=current_user)
if form.validate_on_submit():
form.populate_obj(current_user)
session.commit()
- flash('Account details updated.')
- return redirect(url_for(request.endpoint))
- return render_template('user/account.html', form=form)
+ flask.flash("Account details updated.")
+ assert flask.request.endpoint
+ return flask.redirect(flask.url_for(flask.request.endpoint))
+ return flask.render_template("user/account.html", form=form)
-@bp.route('/new/sourcedoc', methods=['GET', 'POST'])
+
+@bp.route("/new/sourcedoc", methods=["GET", "POST"])
@login_required
-def new_sourcedoc():
+def new_sourcedoc() -> str | Response:
+ """Add a new sourcedoc."""
form = SourceDocForm()
if form.validate_on_submit():
doc = SourceDoc(user=current_user)
form.populate_obj(doc)
session.add(doc)
session.commit()
- flash('New document saved.')
- return redirect(doc.url)
- return render_template('new.html', form=form, item_type='source document')
+ flask.flash("New document saved.")
+ return flask.redirect(doc.url)
+ return flask.render_template("new.html", form=form, item_type="source document")
-@bp.route('/new/xanalink/raw', methods=['GET', 'POST'])
+
+@bp.route("/new/xanalink/raw", methods=["GET", "POST"])
@login_required
def new_xanalink_raw():
form = ItemForm()
@@ -520,72 +612,80 @@ def new_xanalink_raw():
form.populate_obj(obj)
session.add(obj)
session.commit()
- flash('New xanalink saved.')
- return redirect(obj.url)
- return render_template('new.html', form=form, item_type='xanalink')
+ flask.flash("New xanalink saved.")
+ return flask.redirect(obj.url)
+ return flask.render_template("new.html", form=form, item_type="xanalink")
-@bp.route('/new/xanalink', methods=['GET', 'POST'])
+
+@bp.route("/new/xanalink", methods=["GET", "POST"])
@login_required
-def new_xanalink():
- if request.method != 'POST':
- return render_template('new_xanalink.html')
+def new_xanalink() -> str | Response:
+ """Add a new xanalink."""
+ if flask.request.method != "POST":
+ return flask.render_template("new_xanalink.html")
- data = request.get_json()
- lines = ['type=' + data['link_type']]
- for facet in data['facets']:
- lines += ['facet='] + facet
- text = ''.join(line + '\n' for line in lines)
+ data = flask.request.get_json()
+ lines = ["type=" + data["link_type"]]
+ for facet in data["facets"]:
+ lines += ["facet="] + facet
+ text = "".join(line + "\n" for line in lines)
obj = XanaLink(user=current_user, text=text)
session.add(obj)
session.commit()
- flash('New xanalink saved.')
- return jsonify(url=obj.url)
+ flask.flash("New xanalink saved.")
+ return flask.jsonify(url=obj.url)
-@bp.route('/new/xanapage', methods=['GET', 'POST'])
+
+@bp.route("/new/xanapage", methods=["GET", "POST"])
@login_required
-def new_xanapage():
+def new_xanapage() -> str | Response:
+ """Start a new xanapage."""
form = ItemForm()
if form.validate_on_submit():
obj = XanaPage(user=current_user)
form.populate_obj(obj)
session.add(obj)
session.commit()
- flash('New xanapage saved.')
- return redirect(obj.url)
- return render_template('new.html', form=form, item_type='xanapage')
+ flask.flash("New xanapage saved.")
+ return flask.redirect(obj.url)
+ return flask.render_template("new.html", form=form, item_type="xanapage")
-@bp.route('/edit/', methods=['GET', 'POST'])
+
+@bp.route("/edit/", methods=["GET", "POST"])
@login_required
-def edit_source_document(filename):
+def edit_source_document(filename: str) -> str | Response:
+ """Edit a source document."""
doc = get_source_doc(current_user.username, filename)
form = SourceDocForm(obj=doc)
if form.validate_on_submit():
form.populate_obj(doc)
session.add(doc)
session.commit()
- flash('Changes to document saved.')
- return redirect(doc.url)
- return render_template('edit.html', form=form, doc=doc)
+ flask.flash("Changes to document saved.")
+ return flask.redirect(doc.url)
+ return flask.render_template("edit.html", form=form, doc=doc)
-@bp.route('/api/1/get//')
-def api_get_document(username, filename):
+
+@bp.route("/api/1/get//")
+def api_get_document(username: str, filename: str) -> Response:
doc = get_source_doc(username, filename)
- if not doc:
- return abort(404)
+ assert doc.text is not None
ret = {
- 'username': username,
- 'filename': filename,
- 'character_count': len(doc.text),
- 'document_price': str(doc.document_price),
- 'price_per_character': str(doc.price_per_character),
+ "username": username,
+ "filename": filename,
+ "character_count": len(doc.text),
+ "document_price": str(doc.document_price),
+ "price_per_character": str(doc.price_per_character),
}
- return jsonify(ret)
+ return flask.jsonify(ret)
-@bp.route('/get_span.json')
-def get_span():
- url = request.args['url']
- start = int(request.args['start'])
- length = int(request.args['length'])
- spanid = request.args['spanid']
+
+@bp.route("/get_span.json")
+def get_span() -> Response:
+ """Return JSON representing a span."""
+ url = flask.request.args["url"]
+ start = int(flask.request.args["start"])
+ length = int(flask.request.args["length"])
+ spanid = flask.request.args["spanid"]
text = get_url(url)
- return jsonify(text=text[start:start + length], spanid=spanid)
+ return flask.jsonify(text=text[start : start + length], spanid=spanid)