by @ulds · python
import re
from dataclasses import dataclass
from typing import Dict, List, Tuple
from enum import Enum
class Theme(Enum):
"""Available color themes"""
DRACULA = "dracula"
MONOKAI = "monokai"
NORD = "nord"
ONE_DARK = "one_dark"
GITHUB_DARK = "github_dark"
@dataclass
class ColorScheme:
"""Color scheme for syntax highlighting"""
background: str
foreground: str
comment: str
keyword: str
string: str
number: str
function: str
class_name: str
operator: str
variable: str
class SyntaxHighlighter:
"""
Syntax highlighter with support for multiple languages and themes
"""
# Theme definitions
THEMES = {
Theme.DRACULA: ColorScheme(
background="#282a36",
foreground="#f8f8f2",
comment="#6272a4",
keyword="#ff79c6",
string="#f1fa8c",
number="#bd93f9",
function="#50fa7b",
class_name="#8be9fd",
operator="#ff79c6",
variable="#f8f8f2"
),
Theme.MONOKAI: ColorScheme(
background="#272822",
foreground="#f8f8f2",
comment="#75715e",
keyword="#f92672",
string="#e6db74",
number="#ae81ff",
function="#a6e22e",
class_name="#66d9ef",
operator="#f92672",
variable="#f8f8f2"
),
Theme.NORD: ColorScheme(
background="#2e3440",
foreground="#d8dee9",
comment="#616e88",
keyword="#81a1c1",
string="#a3be8c",
number="#b48ead",
function="#88c0d0",
class_name="#8fbcbb",
operator="#81a1c1",
variable="#d8dee9"
),
Theme.ONE_DARK: ColorScheme(
background="#282c34",
foreground="#abb2bf",
comment="#5c6370",
keyword="#c678dd",
string="#98c379",
number="#d19a66",
function="#61afef",
class_name="#e5c07b",
operator="#c678dd",
variable="#e06c75"
),
Theme.GITHUB_DARK: ColorScheme(
background="#0d1117",
foreground="#c9d1d9",
comment="#8b949e",
keyword="#ff7b72",
string="#a5d6ff",
number="#79c0ff",
function="#d2a8ff",
class_name="#ffa657",
operator="#ff7b72",
variable="#ffa657"
)
}
# Language patterns
PATTERNS = {
'python': {
'comment': r'#.*$',
'string': r'(["\'])(?:(?=(\\?))\2.)*?\1',
'keyword': r'\b(def|class|import|from|if|elif|else|for|while|return|try|except|with|as|lambda|yield|async|await|pass|break|continue|raise|assert|global|nonlocal|del|in|is|not|and|or)\b',
'function': r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()',
'class': r'\bclass\s+([A-Z][a-zA-Z0-9_]*)',
'number': r'\b\d+\.?\d*\b',
'operator': r'[+\-*/%=<>!&|^~]+'
},
'javascript': {
'comment': r'//.*$|/\*[\s\S]*?\*/',
'string': r'(["\'])(?:(?=(\\?))\2.)*?\1|`[^`]*`',
'keyword': r'\b(var|let|const|function|class|if|else|for|while|return|try|catch|finally|async|await|import|export|from|default|new|this|typeof|instanceof|void|delete)\b',
'function': r'\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*(?=\()',
'class': r'\bclass\s+([A-Z][a-zA-Z0-9_]*)',
'number': r'\b\d+\.?\d*\b',
'operator': r'[+\-*/%=<>!&|^~?:]+'
},
'go': {
'comment': r'//.*$|/\*[\s\S]*?\*/',
'string': r'(["\'])(?:(?=(\\?))\2.)*?\1|`[^`]*`',
'keyword': r'\b(func|var|const|type|struct|interface|if|else|for|range|return|go|defer|package|import|switch|case|default|break|continue|select|chan|map)\b',
'function': r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()',
'class': r'\btype\s+([A-Z][a-zA-Z0-9_]*)',
'number': r'\b\d+\.?\d*\b',
'operator': r'[+\-*/%=<>!&|^~:]+'
}
}
def __init__(self, theme: Theme = Theme.DRACULA):
"""Initialize highlighter with theme"""
self.theme = theme
self.colors = self.THEMES[theme]
def highlight(self, code: str, language: str = 'python') -> str:
"""
Highlight code with syntax colors
Args:
code: Source code to highlight
language: Programming language (python, javascript, go, etc.)
Returns:
HTML string with colored syntax
"""
if language not in self.PATTERNS:
return self._wrap_html(code)
patterns = self.PATTERNS[language]
tokens = self._tokenize(code, patterns)
highlighted = self._apply_colors(tokens)
return self._wrap_html(highlighted)
def _tokenize(self, code: str, patterns: Dict[str, str]) -> List[Tuple[str, str]]:
"""Tokenize code into syntax elements"""
tokens = []
lines = code.split('\n')
for line in lines:
pos = 0
line_tokens = []
while pos < len(line):
matched = False
# Try to match each pattern
for token_type, pattern in patterns.items():
regex = re.compile(pattern, re.MULTILINE)
match = regex.match(line, pos)
if match:
line_tokens.append((token_type, match.group(0)))
pos = match.end()
matched = True
break
# No match, treat as regular text
if not matched:
line_tokens.append(('text', line[pos]))
pos += 1
tokens.append(line_tokens)
return tokens
def _apply_colors(self, tokens: List[List[Tuple[str, str]]]) -> str:
"""Apply color scheme to tokens"""
result = []
color_map = {
'comment': self.colors.comment,
'string': self.colors.string,
'keyword': self.colors.keyword,
'function': self.colors.function,
'class': self.colors.class_name,
'number': self.colors.number,
'operator': self.colors.operator,
'text': self.colors.foreground
}
for line_tokens in tokens:
line_html = []
for token_type, text in line_tokens:
color = color_map.get(token_type, self.colors.foreground)
line_html.append(f'<span style="color: {color}">{self._escape_html(text)}</span>')
result.append(''.join(line_html))
return '\n'.join(result)
def _wrap_html(self, content: str) -> str:
"""Wrap highlighted code in HTML with theme background"""
return f'''
<pre style="background-color: {self.colors.background};
color: {self.colors.foreground};
padding: 20px;
border-radius: 8px;
font-family: 'Fira Code', 'Consolas', monospace;
overflow-x: auto;
line-height: 1.6;">
<code>{content}</code>
</pre>
'''
def _escape_html(self, text: str) -> str:
"""Escape HTML special characters"""
return (text.replace('&', '&')
.replace('<', '<')
.replace('>', '>')
.replace('"', '"')
.replace("'", '''))
def set_theme(self, theme: Theme):
"""Change color theme"""
self.theme = theme
self.colors = self.THEMES[theme]
# Example usage
if __name__ == "__main__":
# Sample Python code
python_code = '''
def calculate_fibonacci(n: int) -> int:
"""Calculate nth Fibonacci number"""
if n <= 1:
return n
return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)
class MathHelper:
def __init__(self, name: str):
self.name = name
def greet(self):
print(f"Hello from {self.name}!")
# Create instance
helper = MathHelper("Snippify")
result = calculate_fibonacci(10)
'''
# Highlight with different themes
for theme in Theme:
highlighter = SyntaxHighlighter(theme)
html_output = highlighter.highlight(python_code, 'python')
print(f"\n{'='*50}")
print(f"Theme: {theme.value}")
print(f"{'='*50}")
print(html_output)
# JavaScript example
js_code = '''
const fetchUserData = async (userId) => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch:", error);
return null;
}
};
class User {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
'''
highlighter = SyntaxHighlighter(Theme.DRACULA)
print("\n" + highlighter.highlight(js_code, 'javascript'))