Improve cli, add subcommands and flags.

This commit is contained in:
2026-08-26 02:36:07 +02:00
parent 8c6c0d318e
commit 48af9665f0
2 changed files with 102 additions and 8 deletions
+89 -5
View File
@@ -1,10 +1,56 @@
import argparse
import logging import logging
import sys import sys
from omicron.ssg.site import Site from omicron.ssg.output import OutputError
from omicron.ssg.site import ConfigError, Site
from pathlib import Path from pathlib import Path
def setup_logging(verbose: bool = False, log_file: str | None = None) -> None: class CliError(RuntimeError):
pass
def setup_argparse() -> argparse.ArgumentParser:
# base options
help_formatter = argparse.ArgumentDefaultsHelpFormatter
parser = argparse.ArgumentParser(prog="ossg", formatter_class=help_formatter)
parser.add_argument(
"--site", type=Path, default=Path("."), help="path to site directory"
)
parser.add_argument(
"--verbose", "-v", action="count", default=0, help="increase log verbosity"
)
parser.add_argument(
"--log-file",
type=Path,
default=argparse.SUPPRESS,
help="write logs to this file (default: <site>/build.log)",
)
subparsers = parser.add_subparsers(dest="command", required=True)
# build command
build = subparsers.add_parser(
"build", help="build the site", formatter_class=help_formatter
)
build.add_argument(
"--clean",
action=argparse.BooleanOptionalAction,
default=True,
help="remove output directory before building",
)
build.add_argument(
"--drafts",
action=argparse.BooleanOptionalAction,
default=False,
help="include draft content",
)
subparsers.add_parser("clean", help="remove the output directory")
return parser
def setup_logging(verbose: bool = False, log_file: Path | None = None) -> None:
logger = logging.getLogger("omicron.ssg") logger = logging.getLogger("omicron.ssg")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@@ -13,8 +59,46 @@ def setup_logging(verbose: bool = False, log_file: str | None = None) -> None:
console.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
logger.addHandler(console) logger.addHandler(console)
if log_file is not None:
try:
file_handler = logging.FileHandler(log_file)
except FileNotFoundError as e:
raise CliError("log file can't be written") from e
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
logging.Formatter("%(asctime)s %(levelname)s: %(message)s")
)
logger.addHandler(file_handler)
def build(args: argparse.Namespace) -> None:
site = Site(args.site, args.drafts)
if args.clean:
site.clean()
site.build()
def clean(args: argparse.Namespace) -> None:
site = Site(args.site)
site.clean()
def cli() -> None:
parser = setup_argparse()
args = parser.parse_args()
log_file = getattr(args, "log_file", args.site / "build.log")
setup_logging(verbose=bool(args.verbose), log_file=log_file)
if args.command == "build":
build(args)
elif args.command == "clean":
clean(args)
else:
raise NotImplementedError(f"'{args.command}' command not implemented yet")
def main() -> None: def main() -> None:
setup_logging(verbose=True) try:
site = Site(Path(".")) cli()
site.build() except (ConfigError, OutputError, CliError) as e:
print(e)
sys.exit(1)
+11 -1
View File
@@ -1,4 +1,5 @@
import logging import logging
import shutil
from typing import Any, cast from typing import Any, cast
from urllib.parse import urlparse from urllib.parse import urlparse
@@ -31,7 +32,7 @@ class ConfigError(RuntimeError):
class Site: class Site:
def __init__(self, site: Path): def __init__(self, site: Path, drafts: bool = False):
self.site_path = site.resolve() self.site_path = site.resolve()
self.content: list[Content] = [] self.content: list[Content] = []
self.directories: list[Directory] = [] self.directories: list[Directory] = []
@@ -54,8 +55,11 @@ class Site:
def read_config(self) -> dict[str, Any]: def read_config(self) -> dict[str, Any]:
config_path = self.site_path / "config.yml" config_path = self.site_path / "config.yml"
log.debug("Reading config file %s", config_path) log.debug("Reading config file %s", config_path)
try:
with open(config_path, "r") as f: with open(config_path, "r") as f:
config = yaml.safe_load(f) config = yaml.safe_load(f)
except FileNotFoundError:
raise ConfigError("config file missing, not a valid ossg directory")
if not isinstance(config, dict) or not all(isinstance(k, str) for k in config): if not isinstance(config, dict) or not all(isinstance(k, str) for k in config):
raise ConfigError("config file must be a YAML mapping with string keys") raise ConfigError("config file must be a YAML mapping with string keys")
if "name" not in config: if "name" not in config:
@@ -188,6 +192,12 @@ class Site:
self.other_outputs.append(tags_page) self.other_outputs.append(tags_page)
log.info("Discovered %d content items", len(self.content)) log.info("Discovered %d content items", len(self.content))
def clean(self) -> None:
if not self.output_path.exists():
return
shutil.rmtree(self.output_path)
log.info("Removed %s", self.output_path)
def build(self) -> None: def build(self) -> None:
if not self.origin: if not self.origin:
log.warning( log.warning(