roundabout,
created on Tuesday, 25 June 2024, 14:00:38 (1719324038),
received on Tuesday, 25 June 2024, 14:01:57 (1719324117)
Author identity: vlad <vlad.muntoiu@gmail.com>
8b6814c14ccf4b43461ba4abcdeca453642ca5ce
.idea/workspace.xml
@@ -4,15 +4,8 @@
<option name="autoReloadType" value="SELECTIVE" /> </component> <component name="ChangeListManager"> <list default="true" id="bfb6b072-e524-4a79-ba52-6272edbc262e" name="Changes" comment="Initial commit: basic functionality"><change afterPath="$PROJECT_DIR$/static/fonts/RoLeagueGothic-Condensed.woff2" afterDir="false" /><change afterPath="$PROJECT_DIR$/static/fonts/RoLeagueGothic-CondensedItalic.woff2" afterDir="false" /><change afterPath="$PROJECT_DIR$/static/fonts/RoLeagueGothic-Italic.woff2" afterDir="false" /><change afterPath="$PROJECT_DIR$/static/fonts/RoLeagueGothic-Regular.woff2" afterDir="false" /><change beforePath="$PROJECT_DIR$/site.py" beforeDir="false" afterPath="$PROJECT_DIR$/site.py" afterDir="false" /><change beforePath="$PROJECT_DIR$/static/style.css" beforeDir="false" afterPath="$PROJECT_DIR$/static/style.css" afterDir="false" /><change beforePath="$PROJECT_DIR$/templates/default.html" beforeDir="false" afterPath="$PROJECT_DIR$/templates/default.html" afterDir="false" /><change beforePath="$PROJECT_DIR$/vial.py" beforeDir="false" afterPath="$PROJECT_DIR$/vial.py" afterDir="false" /><list default="true" id="bfb6b072-e524-4a79-ba52-6272edbc262e" name="Changes" comment="Snake case"> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /></list> <option name="SHOW_DIALOG" value="false" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -31,7 +24,8 @@
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> </component> <component name="ProjectColorInfo"><![CDATA[{ "associatedIndex": 6"customColor": "8bc34aff", "associatedIndex": 4}]]></component> <component name="ProjectId" id="2fH5y7tciPdpZiAKDRg5OvklhUh" /> <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
@@ -44,7 +38,14 @@
"DefaultHtmlFileTemplate": "HTML File", "RunOnceActivity.ShowReadmeOnStart": "true", "git-widget-placeholder": "master", "last_opened_file_path": "/home/vlad/vial/static/fonts""last_opened_file_path": "/home/vlad/ampoule", "node.js.detected.package.eslint": "true", "node.js.detected.package.tslint": "true", "node.js.selected.package.eslint": "(autodetect)", "node.js.selected.package.tslint": "(autodetect)", "nodejs_package_manager_path": "npm", "settings.editor.selected.configurable": "preferences.lookFeel", "vue.rearranger.settings.migration": "true"} }]]></component> <component name="RecentsManager">
@@ -67,6 +68,7 @@
<option name="IS_MODULE_SDK" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" /> <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /><option name="SCRIPT_NAME" value="$PROJECT_DIR$/vial.py" /> <option name="PARAMETERS" value="" /> <option name="SHOW_COMMAND_LINE" value="false" />
@@ -80,7 +82,8 @@
<component name="SharedIndexes"> <attachedChunks> <set> <option value="bundled-python-sdk-0509580d9d50-746f403e7f0c-com.jetbrains.pycharm.community.sharedIndexes.bundled-PC-241.14494.241" /><option value="bundled-js-predefined-1d06a55b98c1-0b3e54e931b4-JavaScript-PY-241.17890.14" /> <option value="bundled-python-sdk-5b207ade9991-7e9c3bbb6e34-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-241.17890.14" /></set> </attachedChunks> </component>
@@ -92,6 +95,7 @@
<option name="number" value="Default" /> <option name="presentableId" value="Default" /> <updated>1713445413599</updated> <workItem from="1719316171662" duration="2788000" /></task> <task id="LOCAL-00001" summary="Initial commit: basic functionality"> <option name="closed" value="true" />
@@ -101,11 +105,23 @@
<option name="project" value="LOCAL" /> <updated>1713532390233</updated> </task> <option name="localTasksCounter" value="2" /><task id="LOCAL-00002" summary="Snake case"> <option name="closed" value="true" /> <created>1716298507811</created> <option name="number" value="00002" /> <option name="presentableId" value="LOCAL-00002" /> <option name="project" value="LOCAL" /> <updated>1716298507811</updated> </task> <option name="localTasksCounter" value="3" /><servers /> </component> <component name="TypeScriptGeneratedFilesManager"> <option name="version" value="3" /> </component><component name="VcsManagerConfiguration"> <MESSAGE value="Initial commit: basic functionality" /> <option name="LAST_COMMIT_MESSAGE" value="Initial commit: basic functionality" /><MESSAGE value="Snake case" /> <option name="LAST_COMMIT_MESSAGE" value="Snake case" /></component> </project>
ampoule_ssg/__init__.py
@@ -5,143 +5,16 @@ import re
import shutil import contextlib import typing from datetime import datetimeimport jinja2 from ruamel.yaml import YAMLimport colorama from datetime import datetime from ruamel.yaml import YAML from ampoule_ssg._utils import *colorama.init() 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.contextmanagerdef 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:yieldfinally:os.chdir(cwd)def delete_directory_contents(directory, dont_delete: typing.Optional[list[str]] = None):"""Delete all files and directories in a directory recursively,but not the directory itself.:param directory: The directory to clear.:param dont_delete: A list of files and directories to not delete."""for root, dirs, files in os.walk(directory):for file in files:if file not in dont_delete:os.remove(os.path.join(root, file))for dir in dirs:if dir not in dont_delete:shutil.rmtree(os.path.join(root, dir))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"elements = date_string.partition(":")partition_character = " "if " " not in date_string:partition_character = "-"if "-" not in date_string:partition_character = "T"date = elements[0].rpartition(partition_character)[0].strip()time = elements[0].rpartition(partition_character)[2].strip() + elements[1] + elements[2].strip()time = time.removeprefix("T").removesuffix("Z")return date, timetime_formats = [# 24-hour ISO"%H:%M:%S","%H:%M","%H",# Single digit hour"-%H:%M:%S","-%H:%M","-%H",# 12-hour (AM/PM)"%I:%M:%S %p","%I:%M %p","%I %p",# Single digit 12-hour"-%I:%M:%S %p","-%I:%M %p","-%I %p",]date_formats = [# ISO formats"%Y-%m-%d","%y-%m-%d",# European formats"%d.%m.%Y","%d.%m.%y",# American formats"%m/%d/%Y","%m/%d/%y",# Text-based European formats"%d %B %Y","%d %b %Y","%d %B, %Y","%d %b, %Y",# Text-based American formats"%B %d %Y","%b %d %Y","%B %d, %Y","%b %d, %Y",# ISO weekly calendar"%G-W%V-%u",]date, time = split_date_and_time(date_string)time_object = datetime.min.time()date_object = datetime.min.date()for time_format in time_formats:try:time_object = datetime.strptime(time, time_format)except ValueError:passfor date_format in date_formats:try:date_object = datetime.strptime(date, date_format)except ValueError:passreturn datetime.combine(date_object, time_object.time())class Document: """A type representing a document, which can be text or binary.""" def __init__(self, file_name: typing.Union[str, bytes, os.PathLike], url_transform: typing.Callable = lambda x: x, front_matter_enabled: bool = True):
@@ -246,7 +119,7 @@ class Index:
regex = re.compile(exclude) else: regex = re.compile("(?!)") with in_directory(directory):with _in_directory(directory):if recursive: self.file_names = [os.path.join(dir_path, f) for dir_path, dir_name, filenames in os.walk(".") for f in filenames if not regex.search(f)] else:
@@ -341,7 +214,7 @@ class Site:
"""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, dont_delete=dont_delete)_delete_directory_contents(self.build_dir, dont_delete=dont_delete)for location, page in self.pages.items(): # Create the required directories. os.makedirs(os.path.join(self.build_dir, os.path.dirname(location)), exist_ok=True)
ampoule_ssg/_utils.py
@@ -0,0 +1,140 @@
import os import shutil import contextlib import typing from datetime import datetime __all__ = [ "_no_date_constructor", "_in_directory", "_delete_directory_contents", "_parse_date_string", ] 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: yield finally: os.chdir(cwd) def _delete_directory_contents(directory, dont_delete: typing.Optional[list[str]] = None): """Delete all files and directories in a directory recursively, but not the directory itself. :param directory: The directory to clear. :param dont_delete: A list of files and directories to not delete. """ for root, dirs, files in os.walk(directory): for file in files: if file not in dont_delete: os.remove(os.path.join(root, file)) for dir in dirs: if dir not in dont_delete: shutil.rmtree(os.path.join(root, dir)) 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" elements = date_string.partition(":") partition_character = " " if " " not in date_string: partition_character = "-" if "-" not in date_string: partition_character = "T" date = elements[0].rpartition(partition_character)[0].strip() time = elements[0].rpartition(partition_character)[2].strip() + elements[1] + elements[2].strip() time = time.removeprefix("T").removesuffix("Z") return date, time time_formats = [ # 24-hour ISO "%H:%M:%S", "%H:%M", "%H", # Single digit hour "-%H:%M:%S", "-%H:%M", "-%H", # 12-hour (AM/PM) "%I:%M:%S %p", "%I:%M %p", "%I %p", # Single digit 12-hour "-%I:%M:%S %p", "-%I:%M %p", "-%I %p", ] date_formats = [ # ISO formats "%Y-%m-%d", "%y-%m-%d", # European formats "%d.%m.%Y", "%d.%m.%y", # American formats "%m/%d/%Y", "%m/%d/%y", # Text-based European formats "%d %B %Y", "%d %b %Y", "%d %B, %Y", "%d %b, %Y", # Text-based American formats "%B %d %Y", "%b %d %Y", "%B %d, %Y", "%b %d, %Y", # ISO weekly calendar "%G-W%V-%u", ] date, time = split_date_and_time(date_string) time_object = datetime.min.time() date_object = datetime.min.date() for time_format in time_formats: try: time_object = datetime.strptime(time, time_format) except ValueError: pass for date_format in date_formats: try: date_object = datetime.strptime(date, date_format) except ValueError: pass return datetime.combine(date_object, time_object.time())
ampoule_ssg/markdown.py
@@ -261,7 +261,7 @@ def parse_line(source):
hard_break = False tokens = [] pattern = re.compile(inlineRegex, re.MULTILINE | re.DOTALL | re.VERBOSE)pattern = re.compile(inline_regex, re.MULTILINE | re.DOTALL | re.VERBOSE)matches = pattern.finditer(source) lookup = 0