Improve cli, add subcommands and flags.
This commit is contained in:
+89
-5
@@ -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
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user