Pi66

Home | Repos | About | Blog | Gaza

merodi
A markdown-based static site generator.
git clone https://git.pi66.xyz/merodi

About | Files | Commits | Refs | License


feat:add build modes (draft/release)

Author: pixel
Date: 2026-07-22 09:31
commit: 1b8d7d75

Stat

A src/api.py 1 +1 -0
M src/build.py 60 +43 -17
M src/config.py 30 +7 -23
M src/fileops.py 6 +4 -2
M src/init_project.py 22 +12 -10
M src/main.py 11 +7 -4
M src/modules.py 49 +25 -24

Diff

diff --git a/src/api.py b/src/api.py
index ea920cd..a76eac0 100644
--- a/src/api.py
+++ b/src/api.py
@@ -24,6 +24,7 @@ class API:
         self.globals = GlobalStore()
         self.log = None
         self.config = None
+        self.mode = "draft"

api = API()

diff --git a/src/build.py b/src/build.py index 87afdef..004a822 100644 --- a/src/build.py +++ b/src/build.py @@ -190,7 +190,7 @@ def compile_file(md_file, html_dest, config=None, plugins=None, force: bool = Fa hook_call("on_page_built", md_file, html_dest) return result

...
Show all
diff --git a/src/api.py b/src/api.py
index ea920cd..a76eac0 100644
--- a/src/api.py
+++ b/src/api.py
@@ -24,6 +24,7 @@ class API:
         self.globals = GlobalStore()
         self.log = None
         self.config = None
+        self.mode = "draft"

api = API()

diff --git a/src/build.py b/src/build.py index 87afdef..004a822 100644 --- a/src/build.py +++ b/src/build.py @@ -190,7 +190,7 @@ def compile_file(md_file, html_dest, config=None, plugins=None, force: bool = Fa hook_call("on_page_built", md_file, html_dest) return result

-def walk_and_build(config, plugins, force_all=False): +def walk_and_build(config, plugins, dest, force_all=False): md_path = config.tree.markdown hook_call("on_walk_start", config) files = [] @@ -201,16 +201,35 @@ def walk_and_build(config, plugins, force_all=False): hook_call("on_walk_file", md_file) md_relpath = path.relpath(md_file, md_path) if is_dotfile(md_relpath): continue - html_dest = path.join(config.tree.dest, md_relpath) + html_dest = path.join(dest, md_relpath) if html_dest.endswith(".md"): html_dest = html_dest.removesuffix(".md") + ".html" source_file = readlink(md_file) if path.islink(md_file) else md_file compile_file(source_file, html_dest, config, plugins, force=force_all) hook_call("on_walk_end", config)

-def build_all(config, plugins): +def build_all(config, plugins, dest): clear_all_hashes(config) - walk_and_build(config, plugins, force_all=True) + walk_and_build(config, plugins, dest, force_all=True) + +def validate_build(config, plugins): + errors = [] + md_path = config.tree.markdown + for parent, _, filenames in walk(md_path): + for filename in filenames: + md_file = path.join(parent, filename) + if is_dotfile(path.relpath(md_file, md_path)): + continue + source = readlink(md_file) if path.islink(md_file) else md_file + try: + compile_page(read_md_content(source), config=config, plugins=plugins) + except Exception as e: + errors.append(f"{path.relpath(md_file, md_path)}: {e}") + if errors: + for e in errors: + warn(e) + raise Exception(f"Validation failed with {len(errors)} error(s)") + info("Validation passed.")

def build_if_file(project_path, file): if project_path: @@ -220,20 +239,27 @@ def build_if_file(project_path, file): else: compile_file(file[0], file[1])

-def build(building_type: str, project_path: str, file: list[str]): +def build(mode, project_path, file, validate=False, force=False): try: if file: - build_if_file(project_path, file) - else: - project_path = project_path if project_path else getcwd() - find_project_from_path(project_path) - chdir(project_path) - config = load_config() - plugins = load_plugins(config) - hook_call("on_build_start", config) - walk_and_build(config, plugins) - hook_call("on_build_end", config) - - + return build_if_file(project_path, file) + + project_path = project_path or getcwd() + find_project_from_path(project_path) + chdir(project_path) + config = load_config() + plugins = load_plugins(config) + dest = config.tree.draft_dest if mode == "draft" else config.tree.release_dest + + if validate: + validate_build(config, plugins) + if mode == "release": + import shutil + if path.exists(dest): + shutil.rmtree(dest) + + hook_call("on_build_start", config) + walk_and_build(config, plugins, dest, force_all=force) + hook_call("on_build_end", config) except Exception as e: fatal(e, f"Build failed: {e}") diff --git a/src/config.py b/src/config.py index 63ee3be..c5233e9 100644 --- a/src/config.py +++ b/src/config.py @@ -22,30 +22,14 @@ def load_project_config(config) -> Project: )

def load_tree_config(config) -> Tree: - markdown = config["tree"].get("markdown", "src/md") - static = config["tree"].get("static", "src/static") - templates = config["tree"].get("templates", "src/templates") - dest = config["tree"].get("dest", "src/dest") - plugins = config["tree"].get("plugins", "src/plugins.py") + t = config["tree"] + markdown, static, templates = t["markdown"], t["static"], t["templates"] + draft_dest, release_dest, plugins = t["draft_dest"], t["release_dest"], t["plugins"] + for name, p in [("markdown", markdown), ("static", static), ("templates", templates), ("plugins", plugins)]: + if not exists(p): + raise FileNotFoundError(f"{name} directory does not exist: {p}") + return Tree(markdown=markdown, static=static, templates=templates, draft_dest=draft_dest, release_dest=release_dest, plugins=plugins)

- if not exists(markdown): - raise FileNotFoundError(f"markdown directory does not exist: {markdown}") - if not exists(static): - raise FileNotFoundError(f"static directory does not exist: {static}") - if not exists(templates): - raise FileNotFoundError(f"templates directory does not exist: {templates}") - if not exists(plugins): - raise FileNotFoundError(f"plugins file does not exist: {plugins}") - if not exists(dest): - makedirs(dest) - - return Tree( - markdown = markdown, - static = static, - templates = templates, - dest = dest, - plugins = plugins, - )

def load_webview_config(config) -> Webview: return Webview( diff --git a/src/fileops.py b/src/fileops.py index 396870f..0cad009 100644 --- a/src/fileops.py +++ b/src/fileops.py @@ -19,7 +19,8 @@ def resolve_tree_paths(project_dir: str, tree: Tree) -> Tree: markdown=path.join(project_dir, tree.markdown), static=path.join(project_dir, tree.static), templates=path.join(project_dir, tree.templates), - dest=path.join(project_dir, tree.dest), + draft_dest=path.join(project_dir, tree.draft_dest), + release_dest=path.join(project_dir, tree.release_dest), plugins=path.join(project_dir, tree.plugins), )

@@ -28,5 +29,6 @@ def create_tree_dirs (tree:Tree) -> None: makedirs(tree.markdown , exist_ok=True) makedirs(tree.static , exist_ok=True) makedirs(tree.templates, exist_ok=True) - makedirs(tree.dest , exist_ok=True) + makedirs(tree.draft_dest , exist_ok=True) + makedirs(tree.release_dest , exist_ok=True) makedirs(tree.plugins , exist_ok=True) diff --git a/src/init_project.py b/src/init_project.py index 62f3ba9..2ac7973 100644 --- a/src/init_project.py +++ b/src/init_project.py @@ -15,11 +15,12 @@ def init_config_struct(project_name:str) -> Config: description = "Add your description here", ), tree = Tree( - markdown = "src/md", - static = "src/static", - templates = "src/templates", - dest = "src/dest", - plugins = "src/plugins", + markdown = "src/md", + static = "src/static", + templates = "src/templates", + draft_dest = "build/draft", + release_dest = "build/release", + plugins = "src/plugins", ), webview = Webview( host = "localhost", @@ -43,11 +44,12 @@ version = "{config.project.version}" description = "{config.project.description}"

[tree] -markdown = "{config.tree.markdown}" -static = "{config.tree.static}" -templates = "{config.tree.templates}" -dest = "{config.tree.dest}" -plugins = "{config.tree.plugins}" +markdown = "{config.tree.markdown}" +static = "{config.tree.static}" +templates = "{config.tree.templates}" +draft_dest = "{config.tree.draft_dest}" +release_dest = "{config.tree.release_dest}" +plugins = "{config.tree.plugins}"

[webview] host = "{config.webview.host}" diff --git a/src/main.py b/src/main.py index 73b78a1..36f110c 100755 --- a/src/main.py +++ b/src/main.py @@ -27,8 +27,9 @@ def main(): webview_parser.add_argument("path", nargs="?")

 build_parser = sub_parser.add_parser("build", parents=[common])

- build_parser.add_argument("--release", action="store_true") - build_parser.add_argument("--debug", action="store_true") + build_mode = build_parser.add_mutually_exclusive_group() + build_mode.add_argument("--draft", action="store_true", default=True) + build_mode.add_argument("--release", action="store_true") build_parser.add_argument("path", nargs="?") build_parser.add_argument("--file", nargs=2, metavar=("SRC", "DEST"))

@@ -53,8 +54,11 @@ def main(): init(project_path = args.path)

 elif args.command == "build":

+ from .api import api + mode = "release" if args.release else "draft" + api.mode = mode from .build import build - build(building_type = "release" if args.release else "debug", project_path = args.path, file=args.file) + build(mode = mode, project_path = args.path, file=args.file)

 elif args.command == "webview":
     from .webviewer import run

@@ -65,4 +69,3 @@ def main(): run_watcher(args.path)

 atexit.register(lambda: hook_call("on_end"))

- diff --git a/src/modules.py b/src/modules.py index 9e8b61c..8267fc1 100644 --- a/src/modules.py +++ b/src/modules.py @@ -1,39 +1,40 @@ from dataclasses import dataclass

-@dataclass() +@dataclass class Project: - name : str - version : str - description : str + name: str + version: str + description: str

-@dataclass() +@dataclass class Tree: - markdown : str - static : str - templates : str - dest : str - plugins : str + markdown: str + static: str + templates: str + draft_dest: str + release_dest: str + plugins: str

-@dataclass() +@dataclass class Webview: - host : str - port : int - dev_tools : str - html_path : str - static_path : str + host: str + port: int + dev_tools: str + html_path: str + static_path: str

@dataclass class Extras: - highlight :str + highlight: str

@dataclass class Cache: - hash :str + hash: str

-@dataclass() +@dataclass class Config: - project:Project - tree:Tree - webview:Webview - extras:Extras - cache:Cache + project: Project + tree: Tree + webview: Webview + extras: Extras + cache: Cache

// Created by Pi66

// Copyright ©Pi66 2026