flux.vision, tobias.raayoni-last.com and randogoth.com. They were one site until October 2026, serving four audiences from a single dated list, which is the only format that can hold unrelated things at once. flux.vision is recovered from the Wayback Machine, where it had sat since being deleted in 2021: 90 articles, closed and maintained rather than updated. tobias.raayoni-last.com is the professional record, 97 articles back to 1999. randogoth.com is the technical work, 41 articles across six areas, served as an ANSI bulletin board. History starts here deliberately. The development history, the 266-row inventory that records why each article went where it did, the Wayback pipelines, the pruned articles, the source archives and the full-size images are kept beside the repo rather than in it, so a clone is the web presence and not the argument that produced it. REFACTOR.md is gitignored for the same reason. See README.md for where that material lives, and keep it: the sort is not reproducible from these three directories alone. Each site documents itself in its own README, including the traps that cost time and look like nothing when you get them wrong.
143 lines
No EOL
4 KiB
Python
143 lines
No EOL
4 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import os
|
|
import shlex
|
|
import shutil
|
|
import sys
|
|
import datetime
|
|
|
|
from invoke import task
|
|
from invoke.main import program
|
|
from invoke.util import cd
|
|
from pelican import main as pelican_main
|
|
from pelican.server import ComplexHTTPRequestHandler, RootedHTTPServer
|
|
from pelican.settings import DEFAULT_CONFIG, get_settings_from_file
|
|
|
|
OPEN_BROWSER_ON_SERVE = True
|
|
SETTINGS_FILE_BASE = 'pelicanconf.py'
|
|
SETTINGS = {}
|
|
SETTINGS.update(DEFAULT_CONFIG)
|
|
LOCAL_SETTINGS = get_settings_from_file(SETTINGS_FILE_BASE)
|
|
SETTINGS.update(LOCAL_SETTINGS)
|
|
|
|
CONFIG = {
|
|
'settings_base': SETTINGS_FILE_BASE,
|
|
'settings_publish': 'publishconf.py',
|
|
# Output path. Can be absolute or relative to tasks.py. Default: 'output'
|
|
'deploy_path': SETTINGS['OUTPUT_PATH'],
|
|
# Remote server configuration
|
|
'ssh_user': 'root',
|
|
'ssh_host': 'localhost',
|
|
'ssh_port': '22',
|
|
'ssh_path': '/var/www',
|
|
# Host and port for `serve`
|
|
'host': 'localhost',
|
|
'port': 8000,
|
|
}
|
|
|
|
@task
|
|
def clean(c):
|
|
"""Remove generated files"""
|
|
if os.path.isdir(CONFIG['deploy_path']):
|
|
shutil.rmtree(CONFIG['deploy_path'])
|
|
os.makedirs(CONFIG['deploy_path'])
|
|
|
|
@task
|
|
def build(c):
|
|
"""Build local version of site"""
|
|
pelican_run('-s {settings_base}'.format(**CONFIG))
|
|
|
|
@task
|
|
def rebuild(c):
|
|
"""`build` with the delete switch"""
|
|
pelican_run('-d -s {settings_base}'.format(**CONFIG))
|
|
|
|
@task
|
|
def regenerate(c):
|
|
"""Automatically regenerate site upon file modification"""
|
|
pelican_run('-r -s {settings_base}'.format(**CONFIG))
|
|
|
|
@task
|
|
def serve(c):
|
|
"""Serve site at http://$HOST:$PORT/ (default is localhost:8000)"""
|
|
|
|
class AddressReuseTCPServer(RootedHTTPServer):
|
|
allow_reuse_address = True
|
|
|
|
server = AddressReuseTCPServer(
|
|
CONFIG['deploy_path'],
|
|
(CONFIG['host'], CONFIG['port']),
|
|
ComplexHTTPRequestHandler)
|
|
|
|
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()
|
|
|
|
@task
|
|
def reserve(c):
|
|
"""`build`, then `serve`"""
|
|
build(c)
|
|
serve(c)
|
|
|
|
@task
|
|
def preview(c):
|
|
"""Build production version of site"""
|
|
pelican_run('-s {settings_publish}'.format(**CONFIG))
|
|
|
|
@task
|
|
def livereload(c):
|
|
"""Automatically reload browser tab upon file modification."""
|
|
from livereload import Server
|
|
|
|
def cached_build():
|
|
cmd = '-s {settings_base} -e CACHE_CONTENT=true LOAD_CONTENT_CACHE=true'
|
|
pelican_run(cmd.format(**CONFIG))
|
|
|
|
cached_build()
|
|
server = Server()
|
|
theme_path = SETTINGS['THEME']
|
|
watched_globs = [
|
|
CONFIG['settings_base'],
|
|
'{}/templates/**/*.html'.format(theme_path),
|
|
]
|
|
|
|
content_file_extensions = ['.md', '.rst']
|
|
for extension in content_file_extensions:
|
|
content_glob = '{0}/**/*{1}'.format(SETTINGS['PATH'], extension)
|
|
watched_globs.append(content_glob)
|
|
|
|
static_file_extensions = ['.css', '.js']
|
|
for extension in static_file_extensions:
|
|
static_file_glob = '{0}/static/**/*{1}'.format(theme_path, extension)
|
|
watched_globs.append(static_file_glob)
|
|
|
|
for glob in watched_globs:
|
|
server.watch(glob, cached_build)
|
|
|
|
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
|
|
def publish(c):
|
|
"""Publish to production via rsync"""
|
|
pelican_run('-s {settings_publish}'.format(**CONFIG))
|
|
c.run(
|
|
'rsync --delete --exclude ".DS_Store" -pthrvz -c '
|
|
'-e "ssh -p {ssh_port}" '
|
|
'{} {ssh_user}@{ssh_host}:{ssh_path}'.format(
|
|
CONFIG['deploy_path'].rstrip('/') + '/',
|
|
**CONFIG))
|
|
|
|
|
|
def pelican_run(cmd):
|
|
cmd += ' ' + program.core.remainder # allows to pass-through args to pelican
|
|
pelican_main(shlex.split(cmd)) |