Allow documentation files to be served by the webapp.

master
Pacman Ghost 2 years ago
parent 52b0d34a51
commit 5716b0dfe9
  1. 1
      .dockerignore
  2. 1
      Dockerfile
  3. 1
      asl_rulebook2/webapp/__init__.py
  4. 39
      asl_rulebook2/webapp/doc.py
  5. 11
      asl_rulebook2/webapp/static/css/prepare.css
  6. 14
      asl_rulebook2/webapp/static/prepare.js
  7. 35
      asl_rulebook2/webapp/tests/test_doc.py
  8. 3
      doc/extend.md
  9. 1
      requirements.txt

@ -4,4 +4,5 @@
! requirements*.txt
! asl_rulebook2/
! docker/
! doc/
! LICENSE.txt

@ -47,6 +47,7 @@ RUN if [ -n "$CONTROL_TESTS_PORT" ]; then \
# install the application
WORKDIR /app
COPY asl_rulebook2/ ./asl_rulebook2/
COPY doc/ ./doc/
COPY setup.py requirements.txt requirements-dev.txt LICENSE.txt ./
RUN pip3 install --editable .

@ -84,6 +84,7 @@ import asl_rulebook2.webapp.content #pylint: disable=wrong-import-position,cycli
import asl_rulebook2.webapp.search #pylint: disable=wrong-import-position,cyclic-import
import asl_rulebook2.webapp.rule_info #pylint: disable=wrong-import-position,cyclic-import
import asl_rulebook2.webapp.prepare #pylint: disable=wrong-import-position,cyclic-import
import asl_rulebook2.webapp.doc #pylint: disable=wrong-import-position,cyclic-import
from asl_rulebook2.webapp import globvars #pylint: disable=wrong-import-position,cyclic-import
app.before_request( globvars.on_request )

@ -0,0 +1,39 @@
""" Serve documentation files. """
import os
import io
import re
import markdown
from flask import make_response, send_file, abort, safe_join
from asl_rulebook2.webapp import app
# ---------------------------------------------------------------------
@app.route( "/doc/<path:path>" )
def get_doc( path ):
"""Return the specified documentation file."""
# locate the documentation file
doc_dir = os.path.join( os.path.dirname( __file__ ), "../../doc/" )
fname = safe_join( doc_dir, path )
if not os.path.isfile( fname ):
abort( 404 )
# check if the file is Markdown
if os.path.splitext( path )[1] == ".md":
# yup - convert it to HTML
buf = io.BytesIO()
markdown.markdownFromFile( input=fname, output=buf, encoding="utf-8" )
# FUDGE! Code fragments are wrapped with <code> tags, and while we would like to style them using CSS,
# there's no way to distinguish between inline and block fragments :-/ We identify block fragments
# by the <code> tag being at the start of a line, and style them using inline attributes. Sigh...
code = b"<code style='display:block;white-space:pre;margin:0.5em 0 1em 2em;'>"
resp = re.sub( rb"^<code>", code, buf.getvalue(), flags=re.MULTILINE )
resp = make_response( resp )
resp.mimetype = "text/html"
return resp
else:
# nope - just serve it as-is
return send_file( fname )

@ -19,15 +19,22 @@ code { display: block ; margin: 5px 0 5px 20px ; }
#progress-panel {
flex-grow: 1 ; overflow-y: auto ;
border: 1px solid black ; border-radius: 5px ; padding: 10px ;
font-family: monospace ; font-size: 90% ;
}
#progress-panel .progress { font-style: italic ; }
#progress-panel .status { margin: 5px 0 ; }
#progress-panel .status { margin: 5px 0 ; font-family: monospace ; font-size: 90% ; }
#progress-panel .status:first-of-type { margin-top: 0 ; }
#progress-panel .status table { margin-left: 2px ; }
#progress-panel .status table td { vertical-align: top ; }
#progress-panel .status img.icon { height: 15px ; margin: 1px 3px 0 0 ; }
#progress-panel .loading {
position: fixed ; bottom: 18px ; right: 18px ;
border: 1px solid black ; border-radius: 5px ;
padding: 10px ;
background: #f0f0f0 ;
font-size: 80% ; font-style: italic ; text-align: center ;
}
#download-panel {
position: fixed ; bottom: 18px ; right: 18px ; width: 75% ;
border: 1px solid black ; border-radius: 5px ; background: white ;

@ -31,7 +31,7 @@ gPrepareApp.component( "prepare-app", {
It will take around 10-15 minutes.
</p>
<p> If there are problems, you can try to prepare your data files manually,
as described <a href="https://github.com/pacman-ghost/asl-rulebook2/blob/master/doc/prepare.md" target="_blank">here</a>.
as described <a href="/doc/prepare.md" target="_blank">here</a>.
</p>
</div>
<div v-show=fatalErrorMsg id="fatal-error" >
@ -185,11 +185,18 @@ gPrepareApp.component( "progress-panel", {
data() { return {
socketIOClient: null,
statusBlocks: [],
isDone: false,
} ; },
template: `
<div id="progress-panel">
<status-block v-for="sb in statusBlocks" :statusBlock=sb :key=sb />
<div v-if="!isDone" class="loading">
<img src="/static/images/loading.gif" />
<div style="margin-top:3px;">
While you're waiting, you can <br> check out the features <a href="/doc/features/index.html" target="_blank">here</a>.
</div>
</div>
</div>`,
created() {
@ -202,10 +209,9 @@ gPrepareApp.component( "progress-panel", {
initSocketIOClient() {
// initialize the socketio client
let done = false ;
this.socketIOClient = io.connect() ; //eslint-disable-line no-undef
this.socketIOClient.on( "disconnect", () => {
if ( ! done )
if ( ! this.isDone )
this.$emit( "fatal", "The server has gone away. Please restart it, then reload this page." ) ;
} ) ;
this.socketIOClient.on( "status", (msg) => { this.addStatusBlock( msg ) ; } ) ;
@ -213,7 +219,7 @@ gPrepareApp.component( "progress-panel", {
this.socketIOClient.on( "warning", (msg) => { this.addProgressMsg( "warning", msg ) ; } ) ;
this.socketIOClient.on( "error", (msg) => { this.addProgressMsg( "error", msg ) ; } ) ;
this.socketIOClient.on( "done", (downloadUrl) => {
done = true ;
this.isDone = true ;
gProgressPanel.addStatusBlock( "All done." ) ;
this.socketIOClient.disconnect() ;
this.socketIOClient = null ;

@ -0,0 +1,35 @@
""" Test documentation files. """
import urllib.request
import urllib.error
import pytest
from asl_rulebook2.webapp.tests.utils import init_webapp
# ---------------------------------------------------------------------
def test_doc( webapp, webdriver ):
"""Test serving documentation files."""
# initialize
webapp.control_tests.set_data_dir( "simple" )
init_webapp( webapp, webdriver )
def get_doc( path ):
# get the specified documentation file
url = "{}/{}".format( webapp.base_url, path )
resp = urllib.request.urlopen( url ).read()
return resp.decode( "utf-8" )
# test a valid documentation file
resp = get_doc( "/doc/prepare.md" )
assert "Preparing the data files" in resp
# test an unknown documentation file
with pytest.raises( urllib.error.HTTPError ):
_ = get_doc( "/doc/UNKNOWN" )
# try to bust out of the documentation directory
with pytest.raises( urllib.error.HTTPError ):
_ = get_doc( "/doc/../LICENSE.txt" )

@ -19,6 +19,7 @@ Finally, bookmarks need to be created in the PDF for each rule, so that the prog
Save the prepared file as `ASL Rulebook (xxx).pdf` in your data directory, together with the targets file, then restart the server.
Optionally, you can also provide:
- a `ASL Rulebook (xxx).chapters` file (to be able to browse the PDF in the *Chapters* panel)
- a `ASL Rulebook (xxx).footnotes` file (if the rules have any footnotes).
@ -29,6 +30,7 @@ To add a chapter icon and background, create files `XXX-icon.png` and `XXX-backg
To add rules for a module not already referenced by the ASLRB index, the process is the same as above, but you also need to write a `.index` file. As an example, take a look at the `ASL Rulebook.index` file that was extracted for you.
All the files should have the same base filename e.g.
- kgs.index
- kgs.pdf
- kgs.targets
@ -45,6 +47,7 @@ This is described [here](../asl_rulebook2/webapp/tests/fixtures/full/).
As you add more content, program startup will use more and more CPU (as it converts rule ID's to clickable links), and while the program will still come up and be functional quickly, rule ID's will take longer to become clickable, and this processing may affect other things running on your computer.
To alleviate this, you can specify a file to cache the results of this work:
- add a `CACHED_SEARCHDB` settings to your `site.cfg` file (if running from source)
- add a `--cached-searchdb` parameter when running `run-container.sh` (if running using Docker)

@ -5,6 +5,7 @@ flask-socketio==5.1.1
eventlet==0.33.0
pyyaml==5.4.1
lxml==4.6.2
markdown==3.3.6
click==7.1.2
pdfminer.six==20201018

Loading…
Cancel
Save