Implement data model for build script
This commit is contained in:
parent
c14ff1066c
commit
15cb22b955
108
build.py
108
build.py
@ -1,7 +1,11 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import datetime
|
import datetime
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
@ -18,21 +22,25 @@ yaml = ruamel.yaml.YAML(typ="safe")
|
|||||||
@dataclass
|
@dataclass
|
||||||
class MediaContainer:
|
class MediaContainer:
|
||||||
title: str
|
title: str
|
||||||
asset: str
|
source: str
|
||||||
|
preview: Optional[str] = None
|
||||||
anchor: Optional[Union[str, int]] = None
|
anchor: Optional[Union[str, int]] = None
|
||||||
source: Optional[str] = None
|
|
||||||
content: Optional[str] = None
|
content: Optional[str] = None
|
||||||
hide_source: bool = False
|
hide_source: bool = False
|
||||||
|
|
||||||
|
|
||||||
class MediaSerializer(msh.Schema):
|
class MediaSerializer(msh.Schema):
|
||||||
title = msh.fields.String()
|
title = msh.fields.String(required=True)
|
||||||
asset = msh.fields.URL()
|
source = msh.fields.String(required=True)
|
||||||
|
preview = msh.fields.String(required=False)
|
||||||
anchor = msh.fields.String(required=False)
|
anchor = msh.fields.String(required=False)
|
||||||
source = msh.fields.URL(required=False)
|
|
||||||
content = msh.fields.String(required=False)
|
content = msh.fields.String(required=False)
|
||||||
hide_source = msh.fields.Boolean(required=False)
|
hide_source = msh.fields.Boolean(required=False)
|
||||||
|
|
||||||
|
@msh.post_load
|
||||||
|
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> MediaContainer:
|
||||||
|
return MediaContainer(**data)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class LinkContainer:
|
class LinkContainer:
|
||||||
@ -42,10 +50,14 @@ class LinkContainer:
|
|||||||
|
|
||||||
|
|
||||||
class LinkSerializer(msh.Schema):
|
class LinkSerializer(msh.Schema):
|
||||||
link = msh.fields.URL()
|
link = msh.fields.URL(required=True)
|
||||||
title = msh.fields.String(required=False)
|
title = msh.fields.String(required=False)
|
||||||
icon = msh.fields.String(required=False)
|
icon = msh.fields.String(required=False)
|
||||||
|
|
||||||
|
@msh.post_load
|
||||||
|
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> LinkContainer:
|
||||||
|
return LinkContainer(**data)
|
||||||
|
|
||||||
|
|
||||||
class Location(NamedTuple):
|
class Location(NamedTuple):
|
||||||
title: str
|
title: str
|
||||||
@ -53,8 +65,12 @@ class Location(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
class LocationSeralizer(msh.Schema):
|
class LocationSeralizer(msh.Schema):
|
||||||
title = msh.fields.String()
|
title = msh.fields.String(required=True)
|
||||||
link = msh.fields.URL()
|
link = msh.fields.URL(required=True)
|
||||||
|
|
||||||
|
@msh.post_load
|
||||||
|
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> Location:
|
||||||
|
return Location(**data)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -70,47 +86,93 @@ class PostContainer:
|
|||||||
|
|
||||||
class PostSerializer(msh.Schema):
|
class PostSerializer(msh.Schema):
|
||||||
|
|
||||||
title = msh.fields.String()
|
title = msh.fields.String(required=True)
|
||||||
location = msh.fields.Nested(LocationSeralizer)
|
location = msh.fields.Nested(LocationSeralizer, required=True)
|
||||||
date = msh.fields.Date()
|
date = msh.fields.Date("%Y-%m-%d", required=True)
|
||||||
banner = msh.fields.URL()
|
banner = msh.fields.URL(required=True)
|
||||||
slug = msh.fields.String(required=False)
|
slug = msh.fields.String(required=False)
|
||||||
links = msh.fields.List(msh.fields.Nested(LinkSerializer), required=False)
|
links = msh.fields.List(msh.fields.Nested(LinkSerializer), required=False)
|
||||||
media = msh.fields.List()
|
media = msh.fields.List(msh.fields.Nested(MediaSerializer), required=True)
|
||||||
|
|
||||||
|
@msh.validates_schema
|
||||||
|
def _unique_anchors(self, data: Dict[str, Any], **kwargs):
|
||||||
|
anchors = [item.anchor for item in data["media"] if item.anchor is not None]
|
||||||
|
if len(anchors) != len(set(anchors)):
|
||||||
|
raise msh.ValidationError(
|
||||||
|
f"Media anchors used multiple times: {set([item for item in anchors if anchors.count(item) > 1])}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@msh.post_load
|
||||||
|
def _make_dataclass(self, data: Dict[str, Any], *args, **kwargs) -> PostContainer:
|
||||||
|
for index, item in enumerate(data["media"]):
|
||||||
|
item.anchor = item.anchor or index
|
||||||
|
data["media"][index] = item
|
||||||
|
return PostContainer(**data)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigSerializer(msh.Schema):
|
||||||
|
static = msh.fields.List(msh.fields.String(), required=False)
|
||||||
|
posts = msh.fields.List(msh.fields.Nested(PostSerializer), required=True)
|
||||||
|
|
||||||
|
@msh.validates_schema
|
||||||
|
def _unique_slugs(self, data: Dict[str, Any], **kwargs):
|
||||||
|
slugs = [item.slug for item in data["posts"] if item.slug is not None]
|
||||||
|
if len(slugs) != len(set(slugs)):
|
||||||
|
raise msh.ValidationError(
|
||||||
|
f"Post slugs used multiple times: {set([item for item in slugs if slugs.count(item) > 1])}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_args() -> argparse.Namespace:
|
def get_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"--config", help="Path to the config file", default=(Path.cwd() / "config.yaml")
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c", "--check", action="store_true", help="Check the config without building"
|
"-c", "--check", action="store_true", help="Check the config without building"
|
||||||
)
|
)
|
||||||
|
parser.add_argument("-p", "--publish", action="store_true", help="Publish the site")
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
cwd = Path.cwd().resolve()
|
args = get_args()
|
||||||
output = cwd / "explore"
|
|
||||||
|
|
||||||
with (cwd / "config.yaml").open() as infile:
|
cwd = Path.cwd().resolve()
|
||||||
config = yaml.load(infile)
|
output = cwd / "build"
|
||||||
|
explore = output / "explore"
|
||||||
|
|
||||||
|
with Path(args.config).resolve().open() as infile:
|
||||||
|
config = ConfigSerializer().load(yaml.load(infile))
|
||||||
|
|
||||||
|
if args.check:
|
||||||
|
return 0
|
||||||
|
|
||||||
env = jinja2.Environment(
|
env = jinja2.Environment(
|
||||||
loader=jinja2.FileSystemLoader(str(cwd / "templates")),
|
loader=jinja2.FileSystemLoader(str(cwd / "templates")),
|
||||||
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not output.exists():
|
output.mkdir(exist_ok=True)
|
||||||
output.mkdir()
|
explore.mkdir(exist_ok=True)
|
||||||
|
|
||||||
index = env.get_template("index.html.j2").render(config=config)
|
index = env.get_template("index.html.j2").render(config=config)
|
||||||
|
with (explore / "index.html").open("w") as outfile:
|
||||||
with (output / "index.html").open("w") as outfile:
|
|
||||||
outfile.write(index)
|
outfile.write(index)
|
||||||
|
|
||||||
|
sitemap = env.get_template("sitemap.xml.j2").render(config=config)
|
||||||
|
with (output / "sitemap.xml").open("w") as outfile:
|
||||||
|
outfile.write(sitemap)
|
||||||
|
|
||||||
|
for static in config["static"]:
|
||||||
|
dest = Path(output / static).resolve()
|
||||||
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.copyfile(static, str(output / static), follow_symlinks=True)
|
||||||
|
|
||||||
post_template = env.get_template("post.html.j2")
|
post_template = env.get_template("post.html.j2")
|
||||||
for post_data in config["posts"]:
|
for post_data in config["posts"]:
|
||||||
post = post_template.render(post=post_data)
|
post = post_template.render(post=post_data)
|
||||||
with (output / f"{post_data['slug']}.html").open("w") as outfile:
|
with (explore / f"{post_data.slug}.html").open("w") as outfile:
|
||||||
outfile.write(post)
|
outfile.write(post)
|
||||||
|
|
||||||
nginx = env.get_template("nginx.conf.d.j2").render(config=config)
|
nginx = env.get_template("nginx.conf.d.j2").render(config=config)
|
||||||
@ -119,4 +181,4 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
sys.exit(main())
|
||||||
|
Loading…
Reference in New Issue
Block a user