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 logging
import sys
from collections.abc import Callable
from omicron.ssg.output import OutputError
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
log = logging.getLogger(__name__)
class CliError(RuntimeError):
pass
@@ -46,8 +49,15 @@ def setup_argparse() -> argparse.ArgumentParser:
default=False,
help="include draft content",
)
# clean command
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
@@ -84,15 +94,80 @@ def clean(args: argparse.Namespace) -> None:
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:
parser = setup_argparse()
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)
if args.command == "build":
build(args)
elif args.command == "clean":
clean(args)
elif args.command == "init":
init(args)
else:
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