Compare commits
2
Commits
b97487eb4b
...
7bf04ff064
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bf04ff064 | ||
|
|
278d3e937e |
No files matched your search
+77
-2
@@ -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")
|
||||
|
||||
|
||||
@@ -99,3 +99,16 @@ class SiteConfig:
|
||||
items_per_page=items_per_page,
|
||||
template=template,
|
||||
)
|
||||
|
||||
def to_dict(self) -> dict[str, str | int]:
|
||||
return {
|
||||
"name": self.name,
|
||||
"template": self.template,
|
||||
"items_per_page": self.items_per_page,
|
||||
"base_url": f"{self.origin}{self.base_dir}",
|
||||
}
|
||||
|
||||
def to_file(self, path: Path) -> None:
|
||||
log.debug("Writing config file %s", path)
|
||||
with open(path, "w") as f:
|
||||
yaml.safe_dump(self.to_dict(), f, default_flow_style=False)
|
||||
@@ -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
|
||||
@@ -58,6 +58,48 @@ def test_from_dict_full():
|
||||
assert config.base_dir == Path("/blog")
|
||||
|
||||
|
||||
def test_to_dict():
|
||||
config = SiteConfig(
|
||||
name="My Site",
|
||||
origin="http://example.com",
|
||||
base_dir=Path("/blog"),
|
||||
items_per_page=42,
|
||||
template="plain",
|
||||
)
|
||||
|
||||
assert config.to_dict() == {
|
||||
"name": "My Site",
|
||||
"template": "plain",
|
||||
"items_per_page": 42,
|
||||
"base_url": "http://example.com/blog",
|
||||
}
|
||||
|
||||
|
||||
def test_to_dict_default_base_url():
|
||||
config = SiteConfig(
|
||||
name="My Site",
|
||||
origin="",
|
||||
base_dir=Path("/"),
|
||||
items_per_page=DEFAULT_ITEMS_PER_PAGE,
|
||||
template="plain",
|
||||
)
|
||||
|
||||
assert config.to_dict()["base_url"] == "/"
|
||||
|
||||
|
||||
def test_to_dict_round_trip():
|
||||
original = SiteConfig.from_dict(
|
||||
{
|
||||
"name": "My Site",
|
||||
"template": "plain",
|
||||
"base_url": "http://example.com/blog",
|
||||
"items_per_page": 42,
|
||||
}
|
||||
)
|
||||
|
||||
assert SiteConfig.from_dict(original.to_dict()) == original
|
||||
|
||||
|
||||
MISSING = object()
|
||||
|
||||
|
||||
@@ -128,6 +170,21 @@ def test_from_file_full(tmp_path):
|
||||
assert config.base_dir == Path("/blog")
|
||||
|
||||
|
||||
def test_to_file(tmp_path):
|
||||
path = tmp_path / "config.yml"
|
||||
config = SiteConfig(
|
||||
name="My Site",
|
||||
origin="http://example.com",
|
||||
base_dir=Path("/blog"),
|
||||
items_per_page=42,
|
||||
template="plain",
|
||||
)
|
||||
|
||||
config.to_file(path)
|
||||
|
||||
assert SiteConfig.from_file(path) == config
|
||||
|
||||
|
||||
def test_from_file_missing(tmp_path):
|
||||
path = tmp_path / "config.yml"
|
||||
|
||||
|
||||
Reference in new issue
Block a user