feat: ADD 'new' function to generate new instance to avoid git clone #1

This commit is contained in:
Caffeine Fueled 2026-07-04 08:54:05 +02:00
parent 8e91571121
commit 0cbd56b689
Signed by: cf7
GPG key ID: CA295D643074C68C
2 changed files with 119 additions and 13 deletions

View file

@ -2,22 +2,32 @@
import os
import re
import sys
from datetime import datetime
from pathlib import Path
from jinja2 import Environment, FileSystemLoader
import markdown
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_MAIN,
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
ENABLE_RSS_FEED, RSS_FEED_PATH,
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS,
BLOGROLL_PATH, ROOT_PAGE)
# Blog configuration lives in config.py in the working directory. It is absent
# when scaffolding a new blog into an empty directory (the `new` command), so a
# missing config.py must not stop this module from loading — the build command
# checks CONFIG_AVAILABLE below and reports it clearly.
try:
from config import (BLOG_TITLE, BLOG_DESCRIPTION, THEME, EXCLUDE_SECTIONS_FROM_MAIN,
NAVBAR_ITEMS, HIDE_LOGO, HIDE_TITLE, LOGO_PATH,
ENABLE_RSS_FEED, RSS_FEED_PATH,
BASE_URL, AUTHOR_NAME, AUTHOR_EMAIL, FEED_MAX_ITEMS,
BLOGROLL_PATH, ROOT_PAGE)
CONFIG_AVAILABLE = True
except ImportError:
CONFIG_AVAILABLE = False
# STRIP_IMAGE_EXIF is a newer setting; older config.py files may not define it.
# Default to enabled so the privacy feature works without a config update.
try:
from config import STRIP_IMAGE_EXIF
except ImportError:
STRIP_IMAGE_EXIF = True
if CONFIG_AVAILABLE:
try:
from config import STRIP_IMAGE_EXIF
except ImportError:
STRIP_IMAGE_EXIF = True
# Raster image formats whose metadata (EXIF/GPS/XMP/text chunks) is stripped on copy
STRIP_IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.tiff', '.tif', '.bmp'}
@ -543,9 +553,92 @@ class SSGGGenerator:
print(f"\n✓ Site generated successfully in {self.output_dir}/")
# Location of the default project files baked into the container image.
DEFAULT_SKELETON_DIR = '/usr/local/share/picopaper/skeleton'
# The blog content that `new` scaffolds. Only these are copied — the CLI itself
# (picopaper.py), requirements.txt and .gitignore belong to the tool/image, not
# the user's blog, so they are intentionally excluded.
SCAFFOLD_ITEMS = ('config.py', 'theme', 'items', 'images', 'static')
def scaffold_new(target_dir='.'):
"""Create a new picopaper blog in target_dir by copying the default files.
The source ("skeleton") defaults to the path baked into the container image
and can be overridden with the PICOPAPER_SKELETON environment variable.
Existing files in the target are never overwritten.
"""
import shutil
skeleton = Path(os.environ.get('PICOPAPER_SKELETON', DEFAULT_SKELETON_DIR))
target = Path(target_dir)
if not skeleton.is_dir():
print(f"Error: default project files not found at {skeleton}")
print("Run this inside the picopaper container, or point PICOPAPER_SKELETON "
"at a picopaper source directory.")
return 1
created, skipped = [], []
for name in SCAFFOLD_ITEMS:
source = skeleton / name
if not source.exists():
continue
dest = target / name
if dest.exists():
# Never overwrite the user's files — leave anything already there.
skipped.append(name)
continue
if source.is_dir():
shutil.copytree(source, dest)
else:
shutil.copy2(source, dest)
created.append(name)
if not created:
print("Nothing to create — this directory already contains a picopaper blog.")
return 0
print("✓ Created a new picopaper blog in the current directory:")
for name in created:
print(f" {name}")
if skipped:
print("\nLeft untouched (already present):")
for name in skipped:
print(f" {name}")
print("\nNext steps:")
print(" 1. Edit config.py (blog title, description, navbar).")
print(" 2. Add or edit Markdown posts in items/.")
print(" 3. Build the site by running the container with no command:")
print(" podman run --rm --userns=keep-id -v $(pwd):/app git.uphillsecurity.com/cf7/picopaper:latest")
print("\nYour generated site will appear in output/.")
return 0
def main():
generator = SSGGGenerator()
generator.generate()
args = sys.argv[1:]
command = args[0] if args else 'build'
if command in ('build', 'generate'):
if not CONFIG_AVAILABLE:
print("Error: no config.py found in the current directory.")
print("Run 'picopaper.py new' to scaffold a blog first, then edit config.py.")
sys.exit(1)
generator = SSGGGenerator()
generator.generate()
elif command == 'new':
sys.exit(scaffold_new())
elif command in ('help', '-h', '--help'):
print("Usage: picopaper.py [command]\n")
print("Commands:")
print(" build Generate the site from the current directory (default)")
print(" new Scaffold a new picopaper blog into the current directory")
else:
print(f"Unknown command: {command!r}")
print("Usage: picopaper.py [build|new] (run 'picopaper.py help' for details)")
sys.exit(2)
if __name__ == '__main__':
main()