-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathapplication.py
140 lines (111 loc) · 4.23 KB
/
application.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import re
from flask import Flask, abort, redirect, render_template, request
from html import escape
from werkzeug.exceptions import default_exceptions, HTTPException
from helpers import lines, sentences, substrings
# Configure application
app = Flask(__name__)
# Reload templates when they are changed
app.config["TEMPLATES_AUTO_RELOAD"] = True
@app.after_request
def after_request(response):
"""Disable caching"""
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Expires"] = 0
response.headers["Pragma"] = "no-cache"
return response
@app.route("/")
def index():
"""Handle requests for / via GET (and POST)"""
return render_template("index.html")
@app.route("/compare", methods=["POST"])
def compare():
"""Handle requests for /compare via POST"""
# Read files
if not request.files["file1"] or not request.files["file2"]:
abort(400, "missing file")
try:
file1 = request.files["file1"].read().decode("utf-8")
file2 = request.files["file2"].read().decode("utf-8")
except Exception:
abort(400, "invalid file")
# Compare files
if not request.form.get("algorithm"):
abort(400, "missing algorithm")
elif request.form.get("algorithm") == "lines":
regexes = [f"^{re.escape(match)}$" for match in lines(file1, file2)]
elif request.form.get("algorithm") == "sentences":
regexes = [re.escape(match) for match in sentences(file1, file2)]
elif request.form.get("algorithm") == "substrings":
if not request.form.get("length"):
abort(400, "missing length")
elif not int(request.form.get("length")) > 0:
abort(400, "invalid length")
regexes = [re.escape(match) for match in substrings(
file1, file2, int(request.form.get("length")))]
else:
abort(400, "invalid algorithm")
# Highlight files
highlights1 = highlight(file1, regexes)
highlights2 = highlight(file2, regexes)
# Output comparison
return render_template("compare.html", file1=highlights1, file2=highlights2)
def highlight(s, regexes):
"""Highlight all instances of regexes in s."""
# Get intervals for which strings match
intervals = []
for regex in regexes:
if not regex:
continue
matches = re.finditer(regex, s, re.MULTILINE)
for match in matches:
intervals.append((match.start(), match.end()))
intervals.sort(key=lambda x: x[0])
# Combine intervals to get highlighted areas
highlights = []
for interval in intervals:
if not highlights:
highlights.append(interval)
continue
last = highlights[-1]
# If intervals overlap, then merge them
if interval[0] <= last[1]:
new_interval = (last[0], interval[1])
highlights[-1] = new_interval
# Else, start a new highlight
else:
highlights.append(interval)
# Maintain list of regions: each is a start index, end index, highlight
regions = []
# If no highlights at all, then keep nothing highlighted
if not highlights:
regions = [(0, len(s), False)]
# If first region is not highlighted, designate it as such
elif highlights[0][0] != 0:
regions = [(0, highlights[0][0], False)]
# Loop through all highlights and add regions
for start, end in highlights:
if start != 0:
prev_end = regions[-1][1]
if start != prev_end:
regions.append((prev_end, start, False))
regions.append((start, end, True))
# Add final unhighlighted region if necessary
if regions[-1][1] != len(s):
regions.append((regions[-1][1], len(s), False))
# Combine regions into final result
result = ""
for start, end, highlighted in regions:
escaped = escape(s[start:end])
if highlighted:
result += f"<span>{escaped}</span>"
else:
result += escaped
return result
@app.errorhandler(HTTPException)
def errorhandler(error):
"""Handle errors"""
return render_template("error.html", error=error), error.code
# https://github.com/pallets/flask/pull/2314
for code in default_exceptions:
app.errorhandler(code)(errorhandler)