diff options
Diffstat (limited to 'wikme.py')
-rw-r--r-- | wikme.py | 106 |
1 files changed, 106 insertions, 0 deletions
diff --git a/wikme.py b/wikme.py new file mode 100644 index 0000000..2df5a03 --- /dev/null +++ b/wikme.py | |||
@@ -0,0 +1,106 @@ | |||
1 | import os | ||
2 | import re | ||
3 | import argparse | ||
4 | import markdown | ||
5 | import datetime | ||
6 | import subprocess | ||
7 | import shutil | ||
8 | from typing import Optional | ||
9 | |||
10 | def kebab_case(s: str) -> str: | ||
11 | return re.sub(r"[ _]", "-", s).lower() | ||
12 | |||
13 | def get_title(filename: str, content: Optional[str] = None) -> str: | ||
14 | if content: | ||
15 | # Check for a top-level header in the content | ||
16 | top_level_header = re.search(r"^#\s(.+)$", content, re.MULTILINE) | ||
17 | if top_level_header: | ||
18 | return top_level_header.group(1).strip() | ||
19 | |||
20 | # Extract the inferred title from the filename | ||
21 | title = filename.replace(".md", "").replace("_", " ") | ||
22 | return title.capitalize() | ||
23 | |||
24 | def parse_wikilinks(content: str) -> str: | ||
25 | # Convert wikilinks with tildes and custom titles | ||
26 | content = re.sub(r'\[\[(~[^|\]]+?)\|([^|\]]+?)\]\]', r'<a href="/\1">\2</a>', content) | ||
27 | |||
28 | # Convert wikilinks with tildes and without custom titles | ||
29 | content = re.sub(r'\[\[(~[^|\]]+?)\]\]', r'<a href="/\1">\1</a>', content) | ||
30 | |||
31 | # Convert regular wikilinks with custom titles | ||
32 | content = re.sub(r'\[\[([^~|\]]+?)\|([^~|\]]+?)\]\]', lambda match: f'<a href="./{kebab_case(match.group(1))}.html">{match.group(2)}</a>', content) | ||
33 | |||
34 | # Convert regular wikilinks without custom titles | ||
35 | content = re.sub(r'\[\[([^~|\]]+?)\]\]', lambda match: f'<a href="./{kebab_case(match.group(1))}.html">{match.group(1)}</a>', content) | ||
36 | |||
37 | return content | ||
38 | |||
39 | def render_template(template: str, title: str, content: str, last_edited: str) -> str: | ||
40 | # Insert title, content, and last_edited into the template | ||
41 | rendered = template.replace("{{ title }}", title) | ||
42 | rendered = rendered.replace("{{ content }}", content) | ||
43 | rendered = rendered.replace("{{ last_edited }}", last_edited) | ||
44 | return rendered | ||
45 | |||
46 | def get_last_edited(path: str) -> str: | ||
47 | try: | ||
48 | # Attempt to get the last Git commit date of the file | ||
49 | last_edited = subprocess.check_output( | ||
50 | ["git", "log", "-1", "--format=%cd", "--date=local", path]) | ||
51 | return last_edited.decode("utf-8").strip() | ||
52 | except Exception: | ||
53 | # Fallback to the file's modified timestamp | ||
54 | return str(datetime.datetime.fromtimestamp(os.path.getmtime(path))) | ||
55 | |||
56 | def main(input_folder: str, output_folder: str, template_file: str): | ||
57 | # Load the template | ||
58 | with open(template_file, "r") as template_f: | ||
59 | template = template_f.read() | ||
60 | |||
61 | # Go through each markdown file | ||
62 | for root, dirs, files in os.walk(input_folder): | ||
63 | for file in files: | ||
64 | if file.endswith(".md"): | ||
65 | # Process the markdown file | ||
66 | input_file = os.path.join(root, file) | ||
67 | output_subfolder = os.path.join( | ||
68 | output_folder, os.path.relpath(root, input_folder)) | ||
69 | output_file = os.path.join( | ||
70 | output_subfolder, f"{kebab_case(file.replace('.md', ''))}.html") | ||
71 | |||
72 | # Read the source file | ||
73 | with open(input_file, "r") as source: | ||
74 | markdown_content = source.read() | ||
75 | html_content = markdown.markdown( | ||
76 | parse_wikilinks(markdown_content), extensions=['codehilite'] | ||
77 | ) | ||
78 | |||
79 | # Create the output folder if needed | ||
80 | if not os.path.exists(output_subfolder): | ||
81 | os.makedirs(output_subfolder) | ||
82 | |||
83 | # Render the result | ||
84 | title = get_title(file, markdown_content) | ||
85 | last_edited = get_last_edited(input_file) | ||
86 | rendered_content = render_template(template, title, html_content, last_edited) | ||
87 | |||
88 | # Save the rendered HTML file | ||
89 | with open(output_file, "w") as output_f: | ||
90 | output_f.write(rendered_content) | ||
91 | |||
92 | def cmd(): | ||
93 | parser = argparse.ArgumentParser( | ||
94 | description="Convert a folder of Markdown files into a simple wiki-style website.") | ||
95 | parser.add_argument('--input', dest='input_folder', | ||
96 | required=True, help='input folder containing Markdown files') | ||
97 | parser.add_argument('--output', dest='output_folder', | ||
98 | required=True, help='output folder for generated HTML files') | ||
99 | parser.add_argument('--template', dest='template_file', | ||
100 | required=True, help='HTML template for the generated files') | ||
101 | args = parser.parse_args() | ||
102 | |||
103 | main(args.input_folder, args.output_folder, args.template_file) | ||
104 | |||
105 | if __name__ == "__main__": | ||
106 | cmd() \ No newline at end of file | ||