Watch
1
0
Fork
You've already forked www.xengi.de
0
This commit is contained in:
Ricardo (XenGi) Band 2025-01-27 11:22:22 +01:00
commit 29d565fd1c
No known key found for this signature in database
8 changed files with 530 additions and 160 deletions

366
.gitignore vendored
View file

@ -1,3 +1,363 @@
__pycache__ output/
.idea
output # Created by https://www.toptal.com/developers/gitignore/api/linux,windows,macos,direnv,vim,python,pycharm+all
# Edit at https://www.toptal.com/developers/gitignore?templates=linux,windows,macos,direnv,vim,python,pycharm+all
### direnv ###
.direnv
.envrc
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### PyCharm+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### PyCharm+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# LSP config files
pyrightconfig.json
### Vim ###
# Swap
[._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/linux,windows,macos,direnv,vim,python,pycharm+all

View file

@ -1,4 +1,4 @@
PY?=python3 PY?=
PELICAN?=pelican PELICAN?=pelican
PELICANOPTS= PELICANOPTS=
@ -8,6 +8,11 @@ OUTPUTDIR=$(BASEDIR)/output
CONFFILE=$(BASEDIR)/pelicanconf.py CONFFILE=$(BASEDIR)/pelicanconf.py
PUBLISHCONF=$(BASEDIR)/publishconf.py PUBLISHCONF=$(BASEDIR)/publishconf.py
SSH_HOST=kaede
SSH_PORT=10022
SSH_USER=xengi
SSH_TARGET_DIR=/srv/www/www.xengi.de
DEBUG ?= 0 DEBUG ?= 0
ifeq ($(DEBUG), 1) ifeq ($(DEBUG), 1)
@ -39,6 +44,9 @@ help:
@echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 ' @echo ' make serve-global [SERVER=0.0.0.0] serve (as root) to $(SERVER):80 '
@echo ' make devserver [PORT=8000] serve and regenerate together ' @echo ' make devserver [PORT=8000] serve and regenerate together '
@echo ' make devserver-global regenerate and serve on 0.0.0.0 ' @echo ' make devserver-global regenerate and serve on 0.0.0.0 '
@echo ' make ssh_upload upload the web site via SSH '
@echo ' make sftp_upload upload the web site via SFTP '
@echo ' make rsync_upload upload the web site via rsync+ssh '
@echo ' ' @echo ' '
@echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html ' @echo 'Set the DEBUG variable to 1 to enable debugging, e.g. make DEBUG=1 html '
@echo 'Set the RELATIVE variable to 1 to enable relative urls ' @echo 'Set the RELATIVE variable to 1 to enable relative urls '
@ -63,10 +71,19 @@ devserver:
"$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) "$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS)
devserver-global: devserver-global:
$(PELICAN) -lr $(INPUTDIR) -o $(OUTPUTDIR) -s $(CONFFILE) $(PELICANOPTS) -b 0.0.0.0 "$(PELICAN)" -lr "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(CONFFILE)" $(PELICANOPTS) -b 0.0.0.0
publish: publish:
"$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS) "$(PELICAN)" "$(INPUTDIR)" -o "$(OUTPUTDIR)" -s "$(PUBLISHCONF)" $(PELICANOPTS)
ssh_upload: publish
scp -P $(SSH_PORT) -r "$(OUTPUTDIR)"/* "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)"
.PHONY: html help clean regenerate serve serve-global devserver publish sftp_upload: publish
printf 'put -r $(OUTPUTDIR)/*' | sftp $(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)
rsync_upload: publish
rsync -e "ssh -p $(SSH_PORT)" -P -rvzc --include tags --cvs-exclude --delete "$(OUTPUTDIR)"/ "$(SSH_USER)@$(SSH_HOST):$(SSH_TARGET_DIR)"
.PHONY: html help clean regenerate serve serve-global devserver devserver-global publish ssh_upload sftp_upload rsync_upload

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

BIN
content/images/profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

View file

@ -1,7 +1,7 @@
title: Pokéshell title: Pokéshell
date: 2016-01-01 23:30 date: 2016-01-01 23:30
category: linux category: linux
tags: pokemon, shell, bash, fish, imagemagick tags: pokemon, shell, bash, fish, imagemagick, aur
slug: pokeshell slug: pokeshell
A friend of me had the idea of a shell displaying a random pokémon on launch. I liked the idea and couldn't resist to implement it over christmas. A friend of me had the idea of a shell displaying a random pokémon on launch. I liked the idea and couldn't resist to implement it over christmas.
@ -10,7 +10,7 @@ A friend of me had the idea of a shell displaying a random pokémon on launch. I
What the package does is, it downloads all pokémon images in a defined range which is 1-151 by default (I'm old these are the ones I know) and converts the images to escape sequences. These are saved in `/usr/share/pokeshell`. What the package does is, it downloads all pokémon images in a defined range which is 1-151 by default (I'm old these are the ones I know) and converts the images to escape sequences. These are saved in `/usr/share/pokeshell`.
Now when you ipopen a new shell you could display one of them randomly by putting this code in your `~/.bashrc`: Now when you open a new shell you could display one of them randomly by putting this code in your `~/.bashrc`:
```sh ```sh
cat /usr/share/pokeshell/\$((\$RANDOM % 151 + 1)).pokemon cat /usr/share/pokeshell/\$((\$RANDOM % 151 + 1)).pokemon
@ -27,7 +27,7 @@ end
The images are downloaded using the [Pokéapi][pokeapi]. For the conversation I use Imagemagick and a tool called [img2xterm][img2xterm]. The images are downloaded using the [Pokéapi][pokeapi]. For the conversation I use Imagemagick and a tool called [img2xterm][img2xterm].
You can find the package in the [AUR][aur-pokeshell]. You can find a package for all of this in the [AUR][aur-pokeshell].
[pokeapi]: http://pokeapi.co [pokeapi]: http://pokeapi.co

View file

@ -1,140 +1,111 @@
from datetime import datetime
AUTHOR = 'XenGi' AUTHOR = 'XenGi'
SITEURL = ''
SITENAME = 'XenGis blog' SITENAME = 'XenGis blog'
SITETITLE = 'XenGi' SITEURL = ""
SITESUBTITLE = '' PATH = "content"
SITEDESCRIPTION = 'A typical way to world domination'
SITELOGO = SITEURL + '/images/avatar.png'
FAVICON = SITEURL + '/images/favicon.ico'
BROWSER_COLOR = '#333'
ROBOTS = 'index, follow'
CC_LICENSE = {
'name': 'Creative Commons Attribution-ShareAlike',
'version': '4.0',
'slug': 'by-sa',
'icon': True,
'language': 'en_US',
}
COPYRIGHT_NAME = AUTHOR
COPYRIGHT_YEAR = datetime.now().year
STATIC_PATHS = [
'extra/custom.css'
]
EXTRA_PATH_METADATA = {
'extra/custom.css': {'path': 'static/custom.css'},
}
CUSTOM_CSS = 'static/custom.css'
MAIN_MENU = True
PLUGINS = [
'post_stats',
]
PLUGIN_PATHS = [
'plugins/'
]
JINJA_ENVIRONMENT = {}
I18N_TEMPLATES_LANG = 'en'
DEFAULT_LANG = 'en'
OG_LOCALE = 'en_US'
LOCALE = 'en_US'
THEME = 'flex'
OUTPUT_PATH = 'output/'
PATH = 'content/'
TIMEZONE = 'Europe/Berlin' TIMEZONE = 'Europe/Berlin'
DEFAULT_LANG = 'en'
# Feed generation is usually not desired when developing # Feed generation is usually not desired when developing
FEED_ALL_ATOM = None FEED_ALL_ATOM = None
CATEGORY_FEED_ATOM = None CATEGORY_FEED_ATOM = None
TRANSLATION_FEED_ATOM = None TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None AUTHOR_FEED_RSS = None
USE_FOLDER_AS_CATEGORY = False
# Blogroll
LINKS = (
("Noisy Cricket", "https://gitlab.com/noisy_cricket"),
("mkbd65", "https://gitlab.com/mkbd65"),
)
# Social widget # Social widget
MENUITEMS = [] SOCIAL = (
SOCIAL = [ ("gitlab", "https://gitlab.com/XenGi"),
('Email', 'mailto:blog@xengi.de'), ("github", "https://github.com/XenGi"),
] ("mastodon", "https://chaos.social/@xengi"),
MENUITEMS = [ ("matrix", "https://matrix.to/#/@xengi:xengi.de"),
('Archives', '/archives.html'), )
('Categories', '/categories.html'),
('Tags', '/tags.html'), DEFAULT_PAGINATION = 10
]
STATIC_PATHS = [
'images',
]
DEFAULT_PAGINATION = False
USE_GOOGLE_FONTS = True
# Uncomment following line if you want document-relative URLs when developing # Uncomment following line if you want document-relative URLs when developing
# RELATIVE_URLS = True # RELATIVE_URLS = True
DEFAULT_CATEGORY = 'misc' THEME = "flex"
PYGMENTS_STYLE = "monokai"
DEFAULT_CATEGORY = "misc"
DISPLAY_PAGES_ON_MENU = True DISPLAY_PAGES_ON_MENU = True
DISPLAY_CATEGORIES_ON_MENU = True DISPLAY_CATEGORIES_ON_MENU = True
MARKDOWN = { MARKDOWN = {
'extension_configs': { "extension_configs": {
'markdown.extensions.codehilite': {'css_class': 'highlight'}, "markdown.extensions.codehilite": {"css_class": "highlight"},
'markdown.extensions.extra': {}, "markdown.extensions.extra": {},
'markdown.extensions.meta': {}, "markdown.extensions.meta": {},
'markdown.extensions.nl2br': {}, "markdown.extensions.nl2br": {},
'markdown.extensions.smarty': {}, "markdown.extensions.smarty": {},
'markdown.extensions.sane_lists': {}, "markdown.extensions.sane_lists": {},
}, },
'output_format': 'xhtml5', "output_format": "html5",
} }
PAGE_PATHS = ['pages'] PAGE_PATHS = ['pages']
STATIC_PATHS = ['images'] STATIC_PATHS = ['images']
TYPOGRIFY = True TYPOGRIFY = True
ARTICLE_URL = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/'
ARTICLE_SAVE_AS = 'posts/{date:%Y}/{date:%b}/{date:%d}/{slug}/index.html'
PAGE_URL = 'pages/{slug}/'
PAGE_SAVE_AS = 'pages/{slug}/index.html'
AUTHOR_URL = 'author/{slug}.html'
AUTHOR_SAVE_AS = 'author/{slug}.html'
CATEGORY_URL = 'category/{slug}.html'
CATEGORY_SAVE_AS = 'category/{slug}.html'
TAG_URL = 'tag/{slug}.html'
TAG_SAVE_AS = 'tag/{slug}.html'
# Flex settings # Flex theme
## metadata for <head>
USE_LESS = True SITEDESCRIPTION = "XenGis blog"
## dark mode
THEME_COLOR = 'dark' THEME_COLOR = 'dark'
THEME_COLOR_AUTO_DETECT_BROWSER_PREFERENCE = True THEME_COLOR_AUTO_DETECT_BROWSER_PREFERENCE = True
THEME_COLOR_ENABLE_USER_OVERRIDE = True THEME_COLOR_ENABLE_USER_OVERRIDE = True
## code highlight ## code highlight
PYGMENTS_STYLE = 'emacs' PYGMENTS_STYLE = 'emacs'
PYGMENTS_STYLE_DARK = 'monokai' PYGMENTS_STYLE_DARK = 'monokai'
#PYGMENTS_STYLE = 'solarized-light'
#PYGMENTS_STYLE_DARK = 'solarized-dark'
# Plugin settings ## top menu
MENUITEMS = (
# Home
("Archives", "/archives.html"),
("Categories", "/categories.html"),
("Tags", "/tags.html"),
)
## related posts SITETITLE = "XenGis blog"
SITESUBTITLE = "&#x2620;&#xFE0F; Hacker&#x2122;&#xFE0F; | &#x1F3CD;&#xFE0F; Petrol head | &#x1F680; Tech fondler"
SITELOGO = SITEURL + "/images/profile.png"
FAVICON = SITEURL + "/images/favicon.ico"
RELATED_POSTS_MAX = 10 BROWSER_COLOR = "#333"
ROBOTS = "index, follow"
## sitemap CC_LICENSE = {
"name": "Creative Commons Attribution-NonCommercial 4.0 International",
SITEMAP = { "version": "4.0",
'format': 'xml', "slug": "by-nc",
'priorities': { "icon": True,
'articles': 0.5, "language": "en_US"
'indexes': 0.5,
'pages': 0.5
},
'changefreqs': {
'articles': 'monthly',
'indexes': 'daily',
'pages': 'monthly'
}
} }
from datetime import datetime
COPYRIGHT_YEAR = datetime.now().year
# <p xmlns:cc="http://creativecommons.org/ns#" xmlns:dct="http://purl.org/dc/terms/"><a property="dct:title" rel="cc:attributionURL" href="https://www.xengi.de">blog</a> by <a rel="cc:attributionURL dct:creator" property="cc:attributionName" href="https://www.xengi.de">XenGi</a> is licensed under <a href="https://creativecommons.org/licenses/by-nc/4.0/?ref=chooser-v1" target="_blank" rel="license noopener noreferrer" style="display:inline-block;">CC BY-NC 4.0<img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt=""><img style="height:22px!important;margin-left:3px;vertical-align:text-bottom;" src="https://mirrors.creativecommons.org/presskit/icons/nc.svg?ref=chooser-v1" alt=""></a></p>
## isso #STATIC_PATHS = ["extra/custom.css"]
#EXTRA_PATH_METADATA = {"extra/custom.css": {"path": "static/custom.css"}}
#CUSTOM_CSS = "static/custom.css"
MAIN_MENU = True
# TODO: remove ADD_THIS_ID
# TODO: remove DISQUS_SITENAME
# TODO: remove GOOGLE_ANALYTICS
# TODO: remove GOOGLE_TAG_MANAGER
PLUGINS = []
# Plugins
## Pelican-search Configuration ?
STORK_INPUT_OPTIONS = {"stemming": "English", "url_prefix": SITEURL}
ISSO_URL = ''

View file

@ -1,28 +1,26 @@
#!/usr/bin/env python3
# This file is only used if you use `make publish` or # This file is only used if you use `make publish` or
# explicitly specify it as your config file. # explicitly specify it as your config file.
import os import os
import sys import sys
sys.path.append(os.curdir) sys.path.append(os.curdir)
from pelicanconf import * from pelicanconf import *
# If your site is available via HTTPS, make sure SITEURL begins with https:// # If your site is available via HTTPS, make sure SITEURL begins with https://
SITEURL = 'https://xengi.de' SITEURL = "https://www.xengi.de"
RELATIVE_URLS = False RELATIVE_URLS = False
FEED_ATOM = 'feeds/atom.xml' FEED_ATOM = 'feeds/atom.xml'
FEED_RSS = 'feeds/rss.xml' FEED_RSS = 'feeds/rss.xml'
FEED_ALL_ATOM = 'feeds/all.atom.xml' FEED_ALL_ATOM = "feeds/all.atom.xml"
FEED_ALL_RSS = 'feeds/all.rss.xml' FEED_ALL_RSS = "feeds/all.rss.xml"
CATEGORY_FEED_ATOM = 'feeds/category/%s.atom.xml' CATEGORY_FEED_ATOM = "feeds/{slug}.atom.xml"
CATEGORY_FEED_RSS = 'feeds/category/%s.rss.xml' CATEGORY_FEED_RSS = "feeds/{slug}.rss.xml"
AUTHOR_FEED_ATOM = 'feeds/author/%s.atom.xml'
AUTHOR_FEED_RSS = 'feeds/author/%s.rss.xml'
TAG_FEED_ATOM = 'feeds/tag/%s.atom.xml'
TAG_FEED_RSS = 'feeds/tag/%s.rss.xml'
DELETE_OUTPUT_DIRECTORY = True DELETE_OUTPUT_DIRECTORY = True
USE_LESS = False # Following items are often useful when publishing
# DISQUS_SITENAME = ""
# GOOGLE_ANALYTICS = ""

View file

@ -1,55 +1,62 @@
# -*- coding: utf-8 -*-
import os import os
import shlex import shlex
import shutil import shutil
import sys import sys
import datetime
from invoke import task from invoke import task
from invoke.main import program from invoke.main import program
from invoke.util import cd
from pelican import main as pelican_main from pelican import main as pelican_main
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
from pelican.settings import DEFAULT_CONFIG, get_settings_from_file from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
SETTINGS_FILE_BASE = 'pelicanconf.py' OPEN_BROWSER_ON_SERVE = True
SETTINGS_FILE_BASE = "pelicanconf.py"
SETTINGS = {} SETTINGS = {}
SETTINGS.update(DEFAULT_CONFIG) SETTINGS.update(DEFAULT_CONFIG)
LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE) LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
SETTINGS.update(LOCAL_SETTINGS) SETTINGS.update(LOCAL_SETTINGS)
CONFIG = { CONFIG = {
'settings_base': SETTINGS_FILE_BASE, "settings_base": SETTINGS_FILE_BASE,
'settings_publish': 'publishconf.py', "settings_publish": "publishconf.py",
# Output path. Can be absolute or relative to tasks.py. Default: 'output' # Output path. Can be absolute or relative to tasks.py. Default: 'output'
'deploy_path': SETTINGS['OUTPUT_PATH'], "deploy_path": SETTINGS["OUTPUT_PATH"],
# Remote server configuration
"ssh_user": "xengi",
"ssh_host": "kaede",
"ssh_port": "10022",
"ssh_path": "/srv/www/www.xengi.de",
# Host and port for `serve` # Host and port for `serve`
'host': 'localhost', "host": "localhost",
'port': 8000, "port": 8000,
} }
@task @task
def clean(c): def clean(c):
"""Remove generated files""" """Remove generated files"""
if os.path.isdir(CONFIG['deploy_path']): if os.path.isdir(CONFIG["deploy_path"]):
shutil.rmtree(CONFIG['deploy_path']) shutil.rmtree(CONFIG["deploy_path"])
os.makedirs(CONFIG['deploy_path']) os.makedirs(CONFIG["deploy_path"])
@task @task
def build(c): def build(c):
"""Build local version of site""" """Build local version of site"""
pelican_run('-s {settings_base}'.format(**CONFIG)) pelican_run("-s {settings_base}".format(**CONFIG))
@task @task
def rebuild(c): def rebuild(c):
"""`build` with the delete switch""" """`build` with the delete switch"""
pelican_run('-d -s {settings_base}'.format(**CONFIG)) pelican_run("-d -s {settings_base}".format(**CONFIG))
@task @task
def regenerate(c): def regenerate(c):
"""Automatically regenerate site upon file modification""" """Automatically regenerate site upon file modification"""
pelican_run('-r -s {settings_base}'.format(**CONFIG)) pelican_run("-r -s {settings_base}".format(**CONFIG))
@task @task
def serve(c): def serve(c):
@ -59,23 +66,32 @@ def serve(c):
allow_reuse_address = True allow_reuse_address = True
server = AddressReuseTCPServer( server = AddressReuseTCPServer(
CONFIG['deploy_path'], CONFIG["deploy_path"],
(CONFIG['host'], CONFIG['port']), (CONFIG["host"], CONFIG["port"]),
ComplexHTTPRequestHandler) ComplexHTTPRequestHandler,
)
sys.stderr.write('Serving at {host}:{port} ...\n'.format(**CONFIG)) if OPEN_BROWSER_ON_SERVE:
# Open site in default browser
import webbrowser
webbrowser.open("http://{host}:{port}".format(**CONFIG))
sys.stderr.write("Serving at {host}:{port} ...\n".format(**CONFIG))
server.serve_forever() server.serve_forever()
@task @task
def reserve(c): def reserve(c):
"""`build`, then `serve`""" """`build`, then `serve`"""
build(c) build(c)
serve(c) serve(c)
@task @task
def preview(c): def preview(c):
"""Build production version of site""" """Build production version of site"""
pelican_run('-s {settings_publish}'.format(**CONFIG)) pelican_run("-s {settings_publish}".format(**CONFIG))
@task @task
def livereload(c): def livereload(c):
@ -83,44 +99,52 @@ def livereload(c):
from livereload import Server from livereload import Server
def cached_build(): def cached_build():
cmd = '-s {settings_base} -e CACHE_CONTENT=True LOAD_CONTENT_CACHE=True' cmd = "-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true"
pelican_run(cmd.format(**CONFIG)) pelican_run(cmd.format(**CONFIG))
cached_build() cached_build()
server = Server() server = Server()
theme_path = SETTINGS['THEME'] theme_path = SETTINGS["THEME"]
watched_globs = [ watched_globs = [
CONFIG['settings_base'], CONFIG["settings_base"],
'{}/templates/**/*.html'.format(theme_path), f"{theme_path}/templates/**/*.html",
] ]
content_file_extensions = ['.md', '.rst'] content_file_extensions = [".md", ".rst"]
for extension in content_file_extensions: for extension in content_file_extensions:
content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension) content_glob = "{}/**/*{}".format(SETTINGS["PATH"], extension)
watched_globs.append(content_glob) watched_globs.append(content_glob)
static_file_extensions = ['.css', '.js'] static_file_extensions = [".css", ".js"]
for extension in static_file_extensions: for extension in static_file_extensions:
static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension) static_file_glob = f"{theme_path}/static/**/*{extension}"
watched_globs.append(static_file_glob) watched_globs.append(static_file_glob)
for glob in watched_globs: for glob in watched_globs:
server.watch(glob, cached_build) server.watch(glob, cached_build)
server.serve(host=CONFIG['host'], port=CONFIG['port'], root=CONFIG['deploy_path'])
if OPEN_BROWSER_ON_SERVE:
# Open site in default browser
import webbrowser
webbrowser.open("http://{host}:{port}".format(**CONFIG))
server.serve(host=CONFIG["host"], port=CONFIG["port"], root=CONFIG["deploy_path"])
@task @task
def publish(c): def publish(c):
"""Publish to production via rsync""" """Publish to production via rsync"""
pelican_run('-s {settings_publish}'.format(**CONFIG)) pelican_run("-s {settings_publish}".format(**CONFIG))
c.run( c.run(
'rsync --delete --exclude ".DS_Store" -pthrvz -c ' 'rsync --delete --exclude ".DS_Store" -pthrvz -c '
'-e "ssh -p {ssh_port}" ' '-e "ssh -p {ssh_port}" '
'{} {ssh_user}@{ssh_host}:{ssh_path}'.format( "{} {ssh_user}@{ssh_host}:{ssh_path}".format(
CONFIG['deploy_path'].rstrip('/') + '/', CONFIG["deploy_path"].rstrip("/") + "/", **CONFIG
**CONFIG)) )
)
def pelican_run(cmd): def pelican_run(cmd):
cmd += ' ' + program.core.remainder # allows to pass-through args to pelican cmd += " " + program.core.remainder # allows to pass-through args to pelican
pelican_main(shlex.split(cmd)) pelican_main(shlex.split(cmd))