Gallery front
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
|
||||
def generate_css_variables(colors_dict, output_path):
|
||||
"""Generate css variables for theme colors"""
|
||||
css_lines = [":root {"]
|
||||
for key, value in colors_dict.items():
|
||||
css_lines.append(f" --color-{key.replace('_', '-')}: {value};")
|
||||
css_lines.append("}")
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(css_lines))
|
||||
logging.info(f"[✓] CSS variables written to {output_path}")
|
||||
|
||||
def generate_fonts_css(fonts_dir, output_path, fonts_cfg=None):
|
||||
"""Generate css variables fonts"""
|
||||
font_files = list(fonts_dir.glob("*"))
|
||||
font_faces = {}
|
||||
preload_links = []
|
||||
format_map = {".woff2": "woff2", ".woff": "woff", ".ttf": "truetype", ".otf": "opentype"}
|
||||
|
||||
for font_file in font_files:
|
||||
name = font_file.stem
|
||||
ext = font_file.suffix.lower()
|
||||
if ext not in format_map:
|
||||
continue
|
||||
font_faces.setdefault(name, []).append((font_file.name, format_map[ext]))
|
||||
dest_font_path = output_path.parent.parent / "fonts" / font_file.name
|
||||
dest_font_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
copyfile(font_file, dest_font_path)
|
||||
preload_links.append(
|
||||
f'<link rel="preload" href="fonts/{font_file.name}" as="font" type="font/{format_map[ext]}" crossorigin>'
|
||||
)
|
||||
|
||||
css_lines = []
|
||||
for font_name, sources in font_faces.items():
|
||||
css_lines.append(f"@font-face {{")
|
||||
css_lines.append(f" font-family: '{font_name}';")
|
||||
srcs = [f"url('../fonts/{file}') format('{fmt}')" for file, fmt in sorted(sources)]
|
||||
css_lines.append(f" src: {', '.join(srcs)};")
|
||||
css_lines.append(" font-weight: normal;")
|
||||
css_lines.append(" font-style: normal;")
|
||||
css_lines.append("}")
|
||||
|
||||
if fonts_cfg:
|
||||
css_lines.append(":root {")
|
||||
if "primary" in fonts_cfg:
|
||||
p = fonts_cfg["primary"]
|
||||
css_lines.append(f" --font-primary: '{p['name']}', {p['fallback']};")
|
||||
if "secondary" in fonts_cfg:
|
||||
s = fonts_cfg["secondary"]
|
||||
css_lines.append(f" --font-secondary: '{s['name']}', {s['fallback']};")
|
||||
css_lines.append("}")
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text("\n\n".join(css_lines), encoding="utf-8")
|
||||
logging.info(f"[✓] Generated fonts CSS: {output_path}")
|
||||
return preload_links
|
||||
|
||||
def generate_google_fonts_link(fonts):
|
||||
"""Generate src link for Google fonts"""
|
||||
if not fonts:
|
||||
return ""
|
||||
families = []
|
||||
for font in fonts:
|
||||
family = font["family"].replace(" ", "+")
|
||||
weights = font.get("weights", [])
|
||||
if weights:
|
||||
families.append(f"{family}:wght@{';'.join(weights)}")
|
||||
else:
|
||||
families.append(family)
|
||||
href = "https://fonts.googleapis.com/css2?" + "&".join(f"family={f}" for f in families) + "&display=swap"
|
||||
return f'<link href="{href}" rel="stylesheet">'
|
||||
@@ -0,0 +1,114 @@
|
||||
import yaml
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# YAML file paths
|
||||
GALLERY_YAML = "config/gallery.yaml"
|
||||
|
||||
# Image directories
|
||||
GALLERY_DIR = Path("config/photos/gallery")
|
||||
HERO_DIR = Path("config/photos/hero")
|
||||
|
||||
def load_yaml(path):
|
||||
"""Load gallery config .yaml file"""
|
||||
print(f"[→] Loading {path}...")
|
||||
if not os.path.exists(path):
|
||||
print(f"[✗] File not found: {path}")
|
||||
return {}
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
images = data.get("images", []) or []
|
||||
print(f"[✓] Loaded {len(images)} image(s) from {path}")
|
||||
return data
|
||||
|
||||
def save_yaml(data, path):
|
||||
"""Save modified gallery config .yaml file"""
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(data, f, sort_keys=False, allow_unicode=True)
|
||||
print(f"[✓] Saved updated YAML to {path}")
|
||||
|
||||
def get_all_image_paths(directory):
|
||||
"""Get the path to record for builded site"""
|
||||
return sorted([
|
||||
str(p.relative_to(directory.parent)).replace("\\", "/")
|
||||
for p in directory.rglob("*")
|
||||
if p.suffix.lower() in [".jpg", ".jpeg", ".png", ".webp"]
|
||||
])
|
||||
|
||||
def update_gallery():
|
||||
"""Update the gallery photo list"""
|
||||
print("\n=== Updating gallery.yaml (gallery section) ===")
|
||||
gallery = load_yaml(GALLERY_YAML)
|
||||
|
||||
# Access the 'gallery' section within the gallery data, or initialize it if it doesn't exist
|
||||
gallery_section = gallery.get("gallery", {})
|
||||
|
||||
# Access the 'images' list within the 'gallery' section, or initialize it if it doesn't exist
|
||||
gallery_images = gallery_section.get("images", [])
|
||||
|
||||
all_images = set(get_all_image_paths(GALLERY_DIR))
|
||||
known_images = {img["src"] for img in gallery_images}
|
||||
|
||||
# Add new images
|
||||
new_images = [
|
||||
{"src": path, "tags": []}
|
||||
for path in all_images
|
||||
if path not in known_images
|
||||
]
|
||||
if new_images:
|
||||
gallery_images.extend(new_images)
|
||||
print(f"[✓] Added {len(new_images)} new image(s) to gallery.yaml (gallery)")
|
||||
|
||||
# Remove deleted images
|
||||
deleted_images = known_images - all_images
|
||||
if deleted_images:
|
||||
gallery_images = [img for img in gallery_images if img["src"] not in deleted_images]
|
||||
print(f"[✓] Removed {len(deleted_images)} deleted image(s) from gallery.yaml (gallery)")
|
||||
|
||||
# Update the 'gallery' section with the modified 'images' list
|
||||
gallery_section["images"] = gallery_images
|
||||
gallery["gallery"] = gallery_section
|
||||
|
||||
save_yaml(gallery, GALLERY_YAML)
|
||||
|
||||
if not new_images and not deleted_images:
|
||||
print("[✓] No changes to gallery.yaml (gallery)")
|
||||
|
||||
def update_hero():
|
||||
"""Update the hero photo list"""
|
||||
print("\n=== Updating gallery.yaml (hero section) ===")
|
||||
gallery = load_yaml(GALLERY_YAML)
|
||||
|
||||
# Access the 'hero' section within the gallery data, or initialize it if it doesn't exist
|
||||
hero_section = gallery.get("hero", {})
|
||||
|
||||
# Access the 'images' list within the 'hero' section, or initialize it if it doesn't exist
|
||||
hero_images = hero_section.get("images", [])
|
||||
|
||||
all_images = set(get_all_image_paths(HERO_DIR))
|
||||
known_images = {img["src"] for img in hero_images}
|
||||
|
||||
# Add new images
|
||||
new_images = [
|
||||
{"src": path}
|
||||
for path in all_images
|
||||
if path not in known_images
|
||||
]
|
||||
if new_images:
|
||||
hero_images.extend(new_images)
|
||||
print(f"[✓] Added {len(new_images)} new image(s) to gallery.yaml (hero)")
|
||||
|
||||
# Remove deleted images
|
||||
deleted_images = known_images - all_images
|
||||
if deleted_images:
|
||||
hero_images = [img for img in hero_images if img["src"] not in deleted_images]
|
||||
print(f"[✓] Removed {len(deleted_images)} deleted image(s) from gallery.yaml (hero)")
|
||||
|
||||
# Update the 'hero' section with the modified 'images' list
|
||||
hero_section["images"] = hero_images
|
||||
gallery["hero"] = hero_section
|
||||
|
||||
save_yaml(gallery, GALLERY_YAML)
|
||||
|
||||
if not new_images and not deleted_images:
|
||||
print("[✓] No changes to gallery.yaml (hero)")
|
||||
@@ -0,0 +1,80 @@
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
def render_template(template_path, context):
|
||||
"""Render html templates"""
|
||||
with open(template_path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
for key, value in context.items():
|
||||
placeholder = "{{ " + key + " }}"
|
||||
content = content.replace(placeholder, str(value) if value is not None else "")
|
||||
return content
|
||||
|
||||
def render_gallery_images(images):
|
||||
"""Render the photo gallery"""
|
||||
html = ""
|
||||
for img in images:
|
||||
tags = " ".join(img.get("tags", []))
|
||||
tag_html = "".join(f'<span class="tag">#{t}</span>' for t in img.get("tags", []))
|
||||
html += f"""
|
||||
<div class="section" data-tags="{tags}">
|
||||
<div class="tags">{tag_html}</div>
|
||||
<img class="fade-in-img lazyload" data-src="/img/{img['src']}" alt="{img.get('alt', '')}" loading="lazy">
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
|
||||
def generate_gallery_json_from_images(images, output_dir):
|
||||
"""Generte the hero carrousel photo list"""
|
||||
try:
|
||||
img_list = [img["src"] for img in images]
|
||||
output_path = output_dir / "data" / "gallery.json"
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(img_list, f, indent=2)
|
||||
logging.info(f"[✓] Generated hero gallery JSON: {output_path}")
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Error generating gallery JSON: {e}")
|
||||
|
||||
def generate_robots_txt(canonical_url, allowed_paths, output_dir):
|
||||
"""Generate the robot.txt"""
|
||||
robots_lines = ["User-agent: *"]
|
||||
|
||||
# Block everything by default
|
||||
robots_lines.append("Disallow: /")
|
||||
|
||||
# Explicitly allow certain paths
|
||||
for path in allowed_paths:
|
||||
if not path.startswith("/"):
|
||||
path = "/" + path
|
||||
robots_lines.append(f"Allow: {path}")
|
||||
|
||||
robots_lines.append("")
|
||||
robots_lines.append(f"Sitemap: {canonical_url.rstrip('/')}/sitemap.xml")
|
||||
|
||||
content = "\n".join(robots_lines)
|
||||
output_path = Path(output_dir) / "robots.txt"
|
||||
|
||||
try:
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
logging.info(f"[✓] robots.txt generated at {output_path}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Failed to write robots.txt: {e}")
|
||||
|
||||
def generate_sitemap_xml(canonical_url, allowed_paths, output_dir):
|
||||
"""Generate the sitemap"""
|
||||
urlset_start = '<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
|
||||
urlset_end = '</urlset>\n'
|
||||
urls = ""
|
||||
for path in allowed_paths:
|
||||
loc = canonical_url.rstrip("/") + path
|
||||
urls += f" <url>\n <loc>{loc}</loc>\n </url>\n"
|
||||
sitemap_content = urlset_start + urls + urlset_end
|
||||
output_path = output_dir / "sitemap.xml"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(sitemap_content)
|
||||
logging.info(f"[✓] sitemap.xml generated at {output_path}")
|
||||
@@ -0,0 +1,123 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from PIL import Image, features
|
||||
from shutil import copyfile
|
||||
|
||||
def convert_and_resize_image(input_path, output_path, resize=True, max_width=1140):
|
||||
"""Convert an image to WebP (or JPEG fallback) and optionally resize it."""
|
||||
try:
|
||||
if not input_path.exists():
|
||||
logging.error(f"[✗] Image file not found: {input_path}")
|
||||
return
|
||||
|
||||
img = Image.open(input_path)
|
||||
if img.mode != "RGB":
|
||||
img = img.convert("RGB")
|
||||
|
||||
if resize:
|
||||
width, height = img.size
|
||||
if width > max_width:
|
||||
new_height = int((max_width / width) * height)
|
||||
img = img.resize((max_width, new_height), Image.LANCZOS)
|
||||
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Check WebP support, otherwise fallback to JPEG
|
||||
fmt = "WEBP" if features.check("webp") else "JPEG"
|
||||
if fmt == "JPEG":
|
||||
output_path = output_path.with_suffix(".jpg")
|
||||
|
||||
img.save(output_path, fmt, quality=90 if fmt == "JPEG" else 100)
|
||||
logging.info(f"[✓] Processed image: {input_path} → {output_path}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Error processing image {input_path}: {e}")
|
||||
|
||||
def process_images(images, resize_images, img_dir, build_dir):
|
||||
"""Process a list of image references and update paths to optimized versions."""
|
||||
for img in images:
|
||||
src_path = img_dir / img["src"]
|
||||
webp_path = build_dir / "img" / Path(img["src"]).with_suffix(".webp")
|
||||
convert_and_resize_image(src_path, webp_path, resize=resize_images)
|
||||
|
||||
if webp_path.exists():
|
||||
img["src"] = str(Path(img["src"]).with_suffix(".webp"))
|
||||
else:
|
||||
# Fallback if WebP not created
|
||||
jpg_path = webp_path.with_suffix(".jpg")
|
||||
if jpg_path.exists():
|
||||
img["src"] = str(Path(img["src"]).with_suffix(".jpg"))
|
||||
|
||||
def copy_original_images(images, img_dir, build_dir):
|
||||
"""Copy original image files without processing."""
|
||||
for img in images:
|
||||
src_path = img_dir / img["src"]
|
||||
dest_path = build_dir / "img" / img["src"]
|
||||
|
||||
try:
|
||||
if not src_path.exists():
|
||||
logging.error(f"[✗] Original image not found: {src_path}")
|
||||
continue
|
||||
|
||||
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
copyfile(src_path, dest_path)
|
||||
logging.info(f"[✓] Copied original: {src_path} → {dest_path}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Error copying {src_path}: {e}")
|
||||
|
||||
def get_favicon_path(theme_vars, theme_dir):
|
||||
"""Retrieve the favicon path from theme variables, ensuring it exists."""
|
||||
fav_path = theme_vars.get("favicon", {}).get("path")
|
||||
if not fav_path:
|
||||
logging.warning("[~] No favicon path defined in theme.yaml")
|
||||
return None
|
||||
|
||||
path = Path(fav_path)
|
||||
if not path.is_absolute():
|
||||
path = theme_dir / path
|
||||
|
||||
if not path.exists():
|
||||
logging.error(f"[✗] Favicon not found: {path}")
|
||||
return None
|
||||
|
||||
return path
|
||||
|
||||
def generate_favicons_from_logo(theme_vars, theme_dir, output_dir):
|
||||
"""Generate multiple PNG favicons from a single source image."""
|
||||
logo_path = get_favicon_path(theme_vars, theme_dir)
|
||||
if not logo_path:
|
||||
logging.warning("[~] PNG favicons not generated.")
|
||||
return
|
||||
|
||||
try:
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
specs = [
|
||||
(32, "favicon-32.png"), (96, "favicon-96.png"), (128, "favicon-128.png"),
|
||||
(192, "favicon-192.png"), (196, "favicon-196.png"),
|
||||
(152, "favicon-152.png"), (180, "favicon-180.png")
|
||||
]
|
||||
img = Image.open(logo_path).convert("RGBA")
|
||||
for size, name in specs:
|
||||
img.resize((size, size), Image.LANCZOS).save(output_dir / name, format="PNG")
|
||||
|
||||
logging.info(f"[✓] PNG favicons generated in {output_dir}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Error generating PNG favicons: {e}")
|
||||
|
||||
def generate_favicon_ico(theme_vars, theme_dir, output_path):
|
||||
"""Generate a multi-size favicon.ico from a source image."""
|
||||
logo_path = get_favicon_path(theme_vars, theme_dir)
|
||||
if not logo_path:
|
||||
logging.warning("[~] favicon.ico not generated.")
|
||||
return
|
||||
|
||||
try:
|
||||
img = Image.open(logo_path).convert("RGBA")
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
img.save(output_path, format="ICO", sizes=[(16, 16), (32, 32), (48, 48)])
|
||||
logging.info(f"[✓] favicon.ico generated in {output_path}")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Error generating favicon.ico: {e}")
|
||||
@@ -0,0 +1,189 @@
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
from PIL import Image
|
||||
from .utils import ensure_dir, copy_assets, load_yaml, load_theme_config
|
||||
from .css_generator import generate_css_variables, generate_fonts_css, generate_google_fonts_link
|
||||
from .image_processor import process_images, copy_original_images, convert_and_resize_image, generate_favicons_from_logo, generate_favicon_ico
|
||||
from .html_generator import render_template, render_gallery_images, generate_gallery_json_from_images, generate_robots_txt, generate_sitemap_xml
|
||||
|
||||
# Configure logging to display only the messages
|
||||
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
||||
|
||||
# Define key directories used throughout the script
|
||||
SRC_DIR = Path.cwd()
|
||||
BUILD_DIR = SRC_DIR / "output"
|
||||
TEMPLATE_DIR = SRC_DIR / "src/templates"
|
||||
IMG_DIR = SRC_DIR / "config/photos"
|
||||
JS_DIR = SRC_DIR / "src/public/js"
|
||||
STYLE_DIR = SRC_DIR / "src/public/style"
|
||||
GALLERY_FILE = SRC_DIR / "config/gallery.yaml"
|
||||
SITE_FILE = SRC_DIR / "config/site.yaml"
|
||||
THEMES_DIR = SRC_DIR / "config/themes"
|
||||
|
||||
def build():
|
||||
build_version = "v1.3.1"
|
||||
logging.info("\n")
|
||||
logging.info("=" * 24)
|
||||
logging.info(f"🚀 Lumeex builder {build_version}")
|
||||
logging.info("=" * 24)
|
||||
logging.info("\n === Starting build === ")
|
||||
ensure_dir(BUILD_DIR)
|
||||
copy_assets(JS_DIR, STYLE_DIR, BUILD_DIR)
|
||||
|
||||
# Defining build vars
|
||||
build_date = datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
build_date_version = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
site_vars = load_yaml(SITE_FILE)
|
||||
gallery_vars = load_yaml(GALLERY_FILE)
|
||||
build_section = site_vars.get("build", {})
|
||||
theme_name = site_vars.get("build", {}).get("theme", "default")
|
||||
theme_vars, theme_dir = load_theme_config(theme_name, THEMES_DIR)
|
||||
fonts_dir = theme_dir / "fonts"
|
||||
theme_css_path = theme_dir / "theme.css"
|
||||
canonical_url = site_vars.get("info", {}).get("canonical", "").rstrip("/")
|
||||
canonical_home = f"{canonical_url}/"
|
||||
canonical_legals = f"{canonical_url}/legals/"
|
||||
|
||||
# Copying theme.css if existing
|
||||
if theme_css_path.exists():
|
||||
dest_theme_css = BUILD_DIR / "style" / "theme.css"
|
||||
dest_theme_css.parent.mkdir(parents=True, exist_ok=True)
|
||||
copyfile(theme_css_path, dest_theme_css)
|
||||
theme_css = f'<link rel="stylesheet" href="/style/theme.css?build_date={build_date}">'
|
||||
logging.info(f"[✓] Theme CSS found, copied to build folder: {dest_theme_css}")
|
||||
else:
|
||||
theme_css = ""
|
||||
logging.warning(f"[~] No theme.css found in {theme_css_path}, skipping theme CSS injection.")
|
||||
|
||||
preload_links = generate_fonts_css(fonts_dir, BUILD_DIR / "style" / "fonts.css", fonts_cfg=theme_vars.get("fonts"))
|
||||
generate_css_variables(theme_vars.get("colors", {}), BUILD_DIR / "style" / "colors.css")
|
||||
generate_favicons_from_logo(theme_vars, theme_dir, BUILD_DIR / "img" / "favicon")
|
||||
generate_favicon_ico(theme_vars, theme_dir, BUILD_DIR / "favicon.ico")
|
||||
|
||||
# Converting and resizing images if enabled
|
||||
convert_images = build_section.get("convert_images", True)
|
||||
resize_images = build_section.get("resize_images", True)
|
||||
logging.info(f"[~] convert_images = {convert_images}")
|
||||
logging.info(f"[~] resize_images = {resize_images}")
|
||||
|
||||
hero_images = gallery_vars.get("hero", {}).get("images", [])
|
||||
gallery_images = gallery_vars.get("gallery", {}).get("images", [])
|
||||
|
||||
if convert_images:
|
||||
process_images(hero_images, resize_images, IMG_DIR, BUILD_DIR)
|
||||
process_images(gallery_images, resize_images, IMG_DIR, BUILD_DIR)
|
||||
else:
|
||||
copy_original_images(hero_images, IMG_DIR, BUILD_DIR)
|
||||
copy_original_images(gallery_images, IMG_DIR, BUILD_DIR)
|
||||
|
||||
if "hero" not in site_vars:
|
||||
site_vars["hero"] = {} # Initialize an empty hero section
|
||||
|
||||
# Adding menu
|
||||
menu_html = "\n".join(
|
||||
f'<li class="nav-item appear"><a href="{item["href"]}">{item["label"]}</a></li>'
|
||||
for item in site_vars.get("menu", {}).get("items", [])
|
||||
)
|
||||
site_vars["hero"]["menu_items"] = menu_html
|
||||
if "footer" in site_vars:
|
||||
site_vars["footer"]["menu_items"] = menu_html
|
||||
|
||||
# Adding Google fonts if existing
|
||||
google_fonts_link = generate_google_fonts_link(theme_vars.get("google_fonts", []))
|
||||
logging.info(f"[✓] Google Fonts link generated")
|
||||
|
||||
# Generating thumbnail
|
||||
thumbnail_path = site_vars.get("social", {}).get("thumbnail")
|
||||
if thumbnail_path:
|
||||
src_thumb = IMG_DIR / thumbnail_path
|
||||
dest_thumb_dir = BUILD_DIR / "img" / "social"
|
||||
dest_thumb_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest_thumb = dest_thumb_dir / Path(thumbnail_path).name
|
||||
try:
|
||||
img = Image.open(src_thumb)
|
||||
img = img.convert("RGB")
|
||||
img = img.resize((1200, 630), Image.LANCZOS)
|
||||
img.save(dest_thumb, "JPEG", quality=90)
|
||||
logging.info(f"[✓] Thumbnail resized and saved to {dest_thumb}")
|
||||
except Exception as e:
|
||||
logging.error(f"[✗] Failed to process thumbnail: {e}")
|
||||
else:
|
||||
logging.warning("[~] No thumbnail found in social section")
|
||||
|
||||
# Defining head variables
|
||||
head_vars = dict(site_vars.get("info", {}))
|
||||
head_vars.update(theme_vars.get("colors", {}))
|
||||
head_vars.update(site_vars.get("social", {}))
|
||||
head_vars["thumbnail"] = f"/img/social/{Path(thumbnail_path).name}" if thumbnail_path else ""
|
||||
head_vars["google_fonts_link"] = google_fonts_link
|
||||
head_vars["font_preloads"] = "\n".join(preload_links)
|
||||
head_vars["theme_css"] = theme_css
|
||||
head_vars["build_date"] = build_date
|
||||
head_vars["canonical"] = canonical_home
|
||||
|
||||
# Render the home page
|
||||
head = render_template(TEMPLATE_DIR / "head.html", head_vars)
|
||||
hero = render_template(TEMPLATE_DIR / "hero.html", {**site_vars["hero"], **head_vars})
|
||||
footer = render_template(TEMPLATE_DIR / "footer.html", {**site_vars.get("footer", {}), **head_vars})
|
||||
gallery_html = render_gallery_images(gallery_images)
|
||||
gallery = render_template(TEMPLATE_DIR / "gallery.html", {"gallery_images": gallery_html})
|
||||
|
||||
signature = f"<!-- Build with Lumeex {build_version} | https://git.djeex.fr/Djeex/lumeex | {build_date_version} -->"
|
||||
body = f"""
|
||||
<body>
|
||||
<div class="page-loader"><div class="spinner"></div></div>
|
||||
{hero}
|
||||
{gallery}
|
||||
{footer}
|
||||
</body>
|
||||
"""
|
||||
output_file = BUILD_DIR / "index.html"
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write(f"<!DOCTYPE html>\n{signature}\n<html lang='en'>\n{head}\n{body}\n</html>")
|
||||
logging.info(f"[✓] HTML generated: {output_file}")
|
||||
|
||||
# Rendering legals page
|
||||
head_vars["canonical"] = canonical_legals
|
||||
|
||||
legals_vars = site_vars.get("legals", {})
|
||||
if legals_vars:
|
||||
head = render_template(TEMPLATE_DIR / "head.html", head_vars)
|
||||
|
||||
ip_paragraphs = legals_vars.get("intellectual_property", [])
|
||||
paragraphs_html = "\n".join(f"<p>{item['paragraph']}</p>" for item in ip_paragraphs)
|
||||
legals_context = {
|
||||
"hoster_name": legals_vars.get("hoster_name", ""),
|
||||
"hoster_adress": legals_vars.get("hoster_adress", ""),
|
||||
"hoster_contact": legals_vars.get("hoster_contact", ""),
|
||||
"intellectual_property": paragraphs_html,
|
||||
}
|
||||
legals_body = render_template(TEMPLATE_DIR / "legals.html", legals_context)
|
||||
legals_html = f"<!DOCTYPE html>\n{signature}\n<html lang='en'>\n{head}\n{legals_body}\n{footer}\n</html>"
|
||||
output_legals = BUILD_DIR / "legals" / "index.html"
|
||||
output_legals.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_legals, "w", encoding="utf-8") as f:
|
||||
f.write(legals_html)
|
||||
logging.info(f"[✓] Legals page generated: {output_legals}")
|
||||
else:
|
||||
logging.warning("[~] No legals section found in site.yaml")
|
||||
|
||||
# Hero carrousel generator
|
||||
if hero_images:
|
||||
generate_gallery_json_from_images(hero_images, BUILD_DIR)
|
||||
else:
|
||||
logging.warning("[~] No hero images found, skipping JSON generation.")
|
||||
|
||||
# Sitemap and robot.txt generator
|
||||
site_info = site_vars.get("info", {})
|
||||
canonical_url = site_info.get("canonical", "").rstrip("/")
|
||||
if canonical_url:
|
||||
allowed_pages = ["/", "/legals/"]
|
||||
generate_robots_txt(canonical_url, allowed_pages, BUILD_DIR)
|
||||
generate_sitemap_xml(canonical_url, allowed_pages, BUILD_DIR)
|
||||
else:
|
||||
logging.warning("[~] No canonical URL found in site.yaml info section, skipping robots.txt and sitemap.xml generation.")
|
||||
|
||||
logging.info("✅ Build complete.")
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import yaml
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from shutil import copytree, rmtree, copyfile
|
||||
|
||||
def load_yaml(path):
|
||||
"""Load gallery and site .yaml conf"""
|
||||
if not path.exists():
|
||||
logging.warning(f"[!] YAML file not found: {path}")
|
||||
return {}
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def load_theme_config(theme_name, themes_dir):
|
||||
"""Load theme.yaml"""
|
||||
theme_dir = themes_dir / theme_name
|
||||
theme_config_path = theme_dir / "theme.yaml"
|
||||
if not theme_config_path.exists():
|
||||
raise FileNotFoundError(f"[✗] Theme config not found: {theme_config_path}")
|
||||
with open(theme_config_path, "r", encoding="utf-8") as f:
|
||||
theme_vars = yaml.safe_load(f)
|
||||
return theme_vars, theme_dir
|
||||
|
||||
def clear_dir(path: Path):
|
||||
"""Clear the output dir"""
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True)
|
||||
return
|
||||
for child in path.iterdir():
|
||||
if child.is_file() or child.is_symlink():
|
||||
child.unlink()
|
||||
elif child.is_dir():
|
||||
rmtree(child)
|
||||
|
||||
def ensure_dir(path: Path):
|
||||
"""Create the output dir if it does not exist"""
|
||||
if not path.exists():
|
||||
path.mkdir(parents=True)
|
||||
else:
|
||||
clear_dir(path)
|
||||
|
||||
def copy_assets(js_dir, style_dir, build_dir):
|
||||
"""Copy public assets to output dir"""
|
||||
for folder in [js_dir, style_dir]:
|
||||
if folder.exists():
|
||||
dest = build_dir / folder.name
|
||||
copytree(folder, dest)
|
||||
logging.info(f"[✓] Copied assets from {folder.name}")
|
||||
else:
|
||||
logging.warning(f"[~] Skipped missing folder: {folder.name}")
|
||||
Reference in New Issue
Block a user