#!/usr/bin/env python3 import os import sys import time import subprocess import platform from pathlib import Path import marko from marko.ext.gfm import GFM from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler from log.Logger import * from hashes.obfuscation.Obfuscator import Obfuscator from htmlhandler import htmlhandler as Handler # Use absolute paths ROOT = Path(os.path.abspath(".")) MARKDOWN_DIR = ROOT / "markdown" HTML_DIR = ROOT / "html" # Determine executable extension based on OS exe_ext = ".exe" if platform.system() == "Windows" else "" RUST_PARSER_PATH = ROOT / "fastmd" / "target" / "release" / f"fastmd{exe_ext}" # Fallback to debug build if release not found 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) markdown_parser = marko.Markdown(extensions=[GFM]) # Threshold for switching to Rust parser (number of lines) RUST_PARSER_THRESHOLD = 500 Logger = Logger() # Global obfuscate flag, default True obfuscate = True 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) except Exception as e: Logger.log_error(f"Could not count lines in {file_path}: {e}") 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, text=True, encoding='utf-8', check=True ) return result.stdout except subprocess.CalledProcessError as e: Logger.log_error(f"Rust parser failed for {md_path}: {e}") Logger.log_error(f"stderr: {e.stderr}") raise except Exception as e: Logger.log_error(f"Error running Rust parser for {md_path}: {e}") 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 # Decide which parser to use based on file size 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) else: html_body = markdown_parser.convert(text) # Extract title from filename or first H1 title = md_path.stem for line in text.splitlines(): if line.startswith("# "): title = line[2:].strip() break import base64 import random from hashes.hashes import hash_list # Create clean HTML structure # Pick two different hashes from hash_list hash1, hash2 = random.sample(hash_list, 2) clean_html = f""" {title}

Back {title}

write_img
Written @{time.asctime(time.localtime())}

{html_body}
""" # Obfuscate the HTML for browser output obfuscated_html = Obfuscator.obfuscate_html(clean_html) # Ensure html directory exists 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) # if obfuscate: # out_path.write_text(obfuscated_html, encoding="utf-8") # else: # out_path.write_text(clean_html, encoding="utf-8") out_path.write_text(clean_html, encoding="utf-8") Logger.log_debug(f"Rendered: {md_path} -> {out_path}") def remove_html(md_path: Path): relative_path = md_path.relative_to(MARKDOWN_DIR) out_path = HTML_DIR / relative_path.with_suffix(".html") if out_path.exists(): out_path.unlink() Logger.log_debug(f"Removed: {out_path}") def initial_scan(markdown_dir: Path): Logger.log_info(f"Starting initial scan of markdown files in {markdown_dir}...") for md in markdown_dir.rglob("*.md"): render_markdown(md) 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), capture_output=True, 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}") return False except FileNotFoundError: Logger.log_error("cargo command not found. Please install Rust and Cargo.") return False except Exception as e: Logger.log_error(f"Unexpected error building Rust parser: {e}") return False if __name__ == "__main__": # Check for markdown directory if not MARKDOWN_DIR.exists(): alt_root = ROOT / "PyPost" if alt_root.exists() and alt_root.is_dir(): Logger.log_warning(f"Default 'markdown' directory not found, switching ROOT to: {alt_root}") 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}" else: 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 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: Logger.log_error("Build succeeded but parser binary not found") Logger.log_warning("Will use Python parser for all files") else: Logger.log_error("Failed to build Rust parser") 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() observer.schedule(event_handler, str(MARKDOWN_DIR), recursive=True) observer.start() Logger.log_info(f"Started monitoring {MARKDOWN_DIR} for changes.") try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()