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

545 lines
No EOL
18 KiB
Text

#!/usr/bin/env nu
# Markdown processing module with frontmatter support
# Supports multiple processors with Rust tools as default
# Main Markdown processing function
export def process_markdown [markdown_file: string, config: record, dev: bool, verbose: bool] {
let markdown_config = if "markdown" in ($config | columns) { $config.markdown } else { { enabled: false } }
if not $markdown_config.enabled {
if $verbose { print "📝 Markdown processing disabled, skipping..." }
return {
html: "",
metadata: {},
size: 0,
success: false
}
}
if not ($markdown_file | path exists) {
if $verbose { print $"❌ Markdown file not found: ($markdown_file)" }
return {
html: "",
metadata: {},
size: 0,
success: false
}
}
if $verbose { print "📝 Processing Markdown file..." }
# Read and parse Markdown file with frontmatter
let parsed_content = (parse_markdown_with_frontmatter $markdown_file $markdown_config $verbose)
if not $parsed_content.success {
return {
html: "",
metadata: {},
size: 0,
success: false
}
}
# Process Markdown to HTML
let html_result = (markdown_to_html $parsed_content.content $markdown_config $verbose)
if not $html_result.success {
return {
html: "",
metadata: $parsed_content.metadata,
size: 0,
success: false
}
}
# Apply HTML template if specified
let final_html = (apply_markdown_template $html_result.html $parsed_content.metadata $config $verbose)
{
html: $final_html,
metadata: $parsed_content.metadata,
size: ($final_html | str length),
success: true
}
}
# Parse Markdown file with frontmatter (YAML or TOML)
def parse_markdown_with_frontmatter [file_path: string, markdown_config: record, verbose: bool] {
let content = (open $file_path)
let frontmatter_format = if "frontmatter_format" in ($markdown_config | columns) {
$markdown_config.frontmatter_format
} else {
"yaml"
}
mut metadata = {}
mut markdown_content = $content
# Check for frontmatter delimiters
if ($content | str starts-with "---") {
# YAML frontmatter
let parts = ($content | str replace "---" "FRONTMATTER_DELIMITER" | split row "FRONTMATTER_DELIMITER")
if ($parts | length) >= 3 {
let frontmatter_raw = ($parts | get 1 | str trim)
$markdown_content = ($parts | skip 2 | str join "FRONTMATTER_DELIMITER" | str trim)
if ($frontmatter_raw | str length) > 0 {
$metadata = (parse_frontmatter $frontmatter_raw "yaml" $verbose)
}
}
} else if ($content | str starts-with "+++") {
# TOML frontmatter
let parts = ($content | str replace "+++" "FRONTMATTER_DELIMITER" | split row "FRONTMATTER_DELIMITER")
if ($parts | length) >= 3 {
let frontmatter_raw = ($parts | get 1 | str trim)
$markdown_content = ($parts | skip 2 | str join "FRONTMATTER_DELIMITER" | str trim)
if ($frontmatter_raw | str length) > 0 {
$metadata = (parse_frontmatter $frontmatter_raw "toml" $verbose)
}
}
}
if $verbose and (($metadata | columns | length) > 0) {
print $" 📋 Parsed frontmatter: ($metadata | columns | str join ', ')"
}
{
content: $markdown_content,
metadata: $metadata,
success: true
}
}
# Parse frontmatter content (YAML or TOML)
def parse_frontmatter [content: string, format: string, verbose: bool] {
try {
match $format {
"yaml" => {
# For now, we'll use a simple YAML parser
# In a real implementation, we'd use a proper YAML library
parse_simple_yaml $content $verbose
}
"toml" => {
# Parse TOML frontmatter
$content | from toml
}
_ => {
if $verbose { print $" ⚠️ Unknown frontmatter format: ($format)" }
{}
}
}
} catch { |e|
if $verbose { print $" ⚠️ Failed to parse frontmatter: ($e.msg)" }
{}
}
}
# Simple YAML parser for basic frontmatter
def parse_simple_yaml [content: string, verbose: bool] {
mut result = {}
let lines = ($content | lines | where { |line| ($line | str trim | str length) > 0 and not ($line | str starts-with "#") })
for line in $lines {
if ($line | str contains ":") {
let parts = ($line | split row ":" | take 2)
if ($parts | length) == 2 {
let key = ($parts | get 0 | str trim)
let value_raw = ($parts | get 1 | str trim)
# Parse different value types
let value = if ($value_raw | str starts-with '"') and ($value_raw | str ends-with '"') {
# String value
$value_raw | str replace '"' '' | str replace '"' ''
} else if ($value_raw | str starts-with '[') and ($value_raw | str ends-with ']') {
# Array value (simple)
$value_raw | str replace '[' '' | str replace ']' '' | split row ',' | each { |item| $item | str trim | str replace '"' '' }
} else if $value_raw in ["true", "false"] {
# Boolean value
$value_raw == "true"
} else {
# Try to parse as number, fallback to string
try {
$value_raw | into int
} catch {
try {
$value_raw | into float
} catch {
$value_raw
}
}
}
$result = ($result | insert $key $value)
}
}
}
$result
}
# Convert Markdown to HTML using configured processor
def markdown_to_html [markdown_content: string, markdown_config: record, verbose: bool] {
let processor = if "processor" in ($markdown_config | columns) {
$markdown_config.processor
} else {
"pulldown-cmark" # Rust default
}
if $verbose { print $" 🔧 Using Markdown processor: ($processor)" }
match $processor {
"pulldown-cmark" => { rust_pulldown_cmark $markdown_content $markdown_config $verbose }
"comrak" => { rust_comrak $markdown_content $markdown_config $verbose }
"pandoc" => { external_pandoc $markdown_content $markdown_config $verbose }
"marked" => { external_marked $markdown_content $markdown_config $verbose }
"markdown-it" => { external_markdown_it $markdown_content $markdown_config $verbose }
_ => {
if $verbose { print $" ⚠️ Unknown processor ($processor), falling back to pulldown-cmark" }
rust_pulldown_cmark $markdown_content $markdown_config $verbose
}
}
}
# Process Markdown using Rust pulldown-cmark (default)
def rust_pulldown_cmark [content: string, config: record, verbose: bool] {
# Check if pulldown-cmark is available as a CLI tool
let has_pulldown_cmark = (which pulldown-cmark | length) > 0
if $has_pulldown_cmark {
try {
# Create temp file for input
let temp_input = $"/tmp/markdown_input_(date now | format date '%s').md"
$content | save $temp_input
# Process with pulldown-cmark
let html_output = (run-external "pulldown-cmark" $temp_input)
# Clean up
rm $temp_input
{
html: $html_output,
success: true
}
} catch { |e|
if $verbose { print $" ❌ pulldown-cmark failed: ($e.msg)" }
# Fallback to built-in simple processor
simple_markdown_processor $content $config $verbose
}
} else {
if $verbose { print " 💡 pulldown-cmark not found, using built-in processor" }
simple_markdown_processor $content $config $verbose
}
}
# Process Markdown using Rust comrak
def rust_comrak [content: string, config: record, verbose: bool] {
let has_comrak = (which comrak | length) > 0
if $has_comrak {
try {
# Create temp file for input
let temp_input = $"/tmp/markdown_input_(date now | format date '%s').md"
$content | save $temp_input
# Process with comrak (GitHub-flavored Markdown)
let args = ["--gfm", "--unsafe", $temp_input]
let html_output = (run-external "comrak" ...$args)
# Clean up
rm $temp_input
{
html: $html_output,
success: true
}
} catch { |e|
if $verbose { print $" ❌ comrak failed: ($e.msg)" }
# Fallback to built-in processor
simple_markdown_processor $content $config $verbose
}
} else {
if $verbose { print " 💡 comrak not found, falling back to pulldown-cmark" }
rust_pulldown_cmark $content $config $verbose
}
}
# Process Markdown using external pandoc
def external_pandoc [content: string, config: record, verbose: bool] {
let has_pandoc = (which pandoc | length) > 0
if $has_pandoc {
try {
# Create temp file for input
let temp_input = $"/tmp/markdown_input_(date now | format date '%s').md"
$content | save $temp_input
# Process with pandoc
let args = ["-f", "markdown", "-t", "html", $temp_input]
let html_output = (run-external "pandoc" ...$args)
# Clean up
rm $temp_input
{
html: $html_output,
success: true
}
} catch { |e|
if $verbose { print $" ❌ pandoc failed: ($e.msg)" }
# Fallback to Rust processor
rust_pulldown_cmark $content $config $verbose
}
} else {
if $verbose { print " 💡 pandoc not found, falling back to pulldown-cmark" }
rust_pulldown_cmark $content $config $verbose
}
}
# Process Markdown using external marked (Node.js)
def external_marked [content: string, config: record, verbose: bool] {
let has_marked = (which marked | length) > 0
if $has_marked {
try {
# Create temp file for input
let temp_input = $"/tmp/markdown_input_(date now | format date '%s').md"
$content | save $temp_input
# Process with marked
let args = [$temp_input]
let html_output = (run-external "marked" ...$args)
# Clean up
rm $temp_input
{
html: $html_output,
success: true
}
} catch { |e|
if $verbose { print $" ❌ marked failed: ($e.msg)" }
# Fallback to Rust processor
rust_pulldown_cmark $content $config $verbose
}
} else {
if $verbose { print " 💡 marked not found, falling back to pulldown-cmark" }
rust_pulldown_cmark $content $config $verbose
}
}
# Process Markdown using external markdown-it (Node.js)
def external_markdown_it [content: string, config: record, verbose: bool] {
let has_markdown_it = (which markdown-it | length) > 0
if $has_markdown_it {
try {
# Create temp file for input
let temp_input = $"/tmp/markdown_input_(date now | format date '%s').md"
$content | save $temp_input
# Process with markdown-it
let args = [$temp_input]
let html_output = (run-external "markdown-it" ...$args)
# Clean up
rm $temp_input
{
html: $html_output,
success: true
}
} catch { |e|
if $verbose { print $" ❌ markdown-it failed: ($e.msg)" }
# Fallback to Rust processor
rust_pulldown_cmark $content $config $verbose
}
} else {
if $verbose { print " 💡 markdown-it not found, falling back to pulldown-cmark" }
rust_pulldown_cmark $content $config $verbose
}
}
# Simple built-in Markdown processor (fallback)
def simple_markdown_processor [content: string, config: record, verbose: bool] {
if $verbose { print " 🔧 Using built-in Markdown processor" }
mut html = $content
# Headers
$html = ($html | str replace -ar '^# (.+)$' '<h1>$1</h1>')
$html = ($html | str replace -ar '^## (.+)$' '<h2>$1</h2>')
$html = ($html | str replace -ar '^### (.+)$' '<h3>$1</h3>')
$html = ($html | str replace -ar '^#### (.+)$' '<h4>$1</h4>')
# Bold and italic
$html = ($html | str replace -ar '\*\*([^*]+)\*\*' '<strong>$1</strong>')
$html = ($html | str replace -ar '\*([^*]+)\*' '<em>$1</em>')
# Code blocks
$html = ($html | str replace -ar '```([a-zA-Z0-9]*)\n(.*?)\n```' '<pre><code class="language-$1">$2</code></pre>')
$html = ($html | str replace -ar '`([^`]+)`' '<code>$1</code>')
# Links
$html = ($html | str replace -ar '\[([^\]]+)\]\(([^)]+)\)' '<a href="$2">$1</a>')
# Paragraphs (simple)
let lines = ($html | lines)
let processed_lines = ($lines | each { |line|
if ($line | str trim | str length) == 0 {
""
} else if ($line | str starts-with "<") {
$line
} else {
$"<p>($line)</p>"
}
})
$html = ($processed_lines | str join "\n")
{
html: $html,
success: true
}
}
# Apply HTML template to Markdown content
def apply_markdown_template [html_content: string, metadata: record, config: record, verbose: bool] {
let markdown_config = if "markdown" in ($config | columns) { $config.markdown } else { {} }
let template_name = if "template" in ($metadata | columns) {
$metadata.template
} else if "template" in ($markdown_config | columns) {
$markdown_config.template
} else {
"article"
}
if $verbose { print $" 🎨 Applying template: ($template_name)" }
# Load template
let template_result = (load_markdown_template $template_name $config $verbose)
if not $template_result.success {
if $verbose { print " ⚠️ Template loading failed, using content as-is" }
return $html_content
}
# Replace template variables
let final_html = (render_template $template_result.content $html_content $metadata $config)
$final_html
}
# Load HTML template for Markdown content
def load_markdown_template [template_name: string, config: record, verbose: bool] {
# Find framework root
let current_dir = $env.PWD
let framework_templates = ($current_dir | path join "framework" "templates" "markdown")
let template_path = ($framework_templates | path join $"($template_name).html")
if ($template_path | path exists) {
let content = (open $template_path)
{
content: $content,
success: true
}
} else {
# Try custom template path if specified
let markdown_config = if "markdown" in ($config | columns) { $config.markdown } else { {} }
let custom_template = if "custom_template" in ($markdown_config | columns) {
$markdown_config.custom_template
} else { "" }
if ($custom_template | str length) > 0 and ($custom_template | path exists) {
let content = (open $custom_template)
{
content: $content,
success: true
}
} else {
if $verbose { print $" ❌ Template not found: ($template_path)" }
{
content: "",
success: false
}
}
}
}
# Render template with variables
def render_template [template: string, content: string, metadata: record, config: record] {
mut rendered = $template
# Replace content placeholder
$rendered = ($rendered | str replace "{{CONTENT}}" $content)
# Replace metadata variables
for key in ($metadata | columns) {
let value = $metadata | get $key
let placeholder = $"{{($key | str upcase)}}"
$rendered = ($rendered | str replace $placeholder ($value | into string))
}
# Replace config variables
let page_config = if "page" in ($config | columns) { $config.page } else { {} }
for key in ($page_config | columns) {
let value = $page_config | get $key
let placeholder = $"{{PAGE_($key | str upcase)}}"
$rendered = ($rendered | str replace $placeholder ($value | into string))
}
# Default fallbacks
$rendered = ($rendered | str replace -ar '{{[^}]+}}' '') # Remove unmatched placeholders
$rendered
}
# Detect if a directory contains Markdown source
export def has_markdown_source [src_dir: string, markdown_config?: record] {
let config = if ($markdown_config | is-empty) { {} } else { $markdown_config }
let source_file = if "source_file" in ($config | columns) {
$config.source_file
} else {
"content.md"
}
let markdown_path = ($src_dir | path join $source_file)
($markdown_path | path exists)
}
# Get Markdown source file path
export def get_markdown_source_path [src_dir: string, markdown_config?: record] {
let config = if ($markdown_config | is-empty) { {} } else { $markdown_config }
let source_file = if "source_file" in ($config | columns) {
$config.source_file
} else {
"content.md"
}
($src_dir | path join $source_file)
}
# Validate Markdown processor availability
export def check_markdown_processors [] {
let processors = [
{ name: "pulldown-cmark", command: "pulldown-cmark", language: "Rust" },
{ name: "comrak", command: "comrak", language: "Rust" },
{ name: "pandoc", command: "pandoc", language: "Haskell" },
{ name: "marked", command: "marked", language: "Node.js" },
{ name: "markdown-it", command: "markdown-it", language: "Node.js" }
]
$processors | each { |proc|
{
name: $proc.name,
available: ((which $proc.command | length) > 0),
language: $proc.language,
command: $proc.command
}
}
}