Compare commits
No commits in common. "main" and "v0.2.19" have entirely different histories.
23 changed files with 115 additions and 214 deletions
|
@ -1,14 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## 0.2.35
|
||||
|
||||
- Use _Lora_ font for the article body.
|
||||
|
||||
## 0.2.24
|
||||
|
||||
- Better default fonts - `sans-serif` style for the index and the titles,
|
||||
`serif` for the articles' body.
|
||||
|
||||
## 0.2.19
|
||||
|
||||
- Added `short_feed` configuration flag to permanently disable returning the
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "0.2.35"
|
||||
__version__ = '0.2.19'
|
||||
|
|
106
madblog/app.py
106
madblog/app.py
|
@ -12,11 +12,11 @@ from ._sorters import PagesSorter, PagesSortByTime
|
|||
|
||||
|
||||
class BlogApp(Flask):
|
||||
_title_header_regex = re.compile(r"^#\s*((\[(.*)\])|(.*))")
|
||||
_title_header_regex = re.compile(r'^#\s*((\[(.*)\])|(.*))')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, template_folder=config.templates_dir, **kwargs)
|
||||
self.pages_dir = os.path.join(config.content_dir, "markdown")
|
||||
self.pages_dir = os.path.join(config.content_dir, 'markdown')
|
||||
self.img_dir = config.default_img_dir
|
||||
self.css_dir = config.default_css_dir
|
||||
self.js_dir = config.default_js_dir
|
||||
|
@ -27,77 +27,73 @@ class BlogApp(Flask):
|
|||
# `config.content_dir` is treated as the root for markdown files.
|
||||
self.pages_dir = config.content_dir
|
||||
|
||||
img_dir = os.path.join(config.content_dir, "img")
|
||||
img_dir = os.path.join(config.content_dir, 'img')
|
||||
if os.path.isdir(img_dir):
|
||||
self.img_dir = os.path.abspath(img_dir)
|
||||
else:
|
||||
self.img_dir = config.content_dir
|
||||
|
||||
css_dir = os.path.join(config.content_dir, "css")
|
||||
css_dir = os.path.join(config.content_dir, 'css')
|
||||
if os.path.isdir(css_dir):
|
||||
self.css_dir = os.path.abspath(css_dir)
|
||||
|
||||
js_dir = os.path.join(config.content_dir, "js")
|
||||
js_dir = os.path.join(config.content_dir, 'js')
|
||||
if os.path.isdir(js_dir):
|
||||
self.js_dir = os.path.abspath(js_dir)
|
||||
|
||||
fonts_dir = os.path.join(config.content_dir, "fonts")
|
||||
fonts_dir = os.path.join(config.content_dir, 'fonts')
|
||||
if os.path.isdir(fonts_dir):
|
||||
self.fonts_dir = os.path.abspath(fonts_dir)
|
||||
|
||||
templates_dir = os.path.join(config.content_dir, "templates")
|
||||
templates_dir = os.path.join(config.content_dir, 'templates')
|
||||
if os.path.isdir(templates_dir):
|
||||
self.template_folder = os.path.abspath(templates_dir)
|
||||
|
||||
def get_page_metadata(self, page: str) -> dict:
|
||||
if not page.endswith(".md"):
|
||||
page = page + ".md"
|
||||
if not page.endswith('.md'):
|
||||
page = page + '.md'
|
||||
|
||||
md_file = os.path.join(self.pages_dir, page)
|
||||
if not os.path.isfile(md_file):
|
||||
abort(404)
|
||||
|
||||
metadata = {}
|
||||
with open(md_file, "r") as f:
|
||||
metadata["uri"] = "/article/" + page[:-3]
|
||||
with open(md_file, 'r') as f:
|
||||
metadata['uri'] = '/article/' + page[:-3]
|
||||
|
||||
for line in f:
|
||||
for line in f.readlines():
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if not (m := re.match(r"^\[//]: # \(([^:]+):\s*(.*)\)\s*$", line)):
|
||||
if not (m := re.match(r'^\[//]: # \(([^:]+):\s*([^)]+)\)\s*$', line)):
|
||||
break
|
||||
|
||||
if m.group(1) == "published":
|
||||
metadata[m.group(1)] = datetime.datetime.fromisoformat(
|
||||
m.group(2)
|
||||
).date()
|
||||
if m.group(1) == 'published':
|
||||
metadata[m.group(1)] = datetime.date.fromisoformat(m.group(2))
|
||||
else:
|
||||
metadata[m.group(1)] = m.group(2)
|
||||
|
||||
if not metadata.get("title"):
|
||||
if not metadata.get('title'):
|
||||
# If the `title` header isn't available in the file,
|
||||
# infer it from the first line of the file
|
||||
with open(md_file, "r") as f:
|
||||
header = ""
|
||||
with open(md_file, 'r') as f:
|
||||
header = ''
|
||||
for line in f.readlines():
|
||||
header = line
|
||||
break
|
||||
|
||||
metadata["title_inferred"] = True
|
||||
metadata['title_inferred'] = True
|
||||
m = self._title_header_regex.search(header)
|
||||
if m:
|
||||
metadata["title"] = m.group(3) or m.group(1)
|
||||
metadata['title'] = m.group(3) or m.group(1)
|
||||
else:
|
||||
metadata["title"] = os.path.basename(md_file)
|
||||
metadata['title'] = os.path.basename(md_file)
|
||||
|
||||
if not metadata.get("published"):
|
||||
if not metadata.get('published'):
|
||||
# If the `published` header isn't available in the file,
|
||||
# infer it from the file's creation date
|
||||
metadata["published"] = datetime.date.fromtimestamp(
|
||||
os.stat(md_file).st_ctime
|
||||
)
|
||||
metadata["published_inferred"] = True
|
||||
metadata['published'] = datetime.date.fromtimestamp(os.stat(md_file).st_ctime)
|
||||
metadata['published_inferred'] = True
|
||||
|
||||
return metadata
|
||||
|
||||
|
@ -106,42 +102,37 @@ class BlogApp(Flask):
|
|||
page: str,
|
||||
title: Optional[str] = None,
|
||||
skip_header: bool = False,
|
||||
skip_html_head: bool = False,
|
||||
skip_html_head: bool = False
|
||||
):
|
||||
if not page.endswith(".md"):
|
||||
page = page + ".md"
|
||||
if not page.endswith('.md'):
|
||||
page = page + '.md'
|
||||
|
||||
metadata = self.get_page_metadata(page)
|
||||
# Don't duplicate the page title if it's been inferred
|
||||
if not (title or metadata.get("title_inferred")):
|
||||
title = metadata.get("title", config.title)
|
||||
if not (title or metadata.get('title_inferred')):
|
||||
title = metadata.get('title', config.title)
|
||||
|
||||
with open(os.path.join(self.pages_dir, page), "r") as f:
|
||||
with open(os.path.join(self.pages_dir, page), 'r') as f:
|
||||
return render_template(
|
||||
"article.html",
|
||||
'article.html',
|
||||
config=config,
|
||||
title=title,
|
||||
image=metadata.get("image"),
|
||||
description=metadata.get("description"),
|
||||
image=metadata.get('image'),
|
||||
description=metadata.get('description'),
|
||||
author=(
|
||||
re.match(r"(.+?)\s+<([^>]+>)", metadata["author"])[1]
|
||||
if "author" in metadata
|
||||
else None
|
||||
re.match(r'(.+?)\s+<([^>]+>)', metadata['author'])[1]
|
||||
if 'author' in metadata else None
|
||||
),
|
||||
author_email=(
|
||||
re.match(r"(.+?)\s+<([^>]+)>", metadata["author"])[2]
|
||||
if "author" in metadata
|
||||
else None
|
||||
re.match(r'(.+?)\s+<([^>]+)>', metadata['author'])[2]
|
||||
if 'author' in metadata else None
|
||||
),
|
||||
published=(
|
||||
metadata["published"].strftime("%b %d, %Y")
|
||||
if metadata.get("published")
|
||||
and not metadata.get("published_inferred")
|
||||
metadata['published'].strftime('%b %d, %Y')
|
||||
if metadata.get('published') and not metadata.get('published_inferred')
|
||||
else None
|
||||
),
|
||||
content=markdown(
|
||||
f.read(), extensions=["fenced_code", "codehilite", "tables", MarkdownLatex()]
|
||||
),
|
||||
content=markdown(f.read(), extensions=['fenced_code', 'codehilite', MarkdownLatex()]),
|
||||
skip_header=skip_header,
|
||||
skip_html_head=skip_html_head,
|
||||
)
|
||||
|
@ -154,25 +145,26 @@ class BlogApp(Flask):
|
|||
sorter: Type[PagesSorter] = PagesSortByTime,
|
||||
reverse: bool = True,
|
||||
) -> List[Tuple[int, dict]]:
|
||||
pages_dir = app.pages_dir.rstrip("/")
|
||||
pages_dir = app.pages_dir.rstrip('/')
|
||||
pages = [
|
||||
{
|
||||
"path": os.path.join(root[len(pages_dir) + 1 :], f),
|
||||
"folder": root[len(pages_dir) + 1 :],
|
||||
"content": (
|
||||
'path': os.path.join(root[len(pages_dir)+1:], f),
|
||||
'folder': root[len(pages_dir)+1:],
|
||||
'content': (
|
||||
self.get_page(
|
||||
os.path.join(root, f),
|
||||
skip_header=skip_header,
|
||||
skip_html_head=skip_html_head,
|
||||
)
|
||||
if with_content
|
||||
else ""
|
||||
if with_content else ''
|
||||
),
|
||||
**self.get_page_metadata(
|
||||
os.path.join(root[len(pages_dir)+1:], f)
|
||||
),
|
||||
**self.get_page_metadata(os.path.join(root[len(pages_dir) + 1 :], f)),
|
||||
}
|
||||
for root, _, files in os.walk(pages_dir, followlinks=True)
|
||||
for f in files
|
||||
if f.endswith(".md")
|
||||
if f.endswith('.md')
|
||||
]
|
||||
|
||||
sorter_func = sorter(pages)
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from flask import (
|
||||
jsonify,
|
||||
|
@ -117,12 +115,8 @@ def article_route(article: str):
|
|||
|
||||
@app.route("/rss", methods=["GET"])
|
||||
def rss_route():
|
||||
short_description = "short" in request.args or config.short_feed
|
||||
pages = app.get_pages(
|
||||
with_content=not short_description,
|
||||
skip_header=True,
|
||||
skip_html_head=True,
|
||||
)
|
||||
pages = app.get_pages(with_content=True, skip_header=True, skip_html_head=True)
|
||||
short_description = "short" in request.args
|
||||
|
||||
return Response(
|
||||
"""<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
@ -155,36 +149,32 @@ def rss_route():
|
|||
),
|
||||
items="\n\n".join(
|
||||
[
|
||||
(
|
||||
"""
|
||||
"""
|
||||
<item>
|
||||
<title>{title}</title>
|
||||
<link>{base_link}{link}</link>
|
||||
<pubDate>{published}</pubDate>
|
||||
<description><![CDATA[{content}]]></description>
|
||||
<media:content medium="image" url="{image}" width="200" height="150" />
|
||||
<pubDate>{published}</pubDate>"""
|
||||
+ (
|
||||
"<description><![CDATA[{content}]]></description>"
|
||||
if not config.short_feed
|
||||
else ""
|
||||
)
|
||||
+ """
|
||||
<media:content medium="image" url="{base_link}{image}" width="200" height="150" />
|
||||
</item>
|
||||
"""
|
||||
).format(
|
||||
""".format(
|
||||
base_link=config.link,
|
||||
title=page.get("title", "[No Title]"),
|
||||
link=page.get("uri", ""),
|
||||
published=(
|
||||
page["published"].strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
if "published" in page
|
||||
else ""
|
||||
),
|
||||
content=(
|
||||
page.get("description", "")
|
||||
if short_description
|
||||
else page.get("content", "")
|
||||
),
|
||||
image=(
|
||||
urljoin(config.link, page["image"])
|
||||
if page.get("image")
|
||||
and not re.search(r"^https?://", page["image"])
|
||||
else page.get("image", "")
|
||||
),
|
||||
published=page["published"].strftime(
|
||||
"%a, %d %b %Y %H:%M:%S GMT"
|
||||
)
|
||||
if "published" in page
|
||||
else "",
|
||||
content=page.get("description", "")
|
||||
if short_description
|
||||
else page.get("content", ""),
|
||||
image=page.get("image", ""),
|
||||
)
|
||||
for _, page in pages
|
||||
]
|
||||
|
|
|
@ -2,31 +2,22 @@ main .content {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.5em;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
main .content p,
|
||||
main .content ul,
|
||||
main .content ol {
|
||||
font-family: Lora, "Palatino Linotype", "Book Antiqua", "New York", "DejaVu serif", serif;
|
||||
main .content ul {
|
||||
text-align: justify;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
main .content code, .codehilite {
|
||||
font-size: 0.9em;
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
main {
|
||||
font-size: 0.95em;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
main p {
|
||||
padding: 0.25em;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,14 +45,13 @@ a:hover {
|
|||
|
||||
@media screen and (min-width: 1024px) {
|
||||
main .container {
|
||||
max-width: 40em;
|
||||
max-width: 35em;
|
||||
}
|
||||
}
|
||||
|
||||
.codehilite {
|
||||
padding: 0 .5em;
|
||||
overflow: auto;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.main-image-container {
|
||||
|
|
|
@ -2,8 +2,7 @@ html {
|
|||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
font-size: 20px;
|
||||
/* font-family: Lora, "Palatino Linotype", "Book Antiqua", "New York", "DejaVu serif", serif; */
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
@ -96,7 +95,18 @@ main {
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 0.25em;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
main {
|
||||
padding: 0 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
main {
|
||||
padding: 0 2em;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
@ -109,10 +119,6 @@ h2 {
|
|||
line-height: 1.1em;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
footer {
|
||||
width: 100%;
|
||||
font-size: .65em;
|
||||
|
@ -123,7 +129,3 @@ footer {
|
|||
text-align: center;
|
||||
box-shadow: 1px -2px 2px 0 #bbb;
|
||||
}
|
||||
|
||||
.index {
|
||||
font-family: -apple-system, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Open Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,36 +0,0 @@
|
|||
/* lora-regular - latin */
|
||||
@font-face {
|
||||
font-family: 'Lora';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/Lora-Regular.eot'); /* IE9 Compat Modes */
|
||||
src: local('Lora'), local('Lora-Regular'),
|
||||
url('/fonts/Lora-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('/fonts/Lora-Regular.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('/fonts/Lora-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('/fonts/Lora-Regular.ttf') format('truetype'); /* Safari, Android, iOS */
|
||||
}
|
||||
/* lora-700 - latin */
|
||||
@font-face {
|
||||
font-family: 'Lora';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('/fonts/Lora-Bold.eot'); /* IE9 Compat Modes */
|
||||
src: local('Lora Bold'), local('Lora-Bold'),
|
||||
url('/fonts/Lora-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('/fonts/Lora-Bold.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('/fonts/Lora-Bold.woff') format('woff'), /* Modern Browsers */
|
||||
url('/fonts/Lora-Bold.ttf') format('truetype'); /* Safari, Android, iOS */
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Lora';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('/fonts/Lora-Italic.eot'); /* IE9 Compat Modes */
|
||||
src: local('Lora Italic'), local('Lora-Italic'),
|
||||
url('/fonts/Lora-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('/fonts/Lora-Italic.woff2') format('woff2'), /* Super Modern Browsers */
|
||||
url('/fonts/Lora-Italic.woff') format('woff'), /* Modern Browsers */
|
||||
url('/fonts/Lora-Italic.ttf') format('truetype'); /* Safari, Android, iOS */
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@
|
|||
<!-- RSS feed -->
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ config.title }}" href="/rss" />
|
||||
<!-- Fonts & Styles -->
|
||||
<link rel="stylesheet" href="/fonts/lora.css">
|
||||
<link rel="stylesheet" href="/fonts/poppins.css">
|
||||
<link rel="stylesheet" href="/fonts/fira-sans.css">
|
||||
<link rel="stylesheet" href="/css/common.css">
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
[project]
|
||||
name = "madblog"
|
||||
description = "A general-purpose framework for automation"
|
||||
dynamic = ["version", "dependencies", "optional-dependencies", "entry-points", "license"]
|
||||
authors = [
|
||||
{name = "Fabio Manganiello", email = "fabio@manganiello.tech"},
|
||||
]
|
||||
|
||||
classifiers=[
|
||||
"Topic :: Utilities",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 4 - Beta",
|
||||
]
|
||||
|
||||
readme = "README.md"
|
||||
requires-python = '>= 3.8'
|
||||
keywords = ["blog", "markdown"]
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
version = {attr = "madblog.__version__"}
|
||||
dependencies = {file = "requirements.txt"}
|
||||
|
||||
[project.scripts]
|
||||
madblog = 'madblog:__main__'
|
||||
|
||||
[tool.bumpversion]
|
||||
current_version = "0.2.35"
|
||||
commit = true
|
||||
tag = true
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "madblog/__init__.py"
|
||||
|
||||
[[tool.bumpversion.files]]
|
||||
filename = "setup.py"
|
7
setup.cfg
Normal file
7
setup.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[bumpversion]
|
||||
current_version = 0.2.19
|
||||
commit = True
|
||||
tag = True
|
||||
|
||||
[metadata]
|
||||
description-file = README.md
|
39
setup.py
39
setup.py
|
@ -1,41 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def readfile(file):
|
||||
with open(file, "r") as f:
|
||||
with open(file, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
setup(
|
||||
name="madblog",
|
||||
version="0.2.35",
|
||||
author="Fabio Manganiello",
|
||||
author_email="info@fabiomanganiello.com",
|
||||
description="A minimal platform for Markdown-based blogs",
|
||||
license="MIT",
|
||||
python_requires=">= 3.8",
|
||||
keywords="blog markdown",
|
||||
url="https://git.platypush.tech/blacklight/madblog",
|
||||
packages=find_packages(include=["madblog"]),
|
||||
name='madblog',
|
||||
version='0.2.19',
|
||||
author='Fabio Manganiello',
|
||||
author_email='info@fabiomanganiello.com',
|
||||
description='A minimal platform for Markdown-based blogs',
|
||||
license='MIT',
|
||||
python_requires='>= 3.8',
|
||||
keywords='blog markdown',
|
||||
url='https://git.platypush.tech/blacklight/madblog',
|
||||
packages=find_packages(include=['madblog']),
|
||||
include_package_data=True,
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"madblog=madblog.cli:run",
|
||||
'console_scripts': [
|
||||
'madblog=madblog.cli:run',
|
||||
],
|
||||
},
|
||||
long_description=readfile("README.md"),
|
||||
long_description_content_type="text/markdown",
|
||||
long_description=readfile('README.md'),
|
||||
long_description_content_type='text/markdown',
|
||||
classifiers=[
|
||||
"Topic :: Utilities",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Development Status :: 4 - Beta",
|
||||
],
|
||||
install_requires=[
|
||||
"flask",
|
||||
"markdown",
|
||||
"pygments",
|
||||
"pyyaml",
|
||||
'flask',
|
||||
'markdown',
|
||||
'pygments',
|
||||
'pyyaml',
|
||||
],
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue