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 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
@@ -0,0 +1,5 @@
1
+ """Allow running md2 as a module: python -m md2"""
2
+ from .cli import main
3
+
4
+ if __name__ == "__main__":
5
+ main()
md2/_compat.py ADDED
@@ -0,0 +1,7 @@
1
+ """Python version compatibility shims."""
2
+ try:
3
+ import tomllib
4
+ except ImportError:
5
+ import tomli as tomllib
6
+
7
+ __all__ = ["tomllib"]
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
+ }