provisioning-outreach/presentations/rust-laspalmas-250926/auroraframe/framework/optimizers/optimize-highlight.nu

489 lines
16 KiB
Text
Raw Normal View History

#!/usr/bin/env nu
# Highlight.js syntax highlighting optimizer
# Automatically detects code blocks and includes necessary highlight.js assets
# Main highlight.js processing function
export def process_highlight [html_content: string, config: record, dev: bool, verbose: bool] {
let highlight_config = if "highlight" in ($config | columns) { $config.highlight } else { { enabled: false } }
# Handle different enabled values: "auto", true, false
let is_enabled = match $highlight_config.enabled {
"auto" => true # Will be determined by auto-detection
true => true
false => false
_ => false
}
if not $is_enabled {
if $verbose { print "🖍️ Syntax highlighting disabled, skipping..." }
return {
html: $html_content,
css_assets: [],
js_assets: [],
size: 0
}
}
if $verbose { print "🖍️ Processing syntax highlighting..." }
# Auto-detect code blocks if enabled
let auto_detect = if "auto_detect" in ($highlight_config | columns) { $highlight_config.auto_detect } else { true }
let detected_languages = if $auto_detect {
detect_code_languages $html_content $verbose
} else {
if "languages" in ($highlight_config | columns) { $highlight_config.languages } else { [] }
}
if ($detected_languages | length) == 0 {
if $verbose { print " ⚠️ No code blocks detected, skipping highlight.js" }
# Return original HTML without changes
return {
html: $html_content,
css_assets: [],
js_assets: [],
size: 0
}
}
# For "auto" mode, only proceed if we actually detected code blocks
if $highlight_config.enabled == "auto" and ($detected_languages | length) == 0 {
return {
html: $html_content,
css_assets: [],
js_assets: [],
size: 0
}
}
if $verbose { print $" 📝 Detected languages: ($detected_languages | str join ', ')" }
# Prepare highlight.js assets
let assets = (prepare_highlight_assets $detected_languages $highlight_config $dev $verbose)
# Enhance HTML with highlight.js integration
let enhanced_html = (enhance_html_with_highlight $html_content $highlight_config $verbose)
{
html: $enhanced_html,
css_assets: $assets.css_assets,
js_assets: $assets.js_assets,
size: $assets.total_size
}
}
# Detect programming languages in HTML code blocks
def detect_code_languages [html_content: string, verbose: bool] {
mut languages = []
# Check for code blocks with language classes
let has_code_blocks = ($html_content | str contains '<code' or ($html_content | str contains '<pre'))
if not $has_code_blocks {
return []
}
# Simple language detection from common patterns
if ($html_content | str contains 'language-rust') { $languages = ($languages | append 'rust') }
if ($html_content | str contains 'language-javascript') { $languages = ($languages | append 'javascript') }
if ($html_content | str contains 'language-typescript') { $languages = ($languages | append 'typescript') }
if ($html_content | str contains 'language-python') { $languages = ($languages | append 'python') }
if ($html_content | str contains 'language-bash') { $languages = ($languages | append 'bash') }
if ($html_content | str contains 'language-json') { $languages = ($languages | append 'json') }
if ($html_content | str contains 'language-html') { $languages = ($languages | append 'html') }
if ($html_content | str contains 'language-css') { $languages = ($languages | append 'css') }
if ($html_content | str contains 'language-toml') { $languages = ($languages | append 'toml') }
if ($html_content | str contains 'language-yaml') { $languages = ($languages | append 'yaml') }
# Also check for lang- variants
if ($html_content | str contains 'lang-rust') { $languages = ($languages | append 'rust') }
if ($html_content | str contains 'lang-js') { $languages = ($languages | append 'javascript') }
if ($html_content | str contains 'lang-javascript') { $languages = ($languages | append 'javascript') }
if ($html_content | str contains 'lang-ts') { $languages = ($languages | append 'typescript') }
if ($html_content | str contains 'lang-py') { $languages = ($languages | append 'python') }
if ($html_content | str contains 'lang-python') { $languages = ($languages | append 'python') }
if ($html_content | str contains 'lang-sh') { $languages = ($languages | append 'bash') }
if ($html_content | str contains 'lang-bash') { $languages = ($languages | append 'bash') }
# Also check for generic code blocks
let has_generic_code = ($html_content | str contains '<pre><code>' or ($html_content | str contains '<code>'))
if $has_generic_code and ($languages | length) == 0 {
# Default to common languages for generic code blocks
$languages = ["javascript", "bash", "json"]
if $verbose { print " 💡 Found generic code blocks, assuming common languages" }
}
# Normalize language names to highlight.js format
$languages | each { |lang|
match $lang {
"js" => "javascript"
"ts" => "typescript"
"sh" => "bash"
"shell" => "bash"
"yml" => "yaml"
"py" => "python"
_ => $lang
}
} | uniq
}
# Prepare highlight.js assets based on detected languages
def prepare_highlight_assets [languages: list, highlight_config: record, dev: bool, verbose: bool] {
let theme = if "theme" in ($highlight_config | columns) { $highlight_config.theme } else { "github-dark" }
let use_cdn = if "cdn" in ($highlight_config | columns) { $highlight_config.cdn } else { false }
let copy_button = if "copy_button" in ($highlight_config | columns) { $highlight_config.copy_button } else { true }
mut css_assets = []
mut js_assets = []
mut total_size = 0
if $use_cdn {
# Use CDN assets
let cdn_url = if "cdn_url" in ($highlight_config | columns) {
$highlight_config.cdn_url
} else {
"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/"
}
# Add CDN CSS
$css_assets = ($css_assets | append {
type: "cdn",
url: $"($cdn_url)styles/($theme).min.css",
inline: false
})
# Add CDN JS
$js_assets = ($js_assets | append {
type: "cdn",
url: $"($cdn_url)highlight.min.js",
inline: false
})
# Add language-specific modules
for lang in $languages {
$js_assets = ($js_assets | append {
type: "cdn",
url: $"($cdn_url)languages/($lang).min.js",
inline: false
})
}
if $verbose { print $" 🌐 Using CDN assets for theme: ($theme)" }
} else {
# Use local assets - download and bundle
let assets_result = (download_highlight_assets $languages $theme $dev $verbose)
$css_assets = $assets_result.css_assets
$js_assets = $assets_result.js_assets
$total_size = $assets_result.total_size
}
# Add copy button functionality if enabled
if $copy_button {
let copy_js = (generate_copy_button_script)
$js_assets = ($js_assets | append {
type: "inline",
content: $copy_js,
inline: true
})
}
# Add highlight.js initialization
let init_script = (generate_highlight_init_script $highlight_config)
$js_assets = ($js_assets | append {
type: "inline",
content: $init_script,
inline: true
})
{
css_assets: $css_assets,
js_assets: $js_assets,
total_size: $total_size
}
}
# Download and prepare local highlight.js assets
def download_highlight_assets [languages: list, theme: string, dev: bool, verbose: bool] {
# Create vendor directory for highlight.js
let vendor_dir = "shared/vendor/highlight"
mkdir $vendor_dir
mkdir ($vendor_dir | path join "styles")
mkdir ($vendor_dir | path join "languages")
mut css_assets = []
mut js_assets = []
mut total_size = 0
let base_url = "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build"
# Download theme CSS
let theme_css_path = ($vendor_dir | path join "styles" $"($theme).min.css")
if not ($theme_css_path | path exists) {
if $verbose { print $" 📥 Downloading theme: ($theme)" }
try {
http get $"($base_url)/styles/($theme).min.css" | save $theme_css_path
} catch {
if $verbose { print $" ⚠️ Failed to download theme ($theme), using github-dark fallback" }
try {
http get $"($base_url)/styles/github-dark.min.css" | save $theme_css_path
} catch {
if $verbose { print $" ❌ Failed to download highlight.js theme" }
}
}
}
if ($theme_css_path | path exists) {
let css_size = (ls $theme_css_path | get size | first | into int)
$total_size = ($total_size + $css_size)
$css_assets = ($css_assets | append {
type: "local",
path: $theme_css_path,
inline: false
})
}
# Download core highlight.js
let core_js_path = ($vendor_dir | path join "highlight.min.js")
if not ($core_js_path | path exists) {
if $verbose { print " 📥 Downloading highlight.js core" }
try {
http get $"($base_url)/highlight.min.js" | save $core_js_path
} catch {
if $verbose { print " ❌ Failed to download highlight.js core" }
}
}
if ($core_js_path | path exists) {
let js_size = (ls $core_js_path | get size | first | into int)
$total_size = ($total_size + $js_size)
$js_assets = ($js_assets | append {
type: "local",
path: $core_js_path,
inline: false
})
}
# Download language modules
for lang in $languages {
let lang_js_path = ($vendor_dir | path join "languages" $"($lang).min.js")
if not ($lang_js_path | path exists) {
if $verbose { print $" 📥 Downloading language: ($lang)" }
try {
http get $"($base_url)/languages/($lang).min.js" | save $lang_js_path
} catch {
if $verbose { print $" ⚠️ Failed to download language: ($lang)" }
}
}
if ($lang_js_path | path exists) {
let lang_size = (ls $lang_js_path | get size | first | into int)
$total_size = ($total_size + $lang_size)
$js_assets = ($js_assets | append {
type: "local",
path: $lang_js_path,
inline: false
})
}
}
{
css_assets: $css_assets,
js_assets: $js_assets,
total_size: $total_size
}
}
# Enhance HTML with highlight.js classes and copy buttons
def enhance_html_with_highlight [html_content: string, highlight_config: record, verbose: bool] {
let copy_button = if "copy_button" in ($highlight_config | columns) { $highlight_config.copy_button } else { true }
let line_numbers = if "line_numbers" in ($highlight_config | columns) { $highlight_config.line_numbers } else { false }
mut enhanced_html = $html_content
# Add copy buttons to code blocks if enabled
if $copy_button {
# Replace <pre><code> blocks with enhanced version
$enhanced_html = ($enhanced_html | str replace -ar '<pre><code([^>]*)>' '<div class="code-block-container"><button class="copy-button" onclick="copyCode(this)" title="Copy code">📋</button><pre><code$1>')
$enhanced_html = ($enhanced_html | str replace -ar '</code></pre>' '</code></pre></div>')
}
# Add line numbers wrapper if enabled
if $line_numbers {
$enhanced_html = ($enhanced_html | str replace -ar '<pre><code' '<pre class="line-numbers"><code')
}
# Ensure all code blocks have proper classes for highlight.js
$enhanced_html = ($enhanced_html | str replace -ar '<code(?![^>]*class=)' '<code class="hljs"')
if $verbose { print $" ✅ Enhanced HTML with highlight.js integration" }
$enhanced_html
}
# Generate copy button JavaScript
def generate_copy_button_script [] {
"
function copyCode(button) {
const codeBlock = button.nextElementSibling.querySelector('code');
const text = codeBlock.textContent;
navigator.clipboard.writeText(text).then(() => {
const originalText = button.textContent;
button.textContent = '✅';
button.style.color = '#10b981';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '';
}, 2000);
}).catch(() => {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
const originalText = button.textContent;
button.textContent = '✅';
setTimeout(() => {
button.textContent = originalText;
}, 2000);
});
}
"
}
# Generate highlight.js initialization script
def generate_highlight_init_script [highlight_config: record] {
let line_numbers = if "line_numbers" in ($highlight_config | columns) { $highlight_config.line_numbers } else { false }
let init_code = if $line_numbers {
"
document.addEventListener('DOMContentLoaded', function() {
hljs.highlightAll();
hljs.initLineNumbersOnLoad();
});
"
} else {
"
document.addEventListener('DOMContentLoaded', function() {
hljs.highlightAll();
});
"
}
$init_code
}
# Inject highlight.js assets into HTML head and body
export def inject_highlight_assets [html_content: string, css_assets: list, js_assets: list] {
mut updated_html = $html_content
# Inject CSS in head
let css_tags = ($css_assets | each { |asset|
match $asset.type {
"cdn" => $"<link rel=\"stylesheet\" href=\"($asset.url)\">"
"local" => $"<link rel=\"stylesheet\" href=\"($asset.path)\">"
"inline" => $"<style>($asset.content)</style>"
}
} | str join "\n")
if ($css_tags | str length) > 0 {
$updated_html = ($updated_html | str replace '</head>' $"($css_tags)\n</head>")
}
# Inject JS before </body>
let js_tags = ($js_assets | each { |asset|
match $asset.type {
"cdn" => $"<script src=\"($asset.url)\"></script>"
"local" => $"<script src=\"($asset.path)\"></script>"
"inline" => $"<script>($asset.content)</script>"
}
} | str join "\n")
if ($js_tags | str length) > 0 {
$updated_html = ($updated_html | str replace '</body>' $"($js_tags)\n</body>")
}
$updated_html
}
# Generate additional CSS for highlight.js styling
export def generate_highlight_css [highlight_config: record] {
let copy_button = if "copy_button" in ($highlight_config | columns) { $highlight_config.copy_button } else { true }
mut css = ""
if $copy_button {
$css = $css + "
/* Highlight.js code block styling with copy button */
.code-block-container {
position: relative;
margin: 1rem 0;
}
.copy-button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
color: #fff;
cursor: pointer;
font-size: 0.9rem;
padding: 0.25rem 0.5rem;
transition: all 0.2s ease;
z-index: 10;
}
.copy-button:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.3);
}
.code-block-container pre {
margin: 0;
padding-top: 2.5rem;
}
"
}
# Add general code styling
$css = $css + "
/* General code block styling */
pre code.hljs {
border-radius: 8px;
overflow-x: auto;
padding: 1rem;
}
code:not(pre code) {
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.85em;
padding: 0.2em 0.4em;
}
/* Line numbers styling */
pre.line-numbers {
position: relative;
padding-left: 3em;
}
pre.line-numbers::before {
content: counter(line-number);
counter-increment: line-number;
position: absolute;
left: 0;
color: rgba(255, 255, 255, 0.4);
text-align: right;
width: 2.5em;
padding-right: 0.5em;
}
"
$css
}