Blog iteration #2
This commit is contained in:
parent
3b8111882b
commit
d81abaa984
11 changed files with 327 additions and 12 deletions
|
@ -6,4 +6,4 @@ This project provides the pages and the webapp needed by the Platypush blog.
|
|||
|
||||
- `flask`
|
||||
- `markdown`
|
||||
|
||||
- `pygments`
|
||||
|
|
|
@ -1,8 +1,86 @@
|
|||
from flask import Flask
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
app = Flask(__name__)
|
||||
from markdown import markdown
|
||||
|
||||
from flask import Flask, abort, send_from_directory, render_template
|
||||
|
||||
basedir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..', '..'))
|
||||
templates_dir = os.path.join(basedir, 'templates')
|
||||
static_dir = os.path.join(basedir, 'static')
|
||||
pages_dir = os.path.join(static_dir, 'pages')
|
||||
img_dir = os.path.join(static_dir, 'img')
|
||||
css_dir = os.path.join(static_dir, 'css')
|
||||
|
||||
app = Flask(__name__, template_folder=templates_dir)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def test():
|
||||
return 'It works!'
|
||||
def parse_page_title(page: str) -> str:
|
||||
if page.endswith('.md'):
|
||||
page = page[:-3]
|
||||
|
||||
return page.replace('-', ' ')
|
||||
|
||||
|
||||
def get_page_metadata(page: str) -> dict:
|
||||
if not page.endswith('.md'):
|
||||
page = page + '.md'
|
||||
|
||||
if not os.path.isfile(os.path.join(pages_dir, page)):
|
||||
abort(404)
|
||||
|
||||
metadata = {}
|
||||
with open(os.path.join(pages_dir, page), 'r') as f:
|
||||
for line in f.readlines():
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if not (m := re.match(r'^\[//]: # \(([^:]+):\s*([^)]+)\)\s*$', line)):
|
||||
break
|
||||
|
||||
metadata[m.group(1)] = m.group(2)
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
def get_page(page: str, title: Optional[str] = None):
|
||||
if not page.endswith('.md'):
|
||||
page = page + '.md'
|
||||
|
||||
metadata = get_page_metadata(page)
|
||||
with open(os.path.join(pages_dir, page), 'r') as f:
|
||||
return render_template('article.html',
|
||||
title=title if title else metadata.get('title', 'Platypush Blog'),
|
||||
published=(datetime.date.fromisoformat(metadata['published']).strftime('%b %d, %Y')
|
||||
if metadata.get('published') else None),
|
||||
content=markdown(f.read(),extensions=['fenced_code', 'codehilite']))
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def home_route():
|
||||
return get_page('Home', title='Platypush Blog')
|
||||
|
||||
|
||||
@app.route('/favicon.ico', methods=['GET'])
|
||||
def favicon_route():
|
||||
return send_from_directory(img_dir, 'favicon.ico')
|
||||
|
||||
|
||||
@app.route('/img/<img>', methods=['GET'])
|
||||
def img_route(img: str):
|
||||
return send_from_directory(img_dir, img)
|
||||
|
||||
|
||||
@app.route('/css/<style>', methods=['GET'])
|
||||
def css_route(style: str):
|
||||
return send_from_directory(css_dir, style)
|
||||
|
||||
|
||||
@app.route('/<path:path>', methods=['GET'])
|
||||
def catch_all(path: str):
|
||||
if path.endswith('.md'):
|
||||
return get_page(path)
|
||||
|
||||
abort(404)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
from . import app
|
||||
|
||||
|
||||
def get_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host', dest='host', required=False, default='0.0.0.0', help='Bind host/address')
|
||||
parser.add_argument('--port', dest='port', required=False, type=int, default=8000, help='Bind port (default: 8000)')
|
||||
parser.add_argument('--debug', dest='debug', required=False, action='store_true', default=False,
|
||||
help='Enable debug mode (default: False)')
|
||||
|
||||
return parser.parse_known_args(sys.argv[1:])
|
||||
|
||||
|
||||
opts = get_args()[0]
|
||||
app.run(host=opts.host, port=opts.port, debug=opts.debug)
|
3
requirements.txt
Normal file
3
requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
flask
|
||||
markdown
|
||||
pygments
|
114
static/css/blog.css
Normal file
114
static/css/blog.css
Normal file
|
@ -0,0 +1,114 @@
|
|||
html {
|
||||
font-size: calc(1em + 1vw);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
html {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: #555;
|
||||
border-bottom: 1px dashed #999;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 3em;
|
||||
padding: 0 .5em;
|
||||
box-shadow: 1px 3px 3px 0 #bbb;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
header {
|
||||
height: 4em;
|
||||
}
|
||||
}
|
||||
|
||||
header > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
header .icon {
|
||||
background: url(/img/icon.png);
|
||||
background-size: 40px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inline-flex;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
header .title {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
main {
|
||||
height: calc(100% - 3em);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-family: Avenir, Palatino, Georgia, Verdana, Helvetica, Arial, sans-serif;
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
main .content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: justify;
|
||||
line-height: 1.5em;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
main .content code, .codehilite {
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.1em;
|
||||
}
|
||||
|
||||
.published-date {
|
||||
font-size: 0.75em;
|
||||
opacity: .75;
|
||||
margin-top: -1em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
main .content, main .title {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
main .content, main .title {
|
||||
max-width: 768px;
|
||||
}
|
||||
}
|
||||
|
||||
.codehilite {
|
||||
padding: 0 .5em;
|
||||
overflow: auto;
|
||||
}
|
74
static/css/code.css
Normal file
74
static/css/code.css
Normal file
|
@ -0,0 +1,74 @@
|
|||
pre { line-height: 125%; }
|
||||
td.linenos pre { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos { color: #000000; background-color: #f0f0f0; padding-left: 5px; padding-right: 5px; }
|
||||
td.linenos pre.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
||||
.codehilite .hll { background-color: #ffffcc }
|
||||
.codehilite { background: #f8f8f8; }
|
||||
.codehilite .c { color: #408080; font-style: italic } /* Comment */
|
||||
.codehilite .err { border: 1px solid #FF0000 } /* Error */
|
||||
.codehilite .k { color: #008000; font-weight: bold } /* Keyword */
|
||||
.codehilite .o { color: #666666 } /* Operator */
|
||||
.codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
|
||||
.codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */
|
||||
.codehilite .cp { color: #BC7A00 } /* Comment.Preproc */
|
||||
.codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
|
||||
.codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */
|
||||
.codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */
|
||||
.codehilite .gd { color: #A00000 } /* Generic.Deleted */
|
||||
.codehilite .ge { font-style: italic } /* Generic.Emph */
|
||||
.codehilite .gr { color: #FF0000 } /* Generic.Error */
|
||||
.codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
||||
.codehilite .gi { color: #00A000 } /* Generic.Inserted */
|
||||
.codehilite .go { color: #888888 } /* Generic.Output */
|
||||
.codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
|
||||
.codehilite .gs { font-weight: bold } /* Generic.Strong */
|
||||
.codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
||||
.codehilite .gt { color: #0044DD } /* Generic.Traceback */
|
||||
.codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
|
||||
.codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
|
||||
.codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
|
||||
.codehilite .kp { color: #008000 } /* Keyword.Pseudo */
|
||||
.codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
|
||||
.codehilite .kt { color: #B00040 } /* Keyword.Type */
|
||||
.codehilite .m { color: #666666 } /* Literal.Number */
|
||||
.codehilite .s { color: #BA2121 } /* Literal.String */
|
||||
.codehilite .na { color: #7D9029 } /* Name.Attribute */
|
||||
.codehilite .nb { color: #008000 } /* Name.Builtin */
|
||||
.codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */
|
||||
.codehilite .no { color: #880000 } /* Name.Constant */
|
||||
.codehilite .nd { color: #AA22FF } /* Name.Decorator */
|
||||
.codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */
|
||||
.codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
|
||||
.codehilite .nf { color: #0000FF } /* Name.Function */
|
||||
.codehilite .nl { color: #A0A000 } /* Name.Label */
|
||||
.codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
|
||||
.codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */
|
||||
.codehilite .nv { color: #19177C } /* Name.Variable */
|
||||
.codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
|
||||
.codehilite .w { color: #bbbbbb } /* Text.Whitespace */
|
||||
.codehilite .mb { color: #666666 } /* Literal.Number.Bin */
|
||||
.codehilite .mf { color: #666666 } /* Literal.Number.Float */
|
||||
.codehilite .mh { color: #666666 } /* Literal.Number.Hex */
|
||||
.codehilite .mi { color: #666666 } /* Literal.Number.Integer */
|
||||
.codehilite .mo { color: #666666 } /* Literal.Number.Oct */
|
||||
.codehilite .sa { color: #BA2121 } /* Literal.String.Affix */
|
||||
.codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */
|
||||
.codehilite .sc { color: #BA2121 } /* Literal.String.Char */
|
||||
.codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */
|
||||
.codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
|
||||
.codehilite .s2 { color: #BA2121 } /* Literal.String.Double */
|
||||
.codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
|
||||
.codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */
|
||||
.codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
|
||||
.codehilite .sx { color: #008000 } /* Literal.String.Other */
|
||||
.codehilite .sr { color: #BB6688 } /* Literal.String.Regex */
|
||||
.codehilite .s1 { color: #BA2121 } /* Literal.String.Single */
|
||||
.codehilite .ss { color: #19177C } /* Literal.String.Symbol */
|
||||
.codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */
|
||||
.codehilite .fm { color: #0000FF } /* Name.Function.Magic */
|
||||
.codehilite .vc { color: #19177C } /* Name.Variable.Class */
|
||||
.codehilite .vg { color: #19177C } /* Name.Variable.Global */
|
||||
.codehilite .vi { color: #19177C } /* Name.Variable.Instance */
|
||||
.codehilite .vm { color: #19177C } /* Name.Variable.Magic */
|
||||
.codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */
|
BIN
static/img/favicon.ico
Normal file
BIN
static/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
static/img/icon.png
Normal file
BIN
static/img/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -1,3 +0,0 @@
|
|||
# Blog articles
|
||||
|
||||
- [Introduction to Platypush](Self-hosted-ultimate-automation-with-Platypush.md)
|
|
@ -1,7 +1,7 @@
|
|||
Ultimate self-hosted automation with Platypush
|
||||
==============================================
|
||||
[//]: # (title: Ultimate self-hosted automation with Platypush)
|
||||
[//]: # (published: 2019-07-28)
|
||||
|
||||
In the last years, we have experienced a terrific spike of products and solutions targeting home automation and
|
||||
In the last few years we have experienced a terrific spike of products and solutions targeting home automation and
|
||||
automation in general. After a couple of years of hype around IoT, assistants and home automation the market is slowly
|
||||
cooling down, and a few solutions are emerging out of the primordial oh-look-at-this-shiny-new-toy chaos.
|
||||
|
31
templates/article.html
Normal file
31
templates/article.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/css/blog.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/code.css">
|
||||
<title>{{ title }}</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<a href="/">
|
||||
<div class="icon"></div>
|
||||
<div class="title">Platypush Blog</div>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<div class="title">
|
||||
<h1>{{ title }}</h1>
|
||||
{% if published %}
|
||||
<div class="published-date">
|
||||
Published on {{ published }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue