Implement cli init command

closes #1
This commit is contained in:
omicron committed 2026-10-20 18:05:15 +02:00
1 parent 278d3e937e
commit 7bf04ff064
2 files changed
+113 -2

No files matched your search

+77 -2
View File
@@ -1,11 +1,14 @@
import argparse import argparse
import logging import logging
import sys import sys
from collections.abc import Callable
from omicron.ssg.output import OutputError from omicron.ssg.output import OutputError
from omicron.ssg.site import Site from omicron.ssg.site import Site
from omicron.ssg.config import ConfigError from omicron.ssg.config import ConfigError, SiteConfig, DEFAULT_ITEMS_PER_PAGE
from pathlib import Path from pathlib import Path
log = logging.getLogger(__name__)
class CliError(RuntimeError): class CliError(RuntimeError):
pass pass
@@ -46,8 +49,15 @@ def setup_argparse() -> argparse.ArgumentParser:
default=False, default=False,
help="include draft content", help="include draft content",
) )
# clean command
subparsers.add_parser("clean", help="remove the output directory") subparsers.add_parser("clean", help="remove the output directory")
# init command
subparsers.add_parser(
"init", help="create a new site directory", formatter_class=help_formatter
)
return parser return parser
@@ -84,15 +94,80 @@ def clean(args: argparse.Namespace) -> None:
site.clean() site.clean()
def prompt(
label: str,
explanation: str | None = None,
default: str | None = None,
required: bool = True,
validate: Callable[[str], bool] | None = None,
error: str = "Invalid value.",
) -> str:
if explanation:
print(explanation)
hint = f" [{default}]" if default is not None else ""
while True:
value = input(f"{label}{hint}: ").strip()
if not value and default is not None:
value = default
if not value and not required:
return ""
if not value:
print("This field is required.")
continue
if validate and not validate(value):
print(error)
continue
return value
def init(args: argparse.Namespace) -> None:
try:
args.site.mkdir(parents=True, exist_ok=True)
except (FileExistsError, NotADirectoryError) as e:
raise CliError(f"{args.site} is not a directory") from e
if any(args.site.iterdir()):
raise CliError(f"{args.site} is not empty")
config_path = args.site / "config.yml"
name = prompt("Site name")
base_url = prompt(
"Base URL", "Optional, e.g. https://example.com/blog", required=False
)
template = prompt("Template", default="plain")
items_per_page = prompt(
"Items per page",
default=str(DEFAULT_ITEMS_PER_PAGE),
validate=lambda v: v.isdigit() and int(v) > 0,
error="Items per page must be a positive integer.",
)
config = SiteConfig.from_dict(
{
"name": name,
"base_url": base_url,
"template": template,
"items_per_page": int(items_per_page),
}
)
Site.resolve_template_path(config.template)
config.to_file(config_path)
(args.site / "content").mkdir(exist_ok=True)
log.info("Created new site at %s", args.site)
def cli() -> None: def cli() -> None:
parser = setup_argparse() parser = setup_argparse()
args = parser.parse_args() args = parser.parse_args()
log_file = getattr(args, "log_file", args.site / "build.log") default_log_file = None if args.command == "init" else args.site / "build.log"
log_file = getattr(args, "log_file", default_log_file)
setup_logging(verbose=bool(args.verbose), log_file=log_file) setup_logging(verbose=bool(args.verbose), log_file=log_file)
if args.command == "build": if args.command == "build":
build(args) build(args)
elif args.command == "clean": elif args.command == "clean":
clean(args) clean(args)
elif args.command == "init":
init(args)
else: else:
raise NotImplementedError(f"'{args.command}' command not implemented yet") raise NotImplementedError(f"'{args.command}' command not implemented yet")
+36
View File
@@ -0,0 +1,36 @@
import subprocess
import yaml
def test_init_creates_site(tmp_path):
site_dir = tmp_path / "newsite"
result = subprocess.run(
["ossg", "--site", str(site_dir), "init"],
input="My Site\nhttps://example.com\n\n\n",
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
config = yaml.safe_load((site_dir / "config.yml").read_text())
assert config == {
"name": "My Site",
"base_url": "https://example.com/",
"template": "plain",
"items_per_page": 20,
}
assert (site_dir / "content").is_dir()
def test_init_refuses_non_empty_dir(tmp_path):
(tmp_path / "some_file").touch()
result = subprocess.run(
["ossg", "--site", str(tmp_path), "init"],
input="My Site\n\n\n\n",
capture_output=True,
text=True,
)
assert result.returncode == 1
assert "is not empty" in result.stdout