Reworked web front, problems are now sorted by category as well as line number

Wed, 31 Jan 2018 14:34:23 +0200

author
Santeri Piippo
date
Wed, 31 Jan 2018 14:34:23 +0200
changeset 32
75f44d3063da
parent 31
02e7e1d73ebb
child 33
d91dfc6056a3

Reworked web front, problems are now sorted by category as well as line number

ldcheck.py file | annotate | diff | comparison | revisions
static/error.svg file | annotate | diff | comparison | revisions
static/favicon.ico file | annotate | diff | comparison | revisions
static/notice.svg file | annotate | diff | comparison | revisions
static/style.css file | annotate | diff | comparison | revisions
static/warning.svg file | annotate | diff | comparison | revisions
templates/webfront.html file | annotate | diff | comparison | revisions
tests/quadrilaterals.py file | annotate | diff | comparison | revisions
testsuite.py file | annotate | diff | comparison | revisions
webfront.py file | annotate | diff | comparison | revisions
--- a/ldcheck.py	Wed Jan 24 23:14:01 2018 +0200
+++ b/ldcheck.py	Wed Jan 31 14:34:23 2018 +0200
@@ -44,10 +44,6 @@
             if (library_path / path).is_file()
         ]
 
-def hairline_score(smallest_angle):
-    from math import log10
-    return max(0, -log10(smallest_angle))
-
 class Model:
     def __init__(self, body):
         self.body = body
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static/error.svg	Wed Jan 31 14:34:23 2018 +0200
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg6361" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" sodipodi:docname="process-stop.svg" xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" height="48px" sodipodi:version="0.32" width="48px" xmlns:cc="http://web.resource.org/cc/" xmlns:xlink="http://www.w3.org/1999/xlink" sodipodi:docbase="/home/jimmac/gfx/ximian/tango-icon-theme/scalable/actions" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+  <radialGradient id="radialGradient21650" gradientUnits="userSpaceOnUse" cy="36.75" cx="25.125" gradientTransform="matrix(1 0 0 .59524 -2.3007e-15 14.875)" r="15.75">
+   <stop id="stop21646" offset="0"/>
+   <stop id="stop21648" stop-opacity="0" offset="1"/>
+  </radialGradient>
+  <linearGradient id="linearGradient2057" y2="47.374" gradientUnits="userSpaceOnUse" x2="53.57" gradientTransform="translate(0 -2)" y1="12.504" x1="15.737">
+   <stop id="stop11782" stop-color="#ff8b8b" offset="0"/>
+   <stop id="stop11784" stop-color="#ec1b1b" offset="1"/>
+  </linearGradient>
+  <linearGradient id="linearGradient4987" y2="37.96" gradientUnits="userSpaceOnUse" x2="41.048" gradientTransform="translate(0 -2)" y1="20.105" x1="23.996">
+   <stop id="stop4983" stop-color="#c00" offset="0"/>
+   <stop id="stop4985" stop-color="#b30000" offset="1"/>
+  </linearGradient>
+  <radialGradient id="radialGradient2239" gradientUnits="userSpaceOnUse" cy="33.302" cx="24.302" gradientTransform="matrix(1.694 -5.7757e-16 5.7757e-16 1.694 -16.865 -25.111)" r="12.302">
+   <stop id="stop9649" stop-color="#fff" offset="0"/>
+   <stop id="stop9651" stop-color="#dbdbdb" offset="1"/>
+  </radialGradient>
+  <radialGradient id="radialGradient2254" gradientUnits="userSpaceOnUse" cy="10.666" cx="16.75" gradientTransform="matrix(4.155 -2.9792e-24 3.2557e-24 3.1987 -52.846 -23.509)" r="21.25">
+   <stop id="stop2250" stop-color="#fff" offset="0"/>
+   <stop id="stop2252" stop-color="#fff" stop-opacity="0" offset="1"/>
+  </radialGradient>
+  <linearGradient id="linearGradient2262" y2="35.052" gradientUnits="userSpaceOnUse" x2="24.302" gradientTransform="translate(0 -2)" y1="15.802" x1="21.75">
+   <stop id="stop2258" stop-color="#ff0202" offset="0"/>
+   <stop id="stop2260" stop-color="#ff9b9b" offset="1"/>
+  </linearGradient>
+ </defs>
+ <sodipodi:namedview id="base" bordercolor="#666666" pagecolor="#ffffff" showgrid="false" borderopacity="0.15294118" showguides="true"/>
+ <metadata id="metadata4">
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:title>Stop</dc:title>
+    <dc:date>2005-10-16</dc:date>
+    <dc:creator>
+     <cc:Agent>
+      <dc:title>Andreas Nilsson</dc:title>
+     </cc:Agent>
+    </dc:creator>
+    <dc:subject>
+     <rdf:Bag>
+      <rdf:li>stop</rdf:li>
+      <rdf:li>halt</rdf:li>
+      <rdf:li>error</rdf:li>
+     </rdf:Bag>
+    </dc:subject>
+    <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/"/>
+    <dc:contributor>
+     <cc:Agent>
+      <dc:title>Jakub Steiner</dc:title>
+     </cc:Agent>
+    </dc:contributor>
+   </cc:Work>
+   <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+    <cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
+    <cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
+    <cc:requires rdf:resource="http://web.resource.org/cc/Notice"/>
+    <cc:requires rdf:resource="http://web.resource.org/cc/Attribution"/>
+    <cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
+    <cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike"/>
+   </cc:License>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+  <path id="path21642" sodipodi:rx="15.75" sodipodi:ry="9.375" style="color:#000000" sodipodi:type="arc" d="m40.875 36.75a15.75 9.375 0 1 1 -31.5 0 15.75 9.375 0 1 1 31.5 0z" fill-rule="evenodd" opacity=".63068" transform="matrix(1.1738 0 0 0.6 -5.2659 19.575)" sodipodi:cy="36.75" sodipodi:cx="25.125" fill="url(#radialGradient21650)"/>
+  <path id="path9480" d="m15.591 0.49192h17.085l12.822 13.094v17.894l-12.649 12.017h-17.43l-12.925-12.839 0.0004-17.194 13.097-12.972z" fill-rule="evenodd" sodipodi:nodetypes="ccccccccc" stroke="#860000" fill="url(#linearGradient4987)"/>
+  <path id="path9482" opacity=".81319" d="m16.021 1.5003h16.228l12.247 12.423v17.114l-11.858 11.451h-16.768l-12.361-12.279 0.0001-16.363 12.512-12.346z" sodipodi:nodetypes="ccccccccc" stroke="url(#linearGradient2057)" fill="none"/>
+  <path id="path2241" opacity=".28977" d="m15.688 0.75l-12.938 12.812v17l2.9375 2.907c16.762 0.057 16.478-13.019 39.562-11.875v-7.906l-12.688-12.938h-16.874z" fill-rule="evenodd" sodipodi:nodetypes="cccccccc" fill="url(#radialGradient2254)"/>
+  <path id="path2787" stroke-linejoin="round" d="m16.767 10.5l-4.267 4.267 7.535 7.535-7.535 7.535 4.267 4.268 7.535-7.536 7.535 7.536 4.268-4.268-7.536-7.535 7.536-7.535-4.268-4.267-7.535 7.535-7.535-7.535z" fill-rule="evenodd" stroke="url(#linearGradient2262)" stroke-linecap="round" fill="url(#radialGradient2239)"/>
+ </g>
+</svg>
Binary file static/favicon.ico has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static/notice.svg	Wed Jan 31 14:34:23 2018 +0200
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg1306" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs1308">
+  <radialGradient id="radialGradient3976" gradientUnits="userSpaceOnUse" cy="40" cx="23.857" gradientTransform="matrix(1 0 0 .5 0 20)" r="17.143">
+   <stop id="stop4128" offset="0"/>
+   <stop id="stop4130" stop-opacity="0" offset="1"/>
+  </radialGradient>
+  <linearGradient id="linearGradient3980" y2="-8.5627" gradientUnits="userSpaceOnUse" x2="20.065" y1="53.836" x1="43.936">
+   <stop id="stop2481" stop-color="#ffe69b" offset="0"/>
+   <stop id="stop2483" stop-color="#fff" offset="1"/>
+  </linearGradient>
+ </defs>
+ <metadata id="metadata1311">
+  <rdf:RDF>
+   <cc:Work rdf:about="">
+    <dc:format>image/svg+xml</dc:format>
+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+    <dc:creator>
+     <cc:Agent>
+      <dc:title>Rodney Dawes</dc:title>
+     </cc:Agent>
+    </dc:creator>
+    <dc:contributor>
+     <cc:Agent>
+      <dc:title>Jakub Steiner, Garrett LeSage</dc:title>
+     </cc:Agent>
+    </dc:contributor>
+    <cc:license rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/"/>
+    <dc:title/>
+   </cc:Work>
+   <cc:License rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+    <cc:permits rdf:resource="http://web.resource.org/cc/Reproduction"/>
+    <cc:permits rdf:resource="http://web.resource.org/cc/Distribution"/>
+    <cc:requires rdf:resource="http://web.resource.org/cc/Notice"/>
+    <cc:requires rdf:resource="http://web.resource.org/cc/Attribution"/>
+    <cc:permits rdf:resource="http://web.resource.org/cc/DerivativeWorks"/>
+    <cc:requires rdf:resource="http://web.resource.org/cc/ShareAlike"/>
+   </cc:License>
+  </rdf:RDF>
+ </metadata>
+ <g id="layer2">
+  <path id="path6548" opacity=".6" style="color:#000000" d="m41 40a17.143 8.5714 0 1 1 -34.286 0 17.143 8.5714 0 1 1 34.286 0z" transform="matrix(1.0706 0 0 .525 -.89276 22.5)" display="block" fill="url(#radialGradient3976)"/>
+ </g>
+ <g id="layer1">
+  <g id="g4006">
+   <path id="path1314" d="m46.857 23.929c0 12.9-10.457 23.357-23.357 23.357s-23.357-10.457-23.357-23.357 10.457-23.357 23.357-23.357 23.357 10.457 23.357 23.357z" transform="matrix(.92049 0 0 .92049 2.3685 .97408)" stroke="#204a87" stroke-width="1.0864" fill="#3465a4"/>
+   <path id="path3560" opacity=".34659" d="m49.902 26.635c0 13.25-10.741 23.991-23.991 23.991s-23.991-10.741-23.991-23.991 10.741-23.991 23.991-23.991 23.991 10.741 23.991 23.991z" fill-opacity="0" transform="matrix(.85448 0 0 .85448 1.86 .24062)" stroke="url(#linearGradient3980)" stroke-width="1.1703"/>
+  </g>
+ </g>
+ <g id="layer3" fill="#fff">
+  <path id="path3684" opacity=".25" d="m24 3c-11.046 0-20 8.954-20 20 0 1.6861 0.23214 3.3108 0.625 4.875 3.204 4.7933 13.254-0.12014 20.219-5.5938 8.008-6.2931 17.842 8.3593 19.125 1.4375 0.008-0.239 0.031-0.478 0.031-0.719 0-11.046-8.954-20-20-20z"/>
+  <g id="g13674" transform="translate(2.8959 -3.6973)">
+   <path id="text3246" d="m21.104 13.697c-0.86423 0.01942-1.5754 0.30821-2.1336 0.86638-0.55819 0.55821-0.84699 1.2694-0.86638 2.1336 0.01939 0.86424 0.30819 1.5754 0.86638 2.1336 0.55818 0.55821 1.2694 0.847 2.1336 0.86638 0.86422-0.01938 1.5754-0.30817 2.1336-0.86638 0.55818-0.55817 0.84697-1.2694 0.86638-2.1336-0.01941-0.8642-0.3082-1.5754-0.86638-2.1336-0.5582-0.55817-1.2694-0.84696-2.1336-0.86638z"/>
+   <path id="path13678" d="m15.104 21.697v3h2c0.55228 0 1 0.44772 1 1v9c0 0.55228-0.44772 1-1 1h-2v3h12v-3h-2c-0.55228 0-1-0.44772-1-1v-13h-9z"/>
+  </g>
+ </g>
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static/style.css	Wed Jan 31 14:34:23 2018 +0200
@@ -0,0 +1,81 @@
+
+body
+{
+    font-family: sans-serif;
+    margin: 0;
+}
+
+.ldraw-code
+{
+    font-family: monospace;
+}
+
+.problems-list
+{
+    list-style: none;
+}
+
+.problems-list li
+{
+    padding: 5px;
+    border: 1px solid black;
+    border-radius: 10px;
+    margin-bottom: 3px;
+}
+
+.problems-list li.problem-error
+{
+    background-color: #b44;
+    border-color: red;
+    color: white;
+}
+
+.problems-list li.problem-warning
+{
+    background-color: #fc6;
+    border-color: #830;
+    color: black;
+}
+
+.problems-list li.problem-notice
+{
+    background-color: #def;
+    border-color: #024;
+    color: black;
+}
+
+.problem-icon
+{
+    float: left;
+    margin-right: 10px;
+}
+
+.top-form
+{
+    background: white;
+    background: linear-gradient(
+        to bottom,
+        #cceecf 0%,
+        #cceecf 85%,
+        #163 100%
+    );
+    padding-bottom: 2vh;
+    padding-top: 2vh;
+    padding-left: 5%;
+    padding-right: 5%;
+    text-align: center;
+}
+
+.report-container
+{
+    background: linear-gradient(
+        to bottom,
+        #ccc 0,
+        #fff 1vh,
+        #fff 100%
+    );
+    min-height: 10vh;
+    padding-left: 5%;
+    padding-right: 5%;
+    padding-top: 2vh;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/static/warning.svg	Wed Jan 31 14:34:23 2018 +0200
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="48"
+   height="48"
+   version="1.1"
+   id="svg998"
+   sodipodi:docname="warning.svg"
+   inkscape:version="0.92.2 (5c3e80d, 2017-08-06)">
+  <metadata
+     id="metadata1002">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1920"
+     inkscape:window-height="1049"
+     id="namedview1000"
+     showgrid="false"
+     inkscape:zoom="3.1267378"
+     inkscape:cx="-74.508859"
+     inkscape:cy="4.4248979"
+     inkscape:window-x="1920"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="svg998" />
+  <defs
+     id="defs982">
+    <radialGradient
+       id="a"
+       gradientUnits="userSpaceOnUse"
+       cy="41.4"
+       cx="24.29"
+       gradientTransform="matrix(1 0 0 .3526 0 26.8)"
+       r="21.1">
+      <stop
+         offset="0"
+         id="stop977" />
+      <stop
+         stop-opacity="0"
+         offset="1"
+         id="stop979" />
+    </radialGradient>
+  </defs>
+  <ellipse
+     opacity=".41"
+     rx="21.1"
+     ry="7.44"
+     cy="41.4"
+     cx="24.29"
+     fill="url(#a)"
+     id="ellipse984" />
+  <circle
+     cy="21.4"
+     cx="24.3"
+     r="21.27"
+     fill="#914900"
+     id="circle986" />
+  <circle
+     r="20"
+     cy="21.31"
+     stroke="#fcaf3e"
+     cx="24.44"
+     fill="#f57900"
+     id="circle988"
+     style="fill:#f5ca00;fill-opacity:1" />
+  <path
+     style="fill:#000000;fill-opacity:1"
+     inkscape:connector-curvature="0"
+     id="path990"
+     d="m 21.46,10.39 c -0.13,0 -0.23,0.16 -0.23,0.37 L 22.3,25.2 c 0,0.21 0.11,0.38 0.23,0.38 h 3.57 c 0.13,0 0.23,-0.17 0.23,-0.38 L 27.4,10.76 c 0,-0.21 -0.11,-0.37 -0.23,-0.37 h -5.7" />
+  <path
+     style="fill:#ffffff;fill-opacity:0.21000001"
+     inkscape:connector-curvature="0"
+     id="path992"
+     d="m 43.68,20.48 c 0,10.83 -6.1,-4.31 -18.67,0.39 C 12.28,25.59 4.43,31.31 4.43,20.48 4.43,9.65 13.23,0.86 24.06,0.86 c 10.83,0 19.62,8.79 19.62,19.62" />
+  <circle
+     style="fill:#000000;fill-opacity:1"
+     id="circle994"
+     r="2.3"
+     cx="24.360001"
+     cy="30.200001" />
+</svg>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/templates/webfront.html	Wed Jan 31 14:34:23 2018 +0200
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <link rel="icon" href="../static/favicon.ico" />
+    <link rel="stylesheet" href="../static/style.css" />
+    <title>LDraw part verification tool</title>
+</head>
+<body>
+<div class="top-form">
+    <h1>Check your part here</h1>
+    <form method="post" enctype="multipart/form-data">
+        <input type="file" name="file"/>
+        <input type="submit" />
+    </form>
+</div>
+<div class="report-container">
+
+{% if report %}
+    <ul class="problems-list">
+    {% for problem in report['problems'] %}
+        <li class="problem-{{problem['type']}}">
+            <img
+                src="static/{{problem['type']}}.svg"
+                width="32" height="32"
+                class="problem-icon"
+            />
+            Line {{problem['line-number']}}: {{problem['message']}}
+            <br />
+            <span class="ldraw-code">{{problem['ldraw-code']}}</span>
+        </li>
+    {% endfor %}
+    </ul>
+{% endif %}
+{% if report and not report['problems'] %}
+No problems whatsoever.
+{% endif %}
+
+</div>
+</body>
+</html>
--- a/tests/quadrilaterals.py	Wed Jan 24 23:14:01 2018 +0200
+++ b/tests/quadrilaterals.py	Wed Jan 31 14:34:23 2018 +0200
@@ -1,5 +1,5 @@
 from math import radians
-from testsuite import warning, error
+from testsuite import warning, error, notice
 from geometry import *
 
 def sign_consistency(container):
@@ -58,12 +58,26 @@
                 if cross_product(b - a, c - a).length() < 1e-5:
                     yield error(element, 'collinearity-error')
 
+def hairline_score(smallest_angle):
+    from math import log10
+    return max(0, -log10(smallest_angle))
+
+def hairline_test(model):
+    for element in model.body:
+        if hasattr(element, 'geometry') and len(element.geometry.vertices) >= 3:
+            smallest_angle = element.geometry.smallest_angle
+            if smallest_angle < radians(0.5):
+                yield notice(element, 'hairline-warning',
+                    smallest_angle = smallest_angle,
+                )
+
 manifest = {
     'tests': {
         'skew': skew_test,
         'concave': concave_test,
         'bowtie': bowtie_test,
         'collinearity': collinear_test,
+        'hairline': hairline_test,
     },
     'messages': {
         'skew-error': lambda skew_angle:
@@ -77,5 +91,9 @@
         'concave-error': 'concave quadrilateral',
         'self-intersecting': 'bowtie quadrilateral',
         'collinearity-error': 'collinear polygon',
+        'hairline-warning': lambda smallest_angle:
+            str.format('hairline polygon (smallest angle {})',
+                degree_rep(smallest_angle),
+            ),
     },
 }
--- a/testsuite.py	Wed Jan 24 23:14:01 2018 +0200
+++ b/testsuite.py	Wed Jan 31 14:34:23 2018 +0200
@@ -14,6 +14,9 @@
 def error(bad_object, error_name, **args):
     return report_element(bad_object, 'error', error_name, args)
 
+def notice(bad_object, error_name, **args):
+    return report_element(bad_object, 'notice', error_name, args)
+
 def test_discovery():
     '''
         Finds all test modules and yields their names.
@@ -67,22 +70,31 @@
             warn(str.format('Module {} does not have a manifest', module_name))
     return test_suite
 
+def problem_key(problem):
+    problem_hierarchy = ['error', 'warning', 'notice']
+    return (problem_hierarchy.index(problem['type']), problem['line-number'])
+
 def check_model(model, test_suite = None):
     if not test_suite:
         test_suite = load_tests()
-    report = []
+    problems = []
     line_numbers = {
         element: (i, i + 1 + model.body_offset)
         for i, element in enumerate(model.body)
     }
     for test_name, test_function in test_suite['tests'].items():
-        problems = test_function(model)
-        for problem in problems:
+        for problem in test_function(model):
             problem['body-index'], problem['line-number'] \
                 = line_numbers[problem['object']]
             del problem['object']
-            report.append(problem)
-    return report
+            problems.append(problem)
+    return {
+        'passed': not any(
+            problem['type'] == 'error'
+            for problem in problems
+        ),
+        'problems': sorted(problems, key = problem_key),
+    }
 
 def problem_text(problem, test_suite):
     message = test_suite['messages'][problem['name']]
@@ -91,8 +103,8 @@
     return message
 
 def format_report_html(report, model, test_suite):
-    result = []
-    for problem in report:
+    messages = []
+    for problem in report['problems']:
         ldraw_code = model.body[problem['body-index']].textual_representation()
         message = str.format(
             '<li class="{problem_type}">{model_name}:{line_number}:'
@@ -103,21 +115,20 @@
             message = problem_text(problem, test_suite),
             ldraw_code = ldraw_code,
         )
-        result.append((problem['line-number'], message))
-    return '\n'.join(
-        problem[1]
-        for problem in sorted(result)
-    )
+        messages.append(message)
+    return '\n'.join(messages)
 
 def format_report(report, model, test_suite):
     import colorama
     colorama.init()
-    result = []
-    for problem in report:
+    messages = []
+    for problem in report['problems']:
         if problem['type'] == 'error':
             text_colour = colorama.Fore.LIGHTRED_EX
         elif problem['type'] == 'warning':
             text_colour = colorama.Fore.LIGHTYELLOW_EX
+        elif problem['type'] == 'notice':
+            text_colour = colorama.Fore.LIGHTBLUE_EX
         else:
             text_colour = ''
         ldraw_code = model.body[problem['body-index']].textual_representation()
@@ -132,11 +143,8 @@
             colour_reset = colorama.Fore.RESET,
             ldraw_code = ldraw_code,
         )
-        result.append((problem['line-number'], message))
-    return '\n'.join(
-        problem[1]
-        for problem in sorted(result)
-    )
+        messages.append(message)
+    return '\n'.join(messages)
 
 if __name__ == '__main__':
     from pprint import pprint
--- a/webfront.py	Wed Jan 24 23:14:01 2018 +0200
+++ b/webfront.py	Wed Jan 31 14:34:23 2018 +0200
@@ -1,7 +1,8 @@
+#!/usr/bin/env python3
 from flask import Flask, render_template, redirect, request
 from ldcheck import load_config, load_colours, find_ldconfig_ldr_paths
 from ldcheck import read_ldraw
-from testsuite import load_tests, check_model, format_report_html
+from testsuite import load_tests, check_model, problem_text
 
 app = Flask('LDCheck')
 
@@ -12,7 +13,6 @@
         if 'file' not in request.files or not request.files['file'].filename:
             return redirect(request.url)
         file = request.files['file']
-        print(type(file))
         config = load_config('ldcheck.cfg')
         for ldconfig_ldr_path in find_ldconfig_ldr_paths(config):
             with ldconfig_ldr_path.open() as ldconfig_ldr:
@@ -24,18 +24,28 @@
         )
         test_suite = load_tests()
         report = check_model(model, test_suite)
-        return str.format(
-            '<!doctype html><html><body><ul>{report}</ul></body></html>',
-            report = format_report_html(report, model, test_suite)
-        )
-    return '''
-    <!doctype html>
-    <title>Upload new File</title>
-    <h1>Upload new File</h1>
-    <form method=post enctype=multipart/form-data>
-      <p><input type=file name=file>
-         <input type=submit value=Upload>
-    </form>
-    '''
+
+        # Amend human-readable messages into the report
+        for problem in report['problems']:
+            object = model.body[problem['body-index']]
+            problem['message'] = problem_text(problem, test_suite)
+            problem['ldraw-code'] = object.textual_representation()
+    else:
+        report = None
+    return render_template('webfront.html',
+        report = report,
+    )
 
-app.run()
+@app.route('/static/<path:path>')
+def static_file(path):
+    from flask import send_from_directory
+    from os import path
+    return send_from_directory(path.join('static', path))
+
+if __name__ == '__main__':
+    from argparse import ArgumentParser
+    parser = ArgumentParser()
+    parser.add_argument('-p', '--port', type = int, default = 5000)
+    parser.add_argument('-d', '--debug', action = 'store_true')
+    args = parser.parse_args()
+    app.run(port = args.port, debug = args.debug)

mercurial