Initial commit.
commit
27ad7da676
@ -0,0 +1,3 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
node_modules
|
@ -0,0 +1,35 @@
|
||||
#
|
||||
|
||||
The boilerplate uses the python framework flask to run the webinterface and to generate the HTML of the book. To generate the PDF it uses the javascript library pagedjs together with the chromium webbrowser in headless mode.
|
||||
|
||||
# Installation
|
||||
|
||||
First [download](gitlaburl) it on gitlab.
|
||||
|
||||
Or clone the repository:
|
||||
|
||||
`git clone `
|
||||
|
||||
This boilerplate uses Python 3 and the framework [Flask](https://palletsprojects.com/p/flask/).
|
||||
|
||||
Find information on how to [install python here](https://www.python.org/downloads/)
|
||||
|
||||
To install the requirements, move to the directory this readme is located, and then run:
|
||||
|
||||
`pip install -r requirements.txt`
|
||||
|
||||
Then install [pagedjs-cli](https://gitlab.coko.foundation/pagedjs/pagedjs-cli), with the command:
|
||||
|
||||
`npm install -g pagedjs-cli`
|
||||
|
||||
Finally edit a copy of `settings.example.py` and save it as `settings.py`.
|
||||
It is important to set the `PAGEDJS_BINARY_PATH`, you should be able to find it
|
||||
by running: `whereis pagedjs-cli`.
|
||||
|
||||
Now you should be able to start the interface with: `./run.sh`.
|
||||
|
||||
# Deployment
|
||||
|
||||
Todo
|
||||
|
||||
Note: this boilerplate does not make any effort at rate-limiting.
|
@ -0,0 +1,84 @@
|
||||
from flask import Flask, render_template, Response, abort, url_for
|
||||
# from weasyprint import HTML
|
||||
import os
|
||||
from pagedjs import make_pdf_from_url
|
||||
from settings import BASEURL, DEBUG, HTML_TMP_DIR, SITEURL
|
||||
import tempfile
|
||||
from werkzeug.security import safe_join
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
"""
|
||||
Shows the index page of the application
|
||||
"""
|
||||
@app.route('{}/'.format(BASEURL))
|
||||
def index():
|
||||
context = {}
|
||||
return render_template('index.html', **context)
|
||||
|
||||
|
||||
"""
|
||||
This is where the book generation happens.
|
||||
|
||||
Possibly adjust the allowed methods.
|
||||
"""
|
||||
@app.route('{}/generate'.format(BASEURL), methods=['GET', 'POST'])
|
||||
def generate ():
|
||||
context = {
|
||||
'DEBUG': DEBUG
|
||||
}
|
||||
|
||||
html = render_template('book.html', **context)
|
||||
|
||||
if (DEBUG):
|
||||
return html
|
||||
else:
|
||||
pdf = make_pdf(html)
|
||||
|
||||
print(pdf)
|
||||
|
||||
r = Response(pdf, mimetype='application/pdf')
|
||||
|
||||
r.headers.extend({
|
||||
'Content-Disposition': 'attachment; filename="Title of the.pdf"'
|
||||
})
|
||||
|
||||
return r
|
||||
|
||||
"""
|
||||
Proxy(?) for pagedjs, it stores the provided html in a separate file.
|
||||
Now pagedjs can connect to the application through HTTP.
|
||||
|
||||
This way static files, like fonts, images and css can be served in a regular way.
|
||||
|
||||
"""
|
||||
def make_pdf (html):
|
||||
if not os.path.exists(HTML_TMP_DIR):
|
||||
os.mkdir(HTML_TMP_DIR)
|
||||
|
||||
with tempfile.NamedTemporaryFile(mode='w', prefix='book_', dir=HTML_TMP_DIR, delete=False) as tmpfile:
|
||||
tmpfile.write(html)
|
||||
tmpfile.flush()
|
||||
|
||||
bookname = os.path.basename(tmpfile.name)
|
||||
url = SITEURL + url_for('show_book', bookname=bookname)
|
||||
tmpfile.close()
|
||||
|
||||
# Generate the pdf with pagedjs
|
||||
return make_pdf_from_url(url)
|
||||
|
||||
|
||||
"""
|
||||
View for pagedjs. It loads the generated HTML from the tmp dir and returns it.
|
||||
"""
|
||||
@app.route('{}/book/<string:bookname>'.format(BASEURL))
|
||||
def show_book (bookname):
|
||||
bookpath = safe_join(HTML_TMP_DIR, bookname)
|
||||
|
||||
if os.path.exists(bookpath):
|
||||
with open(bookpath, 'r') as h:
|
||||
html = h.read()
|
||||
|
||||
return html
|
||||
|
||||
abort(404)
|
@ -0,0 +1,112 @@
|
||||
import subprocess
|
||||
import tempfile
|
||||
import os.path
|
||||
from settings import PAGEDJS_BINARY_PATH
|
||||
|
||||
|
||||
basepath = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
"""
|
||||
Calls the pagedjs binary.
|
||||
path_html path to the html sources to be read by pagedjs
|
||||
path_pdf output path of the generated pdf
|
||||
cwd path to be used as current working directory for the subprocess
|
||||
extra_scripts array of paths to additional javascript
|
||||
"""
|
||||
def run_pagedjs (path_html, path_pdf, cwd=None, extra_scripts=[]):
|
||||
args = [
|
||||
PAGEDJS_BINARY_PATH
|
||||
]
|
||||
|
||||
for script in extra_scripts:
|
||||
args.extend([
|
||||
'--additional-script',
|
||||
script
|
||||
])
|
||||
|
||||
args.extend([
|
||||
'-o', path_pdf,
|
||||
path_html
|
||||
])
|
||||
|
||||
print(' '.join(args))
|
||||
|
||||
try:
|
||||
return subprocess.check_output(args, cwd=cwd, stderr=subprocess.STDOUT).decode()
|
||||
except subprocess.CalledProcessError as e:
|
||||
return 'Error:\n{}'.format(e.output.decode())
|
||||
|
||||
"""
|
||||
Generates a PDF based on provided HTML using pagedjs and returns the generated PDF.
|
||||
|
||||
If optional path_out is provided the PDF is written there and the function returns the path.
|
||||
|
||||
Optional extra_scripts is a list of strings with javascript.
|
||||
Scripts are sent in the same order to paged.js
|
||||
"""
|
||||
def make_pdf_from_string (html, path_out=None, extra_scripts=[]):
|
||||
with tempfile.TemporaryDirectory(prefix='algoliterary_publishing_house_') as tempdir:
|
||||
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.html', delete=False) as temphtml:
|
||||
# Store html in a temporary file
|
||||
temphtml.write(html)
|
||||
temphtml.close()
|
||||
|
||||
name_in = temphtml.name
|
||||
|
||||
extra_scripts_tmp = []
|
||||
|
||||
for script in extra_scripts:
|
||||
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.js', delete=False) as tempjs:
|
||||
tempjs.write(script)
|
||||
tempjs.close()
|
||||
extra_scripts_tmp.append(tempjs.name)
|
||||
|
||||
# Make a temporary file for the generated PDF
|
||||
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.pdf', delete=False) as temppdf:
|
||||
temppdf.close()
|
||||
name_out = temppdf.name
|
||||
|
||||
# Make the pdf
|
||||
run_pagedjs(name_in, name_out, cwd=basepath, extra_scripts=extra_scripts_tmp)
|
||||
|
||||
if path_out:
|
||||
import shutil
|
||||
shutil.copy(name_out, path_out)
|
||||
return path_out
|
||||
else:
|
||||
with open(name_out, 'rb') as generated_pdf:
|
||||
return generated_pdf.read()
|
||||
|
||||
"""
|
||||
Generates a PDF based on provided HTML using pagedjs and returns the generated PDF.
|
||||
|
||||
If optional path_out is provided the PDF is written there and the function returns the path.
|
||||
|
||||
Optional extra_scripts is a list of strings with javascript.
|
||||
Scripts are sent in the same order to paged.js
|
||||
"""
|
||||
def make_pdf_from_url (url, path_out=None, extra_scripts=[]):
|
||||
with tempfile.TemporaryDirectory(prefix='algoliterary_publishing_house_') as tempdir:
|
||||
extra_scripts_tmp = []
|
||||
|
||||
for script in extra_scripts:
|
||||
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.js', delete=False) as tempjs:
|
||||
tempjs.write(script)
|
||||
tempjs.close()
|
||||
extra_scripts_tmp.append(tempjs.name)
|
||||
|
||||
# Make a temporary file for the generated PDF
|
||||
with tempfile.NamedTemporaryFile(dir=tempdir, mode='w', suffix='.pdf', delete=False) as temppdf:
|
||||
temppdf.close()
|
||||
name_out = temppdf.name
|
||||
|
||||
# Make the pdf
|
||||
run_pagedjs(url, name_out, cwd=basepath, extra_scripts=extra_scripts_tmp)
|
||||
|
||||
if path_out:
|
||||
import shutil
|
||||
shutil.copy(name_out, path_out)
|
||||
return path_out
|
||||
else:
|
||||
with open(name_out, 'rb') as generated_pdf:
|
||||
return generated_pdf.read()
|
@ -0,0 +1 @@
|
||||
flask
|
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
export FLASK_APP=app.py
|
||||
export FLASK_ENV=development # Disable line on deployment
|
||||
flask run
|
@ -0,0 +1,11 @@
|
||||
PAGEDJS_BINARY_PATH = ''
|
||||
# Path to the pagedjs-cli executable
|
||||
# Find it with `whereis pagedjs-cli`
|
||||
DEBUG = False
|
||||
# When set to true, the application doesn't generate a PDF,
|
||||
# but uses the pagedjs-polyfill to show a preview of the document
|
||||
SITEURL = 'http://localhost:5000'
|
||||
# The url pagedjs-cli tries to connect to, this url is the
|
||||
# default of the Flask development server
|
||||
BASEURL = ''
|
||||
HTML_TMP_DIR = '/tmp/publishing_house/'
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,4 @@
|
||||
h1 {
|
||||
text-decoration: underline;
|
||||
break-after: always;
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
|
||||
{% if DEBUG %}
|
||||
<link href="{{ url_for('static', filename='pagedjs.interface.css') }}" rel="stylesheet">
|
||||
<script src="{{ url_for('static', filename='js/paged.polyfill.js') }}"></script>
|
||||
<!-- <script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script> -->
|
||||
{% endif %}
|
||||
<title>Generated book</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, world!</h1>
|
||||
<h1>Hello, world!</h1>
|
||||
<h1>Hello, world!</h1>
|
||||
<h1>Hello, world!</h1>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
|
||||
<title>Generative book</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="{{ url_for('generate') }}">Generate</a>
|
||||
<!-- Or to generate the book with a post request, uncomment the block below. -->
|
||||
<!-- <form method="post" action="{{ url_for('generate') }}">
|
||||
<button>Generate</button>
|
||||
</form> -->
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,27 @@
|
||||
import os
|
||||
from fcntl import lockf, LOCK_EX, LOCK_UN
|
||||
|
||||
def get_edition_count(counter_path):
|
||||
# Try to open a file descriptor to the given path
|
||||
fd = os.open(counter_path, os.O_RDWR|os.O_CREAT)
|
||||
# Lock the file so it can't be read in other processes
|
||||
lockf(fd, LOCK_EX)
|
||||
|
||||
# Open the file and read
|
||||
fo = os.fdopen(fd, 'r+', encoding='utf-8')
|
||||
content = fo.read()
|
||||
if not content:
|
||||
edition_count = 0
|
||||
else:
|
||||
edition_count = int(content.strip())
|
||||
edition_count += 1
|
||||
|
||||
# Clear file, write incremented value, unlock file and close
|
||||
fo.seek(0)
|
||||
fo.truncate()
|
||||
fo.write(str(edition_count))
|
||||
fo.flush()
|
||||
lockf(fd, LOCK_UN)
|
||||
os.close(fd)
|
||||
|
||||
return edition_count
|
Loading…
Reference in New Issue