Blog iteration #2

This commit is contained in:
Fabio Manganiello 2021-01-24 20:39:27 +01:00
parent 3b8111882b
commit d81abaa984
11 changed files with 327 additions and 12 deletions

View File

@ -6,4 +6,4 @@ This project provides the pages and the webapp needed by the Platypush blog.
- `flask`
- `markdown`
- `pygments`

View File

@ -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)

View File

@ -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
View File

@ -0,0 +1,3 @@
flask
markdown
pygments

114
static/css/blog.css Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
static/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,3 +0,0 @@
# Blog articles
- [Introduction to Platypush](Self-hosted-ultimate-automation-with-Platypush.md)

View File

@ -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
View 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>