nice visual improvments

This commit is contained in:
2025-10-05 19:52:43 +02:00
parent 6580d5c4bc
commit a4d8283d2a
8 changed files with 398 additions and 231 deletions

101
PyPost.py
View File

@@ -8,6 +8,7 @@ from jinja2 import Environment, FileSystemLoader
import base64
import random
import time
import yaml
import marko
from marko.ext.gfm import GFM
@@ -19,7 +20,7 @@ from htmlhandler import htmlhandler as Handler
from lua import plugin_manager
plugin_manager = plugin_manager.PluginManager()
plugin_manager.load_all() # load plugins
plugin_manager.load_all() # load plugins
# Use absolute paths
ROOT = Path(os.path.abspath("."))
@@ -34,7 +35,7 @@ RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}"
if not RUST_PARSER_PATH.exists():
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "debug" / f"fastmd{exe_ext}"
# Create Python Markdown parser with table support (fallback for small files)
# Python Markdown parser with table support
markdown_parser = marko.Markdown(extensions=[GFM])
# Threshold for switching to Rust parser (number of lines)
@@ -45,8 +46,22 @@ Logger = Logger()
# Global obfuscate flag, default True
obfuscate = True
def split_yaml_front_matter(md_path: Path) -> tuple[str, str]:
"""Return (yaml_text, markdown_text). YAML is '' if none exists."""
try:
text = md_path.read_text(encoding="utf-8")
except Exception as e:
Logger.log_error(f"Could not read {md_path}: {e}")
return '', ''
if text.startswith("---"):
parts = text.split("---", 2)
if len(parts) >= 3:
yaml_text = parts[1].strip()
markdown_text = parts[2].lstrip("\n")
return yaml_text, markdown_text
return '', text
def count_lines_in_file(file_path: Path) -> int:
"""Count the number of lines in a file."""
try:
with open(file_path, 'r', encoding='utf-8') as f:
return sum(1 for _ in f)
@@ -55,24 +70,18 @@ def count_lines_in_file(file_path: Path) -> int:
return 0
def should_use_rust_parser(md_path: Path) -> bool:
"""Determine if we should use the Rust parser based on file size."""
if not RUST_PARSER_PATH.exists():
return False
line_count = count_lines_in_file(md_path)
use_rust = line_count > RUST_PARSER_THRESHOLD
if use_rust:
Logger.log_rust_usage(f"Using Rust parser for {md_path} ({line_count} lines)")
else:
Logger.log_debug(f"Using Python parser for {md_path} ({line_count} lines)")
return use_rust
def parse_markdown_with_rust(md_path: Path) -> str:
"""Parse markdown using the Rust parser."""
try:
# Run the Rust parser
result = subprocess.run(
[str(RUST_PARSER_PATH), str(md_path)],
capture_output=True,
@@ -90,31 +99,27 @@ def parse_markdown_with_rust(md_path: Path) -> str:
raise
def render_markdown(md_path: Path):
"""Render a single markdown file to an obfuscated HTML file."""
try:
text = md_path.read_text(encoding="utf-8")
except Exception as e:
Logger.log_error(f"Could not read {md_path}: {e}")
return
"""Render a single markdown file to HTML, stripping YAML front matter."""
yaml_text, markdown_text = split_yaml_front_matter(md_path)
# Decide which parser to use based on file size
# Choose parser
if should_use_rust_parser(md_path):
try:
html_body = parse_markdown_with_rust(md_path)
except Exception as e:
Logger.log_warning(f"Rust parser failed for {md_path}, falling back to Python parser: {e}")
html_body = markdown_parser.convert(text)
html_body = markdown_parser.convert(markdown_text)
else:
html_body = markdown_parser.convert(text)
html_body = markdown_parser.convert(markdown_text)
# Extract title from filename or first H1
title = md_path.stem
for line in text.splitlines():
for line in markdown_text.splitlines():
if line.startswith("# "):
title = line[2:].strip()
break
# Call pre_template hook properly
# Plugin pre_template hook
Logger.log_debug(f"Calling pre_template hook for {md_path}")
modified = plugin_manager.run_hook("pre_template", str(md_path), html_body)
if modified is not None:
@@ -123,13 +128,11 @@ def render_markdown(md_path: Path):
else:
Logger.log_debug("pre_template hook returned None")
# Create clean HTML structure
# Pick two different hashes from hash_list
# Jinja template
env = Environment(loader=FileSystemLoader("html/base"))
template = env.get_template("template.html")
hash1, hash2 = random.sample(hash_list, 2)
# Load these variable for Jinja to use.
clean_jinja_html = template.render(
title=title,
html_body=html_body,
@@ -139,23 +142,36 @@ def render_markdown(md_path: Path):
timestamp=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
)
# Plugin post_render hook
post_mod = plugin_manager.run_hook("post_render", str(md_path), clean_jinja_html)
if post_mod is not None:
clean_jinja_html = post_mod
# Ensure html directory exists
# Write output
HTML_DIR.mkdir(exist_ok=True)
# Maintain relative directory structure in html/
relative_path = md_path.relative_to(MARKDOWN_DIR)
out_path = HTML_DIR / relative_path.with_suffix(".html")
# Create parent directories if needed
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(clean_jinja_html, encoding="utf-8")
Logger.log_debug(f"Rendered: {md_path} -> {out_path}")
def extract_summary(md_path: Path) -> tuple[str, str] | None:
yaml_text, _ = split_yaml_front_matter(md_path)
if not yaml_text:
Logger.log_debug(f"No YAML front matter in {md_path}")
return None
try:
data = yaml.safe_load(yaml_text)
summary = data.get("summary")
if summary:
html_name = md_path.with_suffix(".html").name
return html_name, summary
else:
Logger.log_debug(f"No 'summary' key in YAML of {md_path}")
except Exception as e:
Logger.log_warning(f"Failed to parse YAML summary in {md_path}: {e}")
return None
def remove_html(md_path: Path):
relative_path = md_path.relative_to(MARKDOWN_DIR)
out_path = HTML_DIR / relative_path.with_suffix(".html")
@@ -170,20 +186,15 @@ def initial_scan(markdown_dir: Path):
def build_rust_parser() -> bool:
fastmd_dir = ROOT / "fastmd"
if not fastmd_dir.exists():
Logger.log_error(f"fastmd directory not found at {fastmd_dir}")
return False
cargo_toml = fastmd_dir / "Cargo.toml"
if not cargo_toml.exists():
Logger.log_error(f"Cargo.toml not found at {cargo_toml}")
return False
Logger.log_info("Attempting to build Rust parser with 'cargo build --release'...")
try:
# Run cargo build --release in the fastmd directory
result = subprocess.run(
["cargo", "build", "--release"],
cwd=str(fastmd_dir),
@@ -191,11 +202,9 @@ def build_rust_parser() -> bool:
text=True,
check=True
)
Logger.log_info("Rust parser built successfully!")
Logger.log_debug(f"Build output: {result.stdout}")
return True
except subprocess.CalledProcessError as e:
Logger.log_error(f"Failed to build Rust parser: {e}")
Logger.log_error(f"Build stderr: {e.stderr}")
@@ -208,7 +217,7 @@ def build_rust_parser() -> bool:
return False
if __name__ == "__main__":
# Check for markdown directory
# Handle alternative markdown folder
if not MARKDOWN_DIR.exists():
alt_root = ROOT / "PyPost"
if alt_root.exists() and alt_root.is_dir():
@@ -216,7 +225,6 @@ if __name__ == "__main__":
ROOT = alt_root
MARKDOWN_DIR = ROOT / "markdown"
HTML_DIR = ROOT / "html"
# Update Rust parser path for new root
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}"
if not RUST_PARSER_PATH.exists():
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "debug" / f"fastmd{exe_ext}"
@@ -224,18 +232,14 @@ if __name__ == "__main__":
Logger.log_error(f"Markdown directory not found: {MARKDOWN_DIR}")
Logger.log_warning("Please create a 'markdown' directory or use a 'PyPost' directory with one inside it.")
sys.exit(1)
# Check if Rust parser exists, if not try to build it
# Build Rust parser if missing
if not RUST_PARSER_PATH.exists():
Logger.log_warning(f"Rust parser not found at {RUST_PARSER_PATH}")
# Try to build the Rust parser
if build_rust_parser():
# Update path after successful build
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}"
if not RUST_PARSER_PATH.exists():
RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "debug" / f"fastmd{exe_ext}"
if RUST_PARSER_PATH.exists():
Logger.log_info(f"Rust parser built and found at: {RUST_PARSER_PATH}")
else:
@@ -246,13 +250,12 @@ if __name__ == "__main__":
Logger.log_warning("Will use Python parser for all files")
else:
Logger.log_info(f"Rust parser found at: {RUST_PARSER_PATH}")
# Log parser strategy
if RUST_PARSER_PATH.exists():
Logger.log_info(f"Will use Rust parser for files with more than {RUST_PARSER_THRESHOLD} lines")
else:
Logger.log_warning("Using Python parser for all files")
initial_scan(MARKDOWN_DIR)
event_handler = Handler()
observer = Observer()
@@ -264,4 +267,4 @@ if __name__ == "__main__":
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
observer.join()