Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions coltrane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,8 @@ def _merge_settings(base_dir: Path, django_settings: Dict[str, Any]) -> Dict[str

templates = _get_default_template_settings(base_dir, debug)

templates = _get_default_template_settings(base_dir)

default_settings = {
"BASE_DIR": base_dir,
"ROOT_URLCONF": "coltrane.urls",
Expand Down Expand Up @@ -421,6 +423,12 @@ def _merge_settings(base_dir: Path, django_settings: Dict[str, Any]) -> Dict[str
if _get_current_command() == "compress":
default_settings["COMPRESS_OFFLINE"] = True

if theme := _get_from_env_or_settings(django_settings, "THEME", None):
default_settings["TEMPLATES"][0]["DIRS"].insert(0, base_dir / "themes" / theme)

if theme := _get_from_env_or_settings(django_settings, "THEME", None):
default_settings["TEMPLATES"][0]["DIRS"].insert(0, base_dir / "themes" / theme)

# Make sure BASE_DIR is a `Path` if it got passed in
if "BASE_DIR" in django_settings and isinstance(django_settings["BASE_DIR"], str):
django_settings["BASE_DIR"] = Path(django_settings["BASE_DIR"])
Expand Down
9 changes: 9 additions & 0 deletions coltrane/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"DISABLE_WILDCARD_TEMPLATES": False,
"IS_SECURE": False,
"DATA_JSON5": False,
"THEME": None,
}


Expand Down Expand Up @@ -135,3 +136,11 @@ def get_data_json_5() -> bool:
"DATA_JSON5",
False,
)


def get_theme() -> Optional[str]:
"""
Get the configured theme.
"""

return get_coltrane_settings().get("THEME")
8 changes: 8 additions & 0 deletions coltrane/theme_converters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pathlib import Path
from typing import Literal


def convert(theme_type: Literal["hugo"], path: Path):
# TODO: Handle zip file

pass
198 changes: 198 additions & 0 deletions coltrane/theme_converters/hugo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import re
from collections import deque
from dataclasses import dataclass
from typing import Optional

GO_TEMPLATE_RE = re.compile(r"\{\{-?\s*(([^\s(\}\}\*)]+\s*?)*?)\s*\}\}")


def convert_files():
"""
rename baseof.html to base.html
"""

pass


def replace_match_str(s: str, match: re.Match, change_difference: int, replacement: str) -> str:
start_with_change_difference = match.start() + change_difference
end_with_change_difference = match.end() + change_difference

return s[:start_with_change_difference] + replacement + s[end_with_change_difference:]


def _calculate_change_difference(replacement: str, match: re.Match):
return len(replacement) - (match.end() - match.start())


def _get_action_and_expression(match):
action = match.group(1)
action_pieces = action.split(" ")
expression = None

if action_pieces:
action = action_pieces[0]

for idx, piece in enumerate(action_pieces[1:]):
if piece.startswith("."):
action_pieces[idx + 1] = piece[1:]

# TODO: Handle more than 3 pieces? i.e. more than one argument?
if len(action_pieces) == 3:
if action_pieces[2] == "" or action_pieces[2] == ".":
expression = action_pieces[1]
else:
expression = f"{action_pieces[1]}|{action_pieces[2]}"
elif len(action_pieces) == 2:
expression = action_pieces[1]

return action, expression


@dataclass
class Action:
action_name: str

def get_django_replacement(self, expression: Optional[str]) -> str:
raise NotImplementedError()

def set_action_queue(self, action_queue: deque):
self.action_queue = action_queue

def push_action_on_queue(self):
assert self.action_queue is not None, "action_queue must be set first"
self.action_queue.append(self.action_name)


@dataclass
class Range(Action):
def __init__(self):
super().__init__(action_name="range")

def get_django_replacement(self, expression: Optional[str]) -> str:
if expression is None:
raise AssertionError("Range requires an expression")

self.push_action_on_queue()

return f"{{% for _ in {expression} %}}"


@dataclass
class Block(Action):
def __init__(self):
super().__init__(action_name="block")

def get_django_replacement(self, expression: Optional[str]) -> str:
if expression is None:
raise AssertionError("Expression requires a name")

if (expression.startswith('"') and expression.endswith('"')) or (
expression.startswith("'") and expression.endswith("'")
):
expression = expression[1:-1]

self.push_action_on_queue()

return f"{{% block {expression} %}}"


@dataclass
class If(Action):
def __init__(self):
super().__init__(action_name="if")

def get_django_replacement(self, expression: Optional[str]) -> str:
if expression is None:
raise AssertionError("If conditional requires an expression")

self.push_action_on_queue()

return f"{{% if {expression} %}}"


@dataclass
class End(Action):
def __init__(self):
super().__init__(action_name="end")

def get_django_replacement(self, expression: Optional[str]) -> str:
if not self.action_queue:
raise Exception("end not applicable here")

django_replacement = None
previous_action = self.action_queue.pop()

if previous_action == "range":
django_replacement = "{% endfor %}"
elif previous_action == "block":
django_replacement = "{% endblock %}"
elif previous_action == "if":
django_replacement = "{% endif %}"
else:
raise AssertionError(f"Unknown previous action: '{previous_action}'")

return django_replacement


action_handlers: list[Action] = [Range(), Block(), If(), End()]


def handle_go_constructs(html: str) -> str:
# Store a list of actions in a queue
action_queue = deque()

# The string that replaces pieces might be a different length, so keep track of the difference is length
# This gets used when replacing the pieces whose indexes need to be adjusted based on previous changes
index_adjustment = 0
django_replacement = ""

for match in GO_TEMPLATE_RE.finditer(html):
(action, expression) = _get_action_and_expression(match)
action_handler = next(filter(lambda a: a.action_name == action, action_handlers), None)

if action_handler:
action_handler.set_action_queue(action_queue)
django_replacement = action_handler.get_django_replacement(expression)

html = replace_match_str(html, match, index_adjustment, django_replacement)
else:
variable = action

if variable.startswith("."):
variable = variable[1:]

django_replacement = "{{ " + variable + " }}"
html = replace_match_str(html, match, index_adjustment, django_replacement)

change_difference = _calculate_change_difference(django_replacement, match)
index_adjustment += change_difference

return html


"""
TODO:
- support `-` to remove all blank strings or whatever it does
"""


def convert_template_html(template_html: str):
# # Define regex patterns for Go template constructs
# patterns = {
# # Includes (partials)
# r'{{\s*partial\s*"([a-zA-Z0-9_/.]+)"\s*\.\s*}}': r'{% include "\1" %}',
# # If statements
# r"{{\s*if\s*\.\s*([a-zA-Z0-9_]+)\s*}}": r"{% if \1 %}",
# r"{{\s*else\s*}}": r"{% else %}",
# # r"{{\s*end\s*}}": r"{% endif %}",
# }

# Convert 1-line comments
comment_re = r"\{\{\*\s*(.*?)\s*\*\}\}"
comment_sub = r"{# \1 #}"
template_html = re.sub(comment_re, comment_sub, template_html)

template_html = handle_go_constructs(template_html)

return template_html
1 change: 1 addition & 0 deletions example_standalone/.env
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ COLTRANE_DESCRIPTION=A Coltrane demo site
COLTRANE_EXTRA_FILE_NAMES=robots.txt
COLTRANE_MARKDOWN_RENDERER=mistune
COLTRANE_CONTENT_DIRECTORY=content
COLTRANE_THEME=PaperMod
2 changes: 1 addition & 1 deletion tests/config/paths/test_get_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_get_file_path(settings, tmp_path: Path):
actual = get_file_path("test.txt")
assert actual.exists()

assert expected == actual.read_text()
assert actual == expected.read_text()


def test_get_file_path_not_exists(settings, tmp_path: Path):
Expand Down
18 changes: 9 additions & 9 deletions tests/init/test_get_caches.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,21 @@ def test_get_caches_default():
expected = DEFAULT_CACHES_SETTINGS
actual = _get_caches({})

assert expected == actual
assert actual == expected


def test_get_caches_override_no_default():
expected = DEFAULT_CACHES_SETTINGS
actual = _get_caches({"CACHES": {"test": "no-default"}})

assert expected == actual
assert actual == expected


def test_get_caches_override_default(caches_settings):
expected = {"default": {"test": "ok"}}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected


def test_get_caches_env_dummy(env, caches_settings):
Expand All @@ -38,7 +38,7 @@ def test_get_caches_env_dummy(env, caches_settings):
expected = {"default": {"BACKEND": "django.core.cache.backends.dummy.DummyCache"}}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected


def test_get_caches_env_invalid(env, caches_settings):
Expand All @@ -62,7 +62,7 @@ def test_get_caches(self, env, caches_settings):
}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected

def test_get_caches_location(self, env, caches_settings):
environ["CACHE"] = "memory"
Expand All @@ -76,7 +76,7 @@ def test_get_caches_location(self, env, caches_settings):
}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected


class TestFilesystem:
Expand All @@ -92,7 +92,7 @@ def test_get_caches(self, env, caches_settings):
}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected

def test_get_caches_missing_location(self, env, caches_settings):
environ["CACHE"] = "filesystem"
Expand All @@ -116,7 +116,7 @@ def test_get_caches(self, env, caches_settings):
}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected

def test_get_caches_missing_location(self, env, caches_settings):
environ["CACHE"] = "memcache"
Expand All @@ -140,7 +140,7 @@ def test_get_caches(self, env, caches_settings):
}
actual = _get_caches(caches_settings)

assert expected == actual
assert actual == expected

def test_get_caches_missing_location(self, env, caches_settings):
environ["CACHE"] = "redis"
Expand Down
6 changes: 3 additions & 3 deletions tests/init/test_get_from_env_or_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test_get_from_env_or_settings_default():
settings = {}
actual = _get_from_env_or_settings(settings, "CONTENT_DIRECTORY", "content1")

assert expected == actual
assert actual == expected


def test_get_from_env_or_settings_in_settings():
Expand All @@ -19,7 +19,7 @@ def test_get_from_env_or_settings_in_settings():
settings = {"COLTRANE": {"CONTENT_DIRECTORY": "content2"}}
actual = _get_from_env_or_settings(settings, "CONTENT_DIRECTORY", "content1")

assert expected == actual
assert actual == expected


def test_get_from_env_or_settings_in_environment(env):
Expand All @@ -29,4 +29,4 @@ def test_get_from_env_or_settings_in_environment(env):
settings = {"COLTRANE": {"CONTENT_DIRECTORY": "content2"}}
actual = _get_from_env_or_settings(settings, "CONTENT_DIRECTORY", "content1")

assert expected == actual
assert actual == expected
Loading