mirror of
https://github.com/sigmasternchen/grimoire
synced 2025-03-15 08:08:55 +00:00
refactor: Extract individual features into toggleable modules
This commit is contained in:
parent
8fe474561a
commit
b2d8198d1d
12 changed files with 221 additions and 132 deletions
|
@ -1,131 +0,0 @@
|
|||
import argparse
|
||||
import glob
|
||||
import os
|
||||
|
||||
import markdown
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from yaml import Loader
|
||||
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("/")
|
||||
)
|
||||
|
||||
|
||||
def to_relative(path):
|
||||
trimmed = path.removeprefix(os.getcwd())
|
||||
if trimmed != path:
|
||||
trimmed = "." + trimmed
|
||||
return trimmed
|
||||
|
||||
|
||||
def compile_markdown(data):
|
||||
for entry in data:
|
||||
if "markdown" in entry:
|
||||
print(f"Compiling markdown for {entry['relative_filename']}...")
|
||||
entry["markdown_compiled"] = markdown.markdown(entry["markdown"])
|
||||
|
||||
|
||||
def render(data, tags, output_dir):
|
||||
files_written = 0
|
||||
|
||||
for entry in data:
|
||||
if "template" in entry:
|
||||
template_path = os.path.realpath(os.path.dirname(entry["filename"]) + "/" + entry["template"])
|
||||
template_dir = os.path.dirname(template_path)
|
||||
print(f"Rendering template for {entry['relative_filename']}...")
|
||||
template = jinja_env.get_template(template_path)
|
||||
entry["rendered"] = template.render(current=entry, all=data, tags=tags, template_dir=template_dir)
|
||||
|
||||
if "rendered" in entry and "output" in entry:
|
||||
files_written += 1
|
||||
filename = os.path.realpath(output_dir + "/" + entry["output"])
|
||||
print(f" ... writing to {to_relative(filename)}")
|
||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||
with open(filename, "w") as file:
|
||||
file.write(entry["rendered"])
|
||||
|
||||
return files_written
|
||||
|
||||
|
||||
def extract_tags(data):
|
||||
tags = {}
|
||||
|
||||
for entry in data:
|
||||
for tag in entry.get("tags", []):
|
||||
entry_list = tags.get(tag, [])
|
||||
entry_list.append(entry)
|
||||
tags[tag] = entry_list
|
||||
|
||||
print(f"Found tags: " + repr(list(tags.keys())))
|
||||
|
||||
return tags
|
||||
|
||||
|
||||
def handle_file_or_glob(globname):
|
||||
results = []
|
||||
|
||||
for filename in glob.glob(os.path.realpath(globname)):
|
||||
results.extend(handle_file(filename))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def handle_file(filename):
|
||||
print(f"Reading {to_relative(filename)}...")
|
||||
|
||||
with open(filename, "r") as file:
|
||||
data = yaml.load(file, Loader)
|
||||
|
||||
data["filename"] = filename
|
||||
data["relative_filename"] = to_relative(filename)
|
||||
results = [data]
|
||||
|
||||
relative_dir = os.path.dirname(filename)
|
||||
for filename in data.get("include", []):
|
||||
filename = relative_dir + "/" + filename
|
||||
sub_data = handle_file_or_glob(filename)
|
||||
results.extend(sub_data)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-o", "--output", default="./output/")
|
||||
|
||||
args, filenames = parser.parse_known_args()
|
||||
|
||||
return args.output, filenames
|
||||
|
||||
|
||||
def main():
|
||||
output_dir, filenames = parse_arguments()
|
||||
|
||||
print(f"Output directory: {output_dir}")
|
||||
print(f"Initial filenames: {filenames}")
|
||||
print()
|
||||
|
||||
if len(filenames) == 0:
|
||||
print("error: at least one filename needed")
|
||||
exit(1)
|
||||
|
||||
data = []
|
||||
for filename in filenames:
|
||||
data.extend(handle_file_or_glob(filename))
|
||||
|
||||
print(f"Total number of entries: {len(data)}")
|
||||
print()
|
||||
|
||||
compile_markdown(data)
|
||||
tags = extract_tags(data)
|
||||
files_written = render(data, tags, output_dir)
|
||||
|
||||
print(f"Total files written: {files_written}")
|
||||
|
||||
print()
|
||||
print("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
25
grimoiressg/__main__.py
Normal file
25
grimoiressg/__main__.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from grimoiressg.arguments import parse_arguments_to_initial_context
|
||||
from grimoiressg.config import read_config
|
||||
from grimoiressg.content_files import recursively_read_files
|
||||
from grimoiressg.modules import available_modules
|
||||
|
||||
|
||||
def apply_modules(data, config, context):
|
||||
for module in config.get("enabled_modules", []):
|
||||
print(f"Applying module {module}...")
|
||||
available_modules[module](data, context)
|
||||
print("")
|
||||
|
||||
|
||||
def main():
|
||||
context = parse_arguments_to_initial_context()
|
||||
config = read_config(context)
|
||||
|
||||
data = recursively_read_files(context)
|
||||
apply_modules(data, config, context)
|
||||
|
||||
print("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
31
grimoiressg/arguments.py
Normal file
31
grimoiressg/arguments.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
import argparse
|
||||
|
||||
|
||||
def parse_arguments_to_initial_context():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='''
|
||||
Grimoire is a minimalistic Static Site Generator.
|
||||
In the simplest case the only argument needed is at least one content file. \
|
||||
The rest of the flags is used to customize the behavior.
|
||||
'''
|
||||
)
|
||||
parser.add_argument("content_file", nargs='+', help="one or more content files")
|
||||
parser.add_argument("-o", "--output", default="./output/", help="the output directory (default: ./output/)")
|
||||
parser.add_argument("-c", "--config", help="the config file to use")
|
||||
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
context = {
|
||||
"output_dir": args.output,
|
||||
"config_file": args.config,
|
||||
"filenames": args.content_file
|
||||
}
|
||||
|
||||
print(f"Output directory: {context['output_dir']}")
|
||||
print(f"Config file: {context['config_file']}")
|
||||
print("Content files:")
|
||||
for filename in context["filenames"]:
|
||||
print(f" - {filename}")
|
||||
print()
|
||||
|
||||
return context
|
35
grimoiressg/config.py
Normal file
35
grimoiressg/config.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import yaml
|
||||
from yaml import Loader
|
||||
|
||||
from grimoiressg.modules import available_modules
|
||||
|
||||
|
||||
def default_config():
|
||||
return {
|
||||
"enabled_modules": [
|
||||
"tags",
|
||||
"markdown",
|
||||
"templating"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def read_config(context):
|
||||
config_file = context.get("config_file", None)
|
||||
|
||||
if not config_file:
|
||||
print("No config file given; using default config")
|
||||
config = default_config()
|
||||
else:
|
||||
with open(config_file, "r") as file:
|
||||
config = yaml.load(file, Loader) or {}
|
||||
|
||||
print("Enabled modules:")
|
||||
for module in config.get("enabled_modules", []):
|
||||
print(f" - {module}")
|
||||
if module not in available_modules:
|
||||
print(f" ERROR: Module does not exist")
|
||||
exit(1)
|
||||
print()
|
||||
|
||||
return config
|
39
grimoiressg/content_files.py
Normal file
39
grimoiressg/content_files.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
import os
|
||||
|
||||
import yaml
|
||||
from yaml import Loader
|
||||
|
||||
from grimoiressg.utils.files import for_each_glob, to_relative
|
||||
|
||||
|
||||
def handle_file(filename):
|
||||
print(f" Reading {to_relative(filename)}...")
|
||||
|
||||
with open(filename, "r") as file:
|
||||
data = yaml.load(file, Loader)
|
||||
|
||||
data["filename"] = filename
|
||||
data["relative_filename"] = to_relative(filename)
|
||||
results = [data]
|
||||
|
||||
relative_dir = os.path.dirname(filename)
|
||||
for filename in data.get("include", []):
|
||||
filename = relative_dir + "/" + filename
|
||||
sub_data = for_each_glob(filename, handle_file)
|
||||
results.extend(sub_data)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def recursively_read_files(context):
|
||||
data = []
|
||||
|
||||
print("Reading content files...")
|
||||
|
||||
for filename in context["filenames"]:
|
||||
data.extend(for_each_glob(filename, handle_file))
|
||||
|
||||
print(f"Read {len(data)} files in total.")
|
||||
print()
|
||||
|
||||
return data
|
9
grimoiressg/modules/__init__.py
Normal file
9
grimoiressg/modules/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from grimoiressg.modules.markdown import compile_markdown
|
||||
from grimoiressg.modules.tags import extract_tags
|
||||
from grimoiressg.modules.templating import render_templates
|
||||
|
||||
available_modules = {
|
||||
"tags": extract_tags,
|
||||
"markdown": compile_markdown,
|
||||
"templating": render_templates
|
||||
}
|
8
grimoiressg/modules/markdown.py
Normal file
8
grimoiressg/modules/markdown.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import markdown
|
||||
|
||||
|
||||
def compile_markdown(data, context):
|
||||
for entry in data:
|
||||
if "markdown" in entry:
|
||||
print(f"Compiling markdown for {entry['relative_filename']}...")
|
||||
entry["markdown_compiled"] = markdown.markdown(entry["markdown"])
|
18
grimoiressg/modules/tags.py
Normal file
18
grimoiressg/modules/tags.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
def extract_tags(data, context):
|
||||
tags = {}
|
||||
|
||||
for entry in data:
|
||||
for tag in entry.get("tags", []):
|
||||
entry_list = tags.get(tag, [])
|
||||
entry_list.append(entry)
|
||||
tags[tag] = entry_list
|
||||
|
||||
if tags:
|
||||
print("Found tags:")
|
||||
for tag in tags.keys():
|
||||
print(f" - {tag} ({len(tags[tag])} files)")
|
||||
else:
|
||||
print("No tags found.")
|
||||
|
||||
context["tags"] = tags
|
36
grimoiressg/modules/templating.py
Normal file
36
grimoiressg/modules/templating.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import os
|
||||
|
||||
from grimoiressg.utils import to_relative
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
jinja_env = Environment(
|
||||
loader=FileSystemLoader("/")
|
||||
)
|
||||
|
||||
|
||||
def render_templates(data, context):
|
||||
files_written = 0
|
||||
|
||||
for entry in data:
|
||||
if "template" in entry:
|
||||
template_path = os.path.realpath(os.path.dirname(entry["filename"]) + "/" + entry["template"])
|
||||
template_dir = os.path.dirname(template_path)
|
||||
print(f"Rendering template for {entry['relative_filename']}...")
|
||||
template = jinja_env.get_template(template_path)
|
||||
entry["rendered"] = template.render(
|
||||
**context,
|
||||
current=entry,
|
||||
all=data,
|
||||
template_dir=template_dir
|
||||
)
|
||||
|
||||
if "rendered" in entry and "output" in entry:
|
||||
files_written += 1
|
||||
filename = os.path.realpath(context["output_dir"] + "/" + entry["output"])
|
||||
print(f" writing to {to_relative(filename)}")
|
||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||
with open(filename, "w") as file:
|
||||
file.write(entry["rendered"])
|
||||
|
||||
print(f"{files_written} rendered")
|
1
grimoiressg/utils/__init__.py
Normal file
1
grimoiressg/utils/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .files import to_relative
|
18
grimoiressg/utils/files.py
Normal file
18
grimoiressg/utils/files.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import glob
|
||||
import os
|
||||
|
||||
|
||||
def to_relative(path):
|
||||
trimmed = path.removeprefix(os.getcwd())
|
||||
if trimmed != path:
|
||||
trimmed = "." + trimmed
|
||||
return trimmed
|
||||
|
||||
|
||||
def for_each_glob(glob_path, callback):
|
||||
results = []
|
||||
|
||||
for filename in glob.glob(os.path.realpath(glob_path)):
|
||||
results.extend(callback(filename))
|
||||
|
||||
return results
|
|
@ -1,7 +1,7 @@
|
|||
[tool.poetry]
|
||||
name = "grimoire-ssg"
|
||||
packages = [
|
||||
{ include = "grimoire-ssg" }
|
||||
{ include = "grimoiressg" }
|
||||
]
|
||||
version = "0.1.0"
|
||||
description = "A minimalistic Static Site Generator"
|
||||
|
|
Loading…
Reference in a new issue