from jinja2 import Template from pygments import highlight from pygments.lexers import get_lexer_by_name, guess_lexer from pygments.formatters import HtmlFormatter from pygments.util import ClassNotFound import os import html as html_module from markupsafe import Markup def get_pygments_css(): """Get CSS for syntax highlighting - a more complete and styled version.""" return """ .c { color: #9ca3af; font-style: italic; } /* Comment */ .c1 { color: #9ca3af; font-style: italic; } /* Comment.Single */ .cm { color: #9ca3af; font-style: italic; } /* Comment.Multiline */ .cp { color: #9ca3af; font-style: italic; } /* Comment.Preproc */ .cs { color: #9ca3af; font-style: italic; } /* Comment.Special */ .k { color: #db2777; font-weight: 500; } /* Keyword */ .kc { color: #db2777; font-weight: 500; } /* Keyword.Constant */ .kd { color: #db2777; font-weight: 500; } /* Keyword.Declaration */ .kn { color: #db2777; font-weight: 500; } /* Keyword.Namespace */ .kp { color: #db2777; font-weight: 500; } /* Keyword.Pseudo */ .kr { color: #db2777; font-weight: 500; } /* Keyword.Reserved */ .kt { color: #9333ea; font-weight: 500; } /* Keyword.Type */ .n { color: #1f2937; } /* Name */ .na { color: #4f46e5; } /* Name.Attribute */ .nb { color: #059669; } /* Name.Builtin */ .nc { color: #4f46e5; font-weight: 500; } /* Name.Class */ .nf { color: #4f46e5; font-weight: 500; } /* Name.Function */ .nx { color: #1f2937; } /* Name.Other */ .s { color: #059669; } /* String */ .s1 { color: #059669; } /* String.Single */ .s2 { color: #059669; } /* String.Double */ .p { color: #6b7280; } /* Punctuation */ .m { color: #059669; } /* Number */ .mi { color: #059669; } /* Number.Integer */ .o { color: #db2777; } /* Operator */ .ow { color: #db2777; font-weight: 500; } /* Operator.Word */ .w { color: #d1d5db; } /* Text.Whitespace */ """ from markupsafe import Markup # pip install markupsafe def format_code_with_pygments(code_text, language='python'): """ Highlights a code snippet using Pygments and wraps each line to preserve indentation and allow for individual line styling. """ try: lexer = get_lexer_by_name(language, stripall=True) except ClassNotFound: lexer = get_lexer_by_name('text') # Fallback to plain text formatter = HtmlFormatter(nowrap=True) highlighted_html = highlight(code_text, lexer, formatter) # Split the original raw code to calculate indentation correctly raw_lines = code_text.rstrip().split('\n') # Split the generated HTML, which should correspond to the raw lines highlighted_lines = highlighted_html.rstrip().split('\n') # As a safeguard, if the line counts don't match, fall back to a simpler method. if len(raw_lines) != len(highlighted_lines): wrapped_html = [f'{line}' for line in highlighted_lines] return Markup('\n'.join(wrapped_html)) wrapped_lines = [] for i, raw_line in enumerate(raw_lines): # Calculate leading whitespace from the original raw line leading_spaces = len(raw_line) - len(raw_line.lstrip(' ')) # Create non-breaking spaces for indentation indentation = ' ' * leading_spaces # Use the corresponding line of highlighted HTML for the content line_content = highlighted_lines[i].lstrip() if raw_line.lstrip() else " " wrapped_lines.append(f'{indentation}{line_content}') return Markup('\n'.join(wrapped_lines)) def render_template(template_path, data): """Render Jinja2 template with data.""" with open(template_path, 'r', encoding='utf-8') as f: template = Template(f.read()) return template.render(**data) def extract_code_blocks(content): """Extract code blocks from markdown-style content.""" import re code_blocks = [] pattern = r'```(\w+)?\n(.*?)```' matches = re.findall(pattern, content, re.DOTALL) for lang, code in matches: code_blocks.append({ 'language': lang if lang else 'python', 'code': code.strip() }) return code_blocks def format_bullet_points(content): """ Formats bullet points from content, converting simple markdown (bold, inline code) to HTML. Code snippets are expected to be handled before this function is called. """ import re lines = content.strip().split('\n') bullets = [] for line in lines: stripped_line = line.strip() if stripped_line.startswith(('-', '*', '•')): bullet_text = re.sub(r'^[\-\*•]\s*', '', stripped_line) # Bold markdown: **text** -> text bullet_text = re.sub(r'\*\*(.*?)\*\*', r'\1', bullet_text) # Inline code markdown: `code` -> code bullet_text = re.sub(r'`([^`]+)`', r'\1', bullet_text) bullets.append(bullet_text) elif stripped_line: # Append non-empty, non-bullet lines as well bullets.append(stripped_line) return { 'bullets': bullets, 'code_snippet': None # This function no longer extracts code. }