from flask import Flask, request, render_template_string, send_file, jsonify import markdown import imgkit import os import traceback from io import BytesIO import re import base64 from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter from pygments.styles import get_all_styles app = Flask(__name__) TEMP_DIR = os.path.join(os.getcwd(), "temp") os.makedirs(TEMP_DIR, exist_ok=True) # --- FORMAT PARSING AND DETECTION (Unchanged) --- def parse_repo2markdown(text): components = [] pattern = re.compile(r'### File: (.*?)\n([\s\S]*?)(?=\n### File:|\Z)', re.MULTILINE) first_match = pattern.search(text) if first_match: intro_text = text[:first_match.start()].strip() if intro_text: components.append({'type': 'intro', 'filename': 'Introduction', 'content': intro_text, 'is_code_block': False, 'language': ''}) for match in pattern.finditer(text): filename = match.group(1).strip() raw_content = match.group(2).strip() code_match = re.search(r'^```(\w*)\s*\n([\s\S]*?)\s*```$', raw_content, re.DOTALL) if code_match: components.append({'type': 'file', 'filename': filename, 'content': code_match.group(2).strip(), 'is_code_block': True, 'language': code_match.group(1)}) else: components.append({'type': 'file', 'filename': filename, 'content': raw_content, 'is_code_block': False, 'language': ''}) return components def parse_standard_readme(text): components = [] parts = re.split(r'^(## .*?)$', text, flags=re.MULTILINE) intro_content = parts[0].strip() if intro_content: components.append({'type': 'intro', 'filename': 'Introduction', 'content': intro_content}) for i in range(1, len(parts), 2): components.append({'type': 'section', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()}) return components def parse_changelog(text): components = [] parts = re.split(r'^(## \[\d+\.\d+\.\d+.*?\].*?)$', text, flags=re.MULTILINE) intro_content = parts[0].strip() if intro_content: components.append({'type': 'intro', 'filename': 'Changelog Header', 'content': intro_content}) for i in range(1, len(parts), 2): components.append({'type': 'version', 'filename': parts[i].replace('##', '').strip(), 'content': parts[i+1].strip()}) return components @app.route('/parse', methods=['POST']) def parse_endpoint(): text = request.form.get('markdown_text', '') if 'markdown_file' in request.files and request.files['markdown_file'].filename != '': text = request.files['markdown_file'].read().decode('utf-8') if not text: return jsonify({'error': 'No text or file provided.'}), 400 try: if "## File Structure" in text and "### File:" in text: format_name, components = "Repo2Markdown", parse_repo2markdown(text) elif re.search(r'^## \[\d+\.\d+\.\d+.*?\].*?$', text, flags=re.MULTILINE): format_name, components = "Changelog", parse_changelog(text) elif text.strip().startswith("#") and re.search(r'^## ', text, flags=re.MULTILINE): format_name, components = "Standard README", parse_standard_readme(text) else: format_name, components = "Unknown", [{'type': 'text', 'filename': 'Full Text', 'content': text}] return jsonify({'format': format_name, 'components': components}) except Exception as e: return jsonify({'error': f'Failed to parse: {e}'}), 500 # --- HTML & PNG BUILDER (Unchanged but correct logic) --- def build_full_html(markdown_text, styles, include_fontawesome): wrapper_id = "#output-wrapper" font_family = styles.get('font_family', "'Arial', sans-serif") google_font_name = font_family.split(',')[0].strip("'\"") google_font_link = "" if " " in google_font_name and google_font_name not in ["Times New Roman", "Courier New"]: google_font_link = f'' highlight_theme = styles.get('highlight_theme', 'default') pygments_css = "" if highlight_theme != 'none': formatter = HtmlFormatter(style=highlight_theme, cssclass="codehilite") pygments_css = formatter.get_style_defs(f' {wrapper_id}') scoped_css = f""" {wrapper_id} {{ font-family: {font_family}; font-size: {styles.get('font_size', '16')}px; color: {styles.get('text_color', '#333')}; background-color: {styles.get('background_color', '#fff')}; }} /* ... other scoped styles ... */ {wrapper_id} table {{ border-collapse: collapse; width: 100%; }} {wrapper_id} th, {wrapper_id} td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }} {wrapper_id} th {{ background-color: #f2f2f2; }} {wrapper_id} img {{ max-width: 100%; height: auto; }} {wrapper_id} pre {{ padding: {styles.get('code_padding', '15')}px; border-radius: 5px; white-space: pre-wrap; word-wrap: break-word; }} {wrapper_id} h1, {wrapper_id} h2, {wrapper_id} h3 {{ border-bottom: 1px solid #eee; padding-bottom: 5px; margin-top: 1.5em; }} {wrapper_id} :not(pre) > code {{ font-family: 'Courier New', monospace; background-color: #eef; padding: .2em .4em; border-radius: 3px; }} {pygments_css} {styles.get('custom_css', '')} """ md_extensions = ['fenced_code', 'tables', 'codehilite'] html_content = markdown.markdown(markdown_text, extensions=md_extensions, extension_configs={'codehilite': {'css_class': 'codehilite'}}) final_html_body = f'