md2-presenter 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- md2/__init__.py +37 -0
- md2/__main__.py +5 -0
- md2/_compat.py +7 -0
- md2/cli.py +142 -0
- md2/compat.py +39 -0
- md2/core.py +792 -0
- md2/palettes/cool.toml +28 -0
- md2/palettes/default.toml +24 -0
- md2/palettes/mono.toml +24 -0
- md2/palettes/pastel.toml +24 -0
- md2/palettes/vivid.toml +24 -0
- md2/palettes/warm.toml +24 -0
- md2/palettes.py +106 -0
- md2/templates/default/base.html +28 -0
- md2/templates/default/components/controls.html +24 -0
- md2/templates/default/components/cover.html +5 -0
- md2/templates/default/components/head.html +17 -0
- md2/templates/default/components/scripts.html +93 -0
- md2/templates/default/components/sidebar.html +16 -0
- md2/templates/default/components/slide.html +1 -0
- md2/templates/default/style.css +651 -0
- md2/templates/default/vendor/charts.min.css +1 -0
- md2_presenter-0.2.0.dist-info/METADATA +567 -0
- md2_presenter-0.2.0.dist-info/RECORD +26 -0
- md2_presenter-0.2.0.dist-info/WHEEL +4 -0
- md2_presenter-0.2.0.dist-info/entry_points.txt +2 -0
md2/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""md2 — Markdown to HTML Presentation Converter."""
|
|
2
|
+
|
|
3
|
+
from .core import (
|
|
4
|
+
sanitize_html,
|
|
5
|
+
autolink,
|
|
6
|
+
process_markdown,
|
|
7
|
+
preprocess_chart_directives,
|
|
8
|
+
preprocess_columns,
|
|
9
|
+
transform_charts,
|
|
10
|
+
prepare_context,
|
|
11
|
+
parse_frontmatter,
|
|
12
|
+
extract_og_description,
|
|
13
|
+
get_jinja_env,
|
|
14
|
+
DEFAULT_THEME,
|
|
15
|
+
ALLOWED_TAGS,
|
|
16
|
+
ALLOWED_ATTRIBUTES,
|
|
17
|
+
MD_EXTENSIONS,
|
|
18
|
+
)
|
|
19
|
+
from .cli import main, render_html
|
|
20
|
+
|
|
21
|
+
# Backward compatibility: generate_css and render_presentation
|
|
22
|
+
from .compat import generate_css, render_presentation
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"sanitize_html",
|
|
26
|
+
"autolink",
|
|
27
|
+
"process_markdown",
|
|
28
|
+
"prepare_context",
|
|
29
|
+
"parse_frontmatter",
|
|
30
|
+
"extract_og_description",
|
|
31
|
+
"get_jinja_env",
|
|
32
|
+
"generate_css",
|
|
33
|
+
"render_presentation",
|
|
34
|
+
"render_html",
|
|
35
|
+
"main",
|
|
36
|
+
"DEFAULT_THEME",
|
|
37
|
+
]
|
md2/__main__.py
ADDED
md2/_compat.py
ADDED
md2/cli.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import html
|
|
3
|
+
import shutil
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .core import (
|
|
8
|
+
parse_frontmatter,
|
|
9
|
+
prepare_context,
|
|
10
|
+
extract_og_description,
|
|
11
|
+
get_jinja_env,
|
|
12
|
+
BUNDLED_TEMPLATES_DIR,
|
|
13
|
+
)
|
|
14
|
+
from .palettes import resolve_colors, generate_palette_css
|
|
15
|
+
|
|
16
|
+
USER_TEMPLATES_DIR = Path.home() / ".md2" / "templates"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _ensure_default_template():
|
|
20
|
+
"""Copy bundled default template to ~/.md2/templates/default/ if not present."""
|
|
21
|
+
dest = USER_TEMPLATES_DIR / "default"
|
|
22
|
+
if dest.exists():
|
|
23
|
+
return False
|
|
24
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
shutil.copytree(BUNDLED_TEMPLATES_DIR, dest, dirs_exist_ok=True)
|
|
26
|
+
print(f"Initialized default template in {dest}")
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _init_templates():
|
|
31
|
+
"""(Re)copy bundled default template to ~/.md2/templates/default/."""
|
|
32
|
+
dest = USER_TEMPLATES_DIR / "default"
|
|
33
|
+
if dest.exists():
|
|
34
|
+
shutil.rmtree(dest)
|
|
35
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
shutil.copytree(BUNDLED_TEMPLATES_DIR, dest, dirs_exist_ok=True)
|
|
37
|
+
print(f"Default template (re)initialized in {dest}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _resolve_template_dir(template_name=None):
|
|
41
|
+
"""Resolve template directory. Returns Path or None for bundled default."""
|
|
42
|
+
if template_name:
|
|
43
|
+
template_dir = USER_TEMPLATES_DIR / template_name
|
|
44
|
+
if not template_dir.exists():
|
|
45
|
+
print(f"Error: Template '{template_name}' not found in {template_dir}")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
return template_dir
|
|
48
|
+
|
|
49
|
+
# Check if user has a default template installed
|
|
50
|
+
user_default = USER_TEMPLATES_DIR / "default"
|
|
51
|
+
if user_default.exists():
|
|
52
|
+
return user_default
|
|
53
|
+
|
|
54
|
+
# Auto-install default template, then use it
|
|
55
|
+
_ensure_default_template()
|
|
56
|
+
return user_default
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def render_html(markdown_text, lang=None, dark_mode=None, template_dir=None):
|
|
60
|
+
"""Render markdown to a full HTML string using Jinja2 templates."""
|
|
61
|
+
metadata, body = parse_frontmatter(markdown_text)
|
|
62
|
+
context = prepare_context(body, metadata=metadata)
|
|
63
|
+
|
|
64
|
+
og_description = extract_og_description(body, context["title"])
|
|
65
|
+
safe_title = html.escape(context["title"])
|
|
66
|
+
|
|
67
|
+
# Resolve lang: caller > frontmatter > default "it"
|
|
68
|
+
resolved_lang = lang if lang is not None else metadata.get("lang", "it")
|
|
69
|
+
|
|
70
|
+
# Resolve dark: caller > frontmatter > default False
|
|
71
|
+
resolved_dark = dark_mode if dark_mode is not None else metadata.get("dark", False)
|
|
72
|
+
|
|
73
|
+
# Resolve palette colors and generate CSS
|
|
74
|
+
colors, dark_colors = resolve_colors(metadata)
|
|
75
|
+
palette_css = generate_palette_css(colors, dark_colors)
|
|
76
|
+
|
|
77
|
+
context.update({
|
|
78
|
+
"title": safe_title,
|
|
79
|
+
"og_description": og_description,
|
|
80
|
+
"lang": resolved_lang,
|
|
81
|
+
"dark_mode": resolved_dark,
|
|
82
|
+
"palette_css": palette_css,
|
|
83
|
+
})
|
|
84
|
+
# Escape cover title consistently
|
|
85
|
+
context["cover"]["title"] = safe_title
|
|
86
|
+
|
|
87
|
+
# Multi-path loader for cross-template inheritance:
|
|
88
|
+
# 1. The specific template dir (so its base.html and own includes resolve)
|
|
89
|
+
# 2. The default template dir (so inherited includes like components/head.html resolve)
|
|
90
|
+
# 3. The parent ~/.md2/templates/ (so {% extends "default/base.html" %} works)
|
|
91
|
+
if template_dir and template_dir.parent == USER_TEMPLATES_DIR:
|
|
92
|
+
from jinja2 import FileSystemLoader, Environment
|
|
93
|
+
search_paths = [str(template_dir)]
|
|
94
|
+
default_dir = USER_TEMPLATES_DIR / "default"
|
|
95
|
+
if default_dir.exists() and template_dir != default_dir:
|
|
96
|
+
search_paths.append(str(default_dir))
|
|
97
|
+
search_paths.append(str(USER_TEMPLATES_DIR))
|
|
98
|
+
loader = FileSystemLoader(search_paths)
|
|
99
|
+
env = Environment(loader=loader, autoescape=False)
|
|
100
|
+
else:
|
|
101
|
+
env = get_jinja_env(template_dir)
|
|
102
|
+
template = env.get_template("base.html")
|
|
103
|
+
|
|
104
|
+
return template.render(context)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main():
|
|
108
|
+
"""CLI entry point: converts a Markdown file to an HTML presentation."""
|
|
109
|
+
parser = argparse.ArgumentParser(description="Convert a Markdown file to an HTML presentation.")
|
|
110
|
+
parser.add_argument("filename", nargs="?", help="The input Markdown file")
|
|
111
|
+
parser.add_argument("--lang", default=None, help="HTML lang attribute (default: it)")
|
|
112
|
+
parser.add_argument("--dark", action="store_true", default=None, help="Use dark theme as default")
|
|
113
|
+
parser.add_argument("--template", metavar="NAME", help="Template name from ~/.md2/templates/")
|
|
114
|
+
parser.add_argument("--init-templates", action="store_true", help="(Re)initialize default template in ~/.md2/templates/")
|
|
115
|
+
args = parser.parse_args()
|
|
116
|
+
|
|
117
|
+
if args.init_templates:
|
|
118
|
+
_init_templates()
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
if not args.filename:
|
|
122
|
+
parser.error("the following arguments are required: filename")
|
|
123
|
+
|
|
124
|
+
filepath = Path(args.filename)
|
|
125
|
+
if not filepath.exists():
|
|
126
|
+
print(f"Error: File '{args.filename}' not found.")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
template_dir = _resolve_template_dir(args.template)
|
|
130
|
+
|
|
131
|
+
output_filename = filepath.with_suffix(".html")
|
|
132
|
+
content = filepath.read_text(encoding="utf-8")
|
|
133
|
+
full_html = render_html(
|
|
134
|
+
content, lang=args.lang, dark_mode=args.dark, template_dir=template_dir,
|
|
135
|
+
)
|
|
136
|
+
output_filename.write_text(full_html, encoding="utf-8")
|
|
137
|
+
|
|
138
|
+
print(f"Success! Generated '{output_filename}'")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
main()
|
md2/compat.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Backward-compatible wrappers for the old API (generate_css, render_presentation)."""
|
|
2
|
+
|
|
3
|
+
from .core import prepare_context, get_jinja_env, BUNDLED_TEMPLATES_DIR
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def generate_css(theme_config=None):
|
|
7
|
+
"""Read the bundled CSS file. theme_config is accepted for API compat but ignored
|
|
8
|
+
(theme customization now happens at the template level)."""
|
|
9
|
+
css_path = BUNDLED_TEMPLATES_DIR / "style.css"
|
|
10
|
+
return css_path.read_text(encoding="utf-8")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def render_presentation(markdown_text, theme_config=None):
|
|
14
|
+
"""Backward-compatible wrapper: returns dict with 'title', 'body_html', 'css'."""
|
|
15
|
+
context = prepare_context(markdown_text)
|
|
16
|
+
|
|
17
|
+
env = get_jinja_env()
|
|
18
|
+
|
|
19
|
+
sidebar_template = env.get_template("components/sidebar.html")
|
|
20
|
+
sidebar_html = sidebar_template.render(context)
|
|
21
|
+
|
|
22
|
+
cover_template = env.get_template("components/cover.html")
|
|
23
|
+
cover_html = cover_template.render(context)
|
|
24
|
+
|
|
25
|
+
slide_template = env.get_template("components/slide.html")
|
|
26
|
+
slides_html = ''.join(slide_template.render(slide=s) for s in context["slides"])
|
|
27
|
+
|
|
28
|
+
main_html = f"""
|
|
29
|
+
<div id="main">
|
|
30
|
+
{cover_html}
|
|
31
|
+
{slides_html}
|
|
32
|
+
</div>
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
"title": context["title"],
|
|
37
|
+
"body_html": sidebar_html + main_html,
|
|
38
|
+
"css": generate_css(theme_config)
|
|
39
|
+
}
|