roundabout,
created on Saturday, 20 April 2024, 10:11:15 (1713607875),
received on Tuesday, 23 April 2024, 11:54:32 (1713873272)
Author identity: vlad <vlad.muntoiu@gmail.com>
fdb6eb1b9f858fc08295d2564c13d8e32a54cb93
ampoule_ssg/__init__.py
@@ -13,14 +13,20 @@ import colorama
colorama.init() # Disable YAML date constructordef no_date_constructor(loader, node):def _no_date_constructor(loader, node): """Function to prevent the YAML loader from converting dates, keeping them as strings, so they can be parsed in a more lenient way. """value = loader.construct_scalar(node) return value @contextlib.contextmanager def in_directory(directory): """Execute a block of code in a different directory. :param directory: The directory to change to. """cwd = os.getcwd() os.chdir(directory) try:
@@ -30,6 +36,11 @@ def in_directory(directory):
def delete_directory_contents(directory): """Delete all files and directories in a directory recursively, but not the directory itself. :param directory: The directory to clear. """for root, dirs, files in os.walk(directory): for file in files: os.remove(os.path.join(root, file))
@@ -38,7 +49,17 @@ def delete_directory_contents(directory):
def parse_date_string(date_string): """Parse a date/time string into a datetime object. Supports multiple unambiguous formats. :param date_string: The date/time string to parse. :return: A datetime object representing the date/time string. """def split_date_and_time(date_string): """Split a date/time string into a date string and a time string. :param date_string: The date/time string to split. :return: A tuple containing the date and time strings. """if ":" not in date_string: return date_string, "00:00:00"
@@ -118,12 +139,18 @@ def parse_date_string(date_string):
class Document: """A type representing a document, which can be text or binary."""def __init__(self, file_name, url_transform=lambda x: x): """Create a new document object. :param file_name: The name of the file to read. :param url_transform: Function to change the file name to a different form. """self.file_name = file_name self.encoding = "utf-8" # If the file is text, read it. self.front_matter = YAML() self.front_matter.Constructor.add_constructor("tag:yaml.org,2002:timestamp", no_date_constructor)self.front_matter.Constructor.add_constructor("tag:yaml.org,2002:timestamp", _no_date_constructor)self.content = "" self.date = datetime.fromtimestamp(os.path.getmtime(file_name)) try:
@@ -178,11 +205,21 @@ class Document:
return self.content def __getitem__(self, item): """Get an item from the front matter of the document"""return self.front_matter[item] class Index: """A type representing an index of documents."""def __init__(self, directory, recursive=False, url_transform=lambda x: x, sort_by=lambda x: x.file_name, exclude=None): """Create a new index object. :param directory: The directory to read the files from. :param recursive: Whether to read files from subdirectories. :param url_transform: Function to change the file name to a different form. :param sort_by: Function returning a key to sort the documents by. :param exclude: Regular expression to exclude files from the index. """self.directory = directory # Temporarily move to the specified directory in order to read the files. if exclude:
@@ -217,19 +254,38 @@ class Index:
class Site: """A type representing a website."""def __init__(self, build_dir, template_dir="templates"): """Create a new site object. :param build_dir: The directory to build the site in. :param template_dir: The directory to read the templates from. """self.build_dir = build_dir self.template_engine = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) self.pages = {} self.context = {} def add_page(self, location, page): """Add a page to the site. :param location: The location the page should be saved to. :param page: The page object itself. """if location.endswith("/"): location += "index.html" location = location.lstrip("/") # interpret it as site root, not OS root self.pages[location] = page def add_from_index(self, index, location, template=None, static=False, **kwargs): """Add pages to the site from an index. :param index: The index to read the documents from. :param location: The location to save the pages to. :param template: The template to use for the pages. :param static: Whether to treat them as static files. :param kwargs: Additional keyword arguments to pass to the template when rendering. """location = location.lstrip("/") # interpret it as site root, not OS root kwargs = {**self.context, **kwargs} if static:
@@ -240,6 +296,10 @@ class Site:
self.pages[os.path.join(location, document.file_name)] = Page(self, template, document, **kwargs) def filter(self, name): """Decorator to add a filter to the template engine. :param name: The name the filter will be used with in Jinja2. """def decorator(func): self.template_engine.filters[name] = func return func
@@ -247,6 +307,10 @@ class Site:
return decorator def test(self, name): """Decorator to add a test to the template engine. :param name: The name the test will be used with in Jinja2. """def decorator(func): self.template_engine.tests[name] = func return func
@@ -254,6 +318,7 @@ class Site:
return decorator def build(self): """Build the site in its directory."""# Clear the build directory if it exists. if os.path.isdir(self.build_dir): delete_directory_contents(self.build_dir)
@@ -271,11 +336,13 @@ class Site:
class Page(str): """A type representing a page, which is a rendered template."""def __new__(cls, site, template, document=None, **kwargs): kwargs = {**site.context, **kwargs} return site.template_engine.get_template(template).render(document=document, **kwargs) class Static(bytes): """A type representing a static file, which is binary content that is not templated."""def __new__(cls, site, document): return document.content