website-htmx-rustelo-code/scripts/content/old/build-localized-content.nu
2026-07-10 03:44:13 +01:00

667 lines
No EOL
26 KiB
Text
Executable file

#!/usr/bin/env nu
# Build Localized Content Script - Updated to use Rust content_processor
# Delegates to the comprehensive content_processor.rs for all content generation
# Simple wrapper that calls the Rust content processor
def main [
--content-type (-t): string # Content type to process (blog, recipes, etc.)
--language (-l): string # Language to process (en, es, etc.)
--category (-c): string # Specific category to process
--file (-f): string # Specific file pattern to process
--watch (-w) # Enable watch mode for hot updates
--help (-h) # Show help
] {
if $help {
print $"
(ansi green)📚 Build Localized Content - Rust Content Processor Wrapper(ansi reset)
(ansi blue)USAGE:(ansi reset)
nu scripts/content/build-localized-content.nu [OPTIONS]
(ansi blue)OPTIONS:(ansi reset)
--content-type, -t Content type to process (blog, recipes, etc.)
--language, -l Language to process (en, es, etc.)
--category, -c Specific category to process
--file, -f Specific file pattern to process
--watch, -w Enable watch mode for hot updates
--help, -h Show this help
(ansi blue)EXAMPLES:(ansi reset)
# Process all blog content
nu scripts/content/build-localized-content.nu --content-type blog
# Process English blog content only
nu scripts/content/build-localized-content.nu --content-type blog --language en
# Process specific category
nu scripts/content/build-localized-content.nu --content-type blog --category rust
# Process specific file
nu scripts/content/build-localized-content.nu --file \"rust-web-development-2024\"
# Enable watch mode
nu scripts/content/build-localized-content.nu --content-type blog --watch
(ansi yellow)NOTE:(ansi reset) This script now delegates to the Rust content_processor for all operations.
Use 'nu scripts/content/build-content-enhanced.nu' for advanced features and statistics.
"
return
}
print $"(ansi green)🚀 Delegating to Rust content processor...(ansi reset)"
mut cmd_args = []
if ($content_type | is-not-empty) {
$cmd_args = ($cmd_args | append ["--content-type" $content_type])
}
if ($language | is-not-empty) {
$cmd_args = ($cmd_args | append ["--language" $language])
}
if ($category | is-not-empty) {
$cmd_args = ($cmd_args | append ["--category" $category])
}
if ($file | is-not-empty) {
$cmd_args = ($cmd_args | append ["--file" $file])
}
if $watch {
$cmd_args = ($cmd_args | append ["--watch"])
}
# Execute the Rust content processor
if ($cmd_args | length) > 0 {
run-external "cargo" "run" "--features=content-static" "--bin" "content_processor" "--" ...$cmd_args
} else {
run-external "cargo" "run" "--features=content-static" "--bin" "content_processor"
}
}
# Load environment variables from .env file
def load_env_from_file [] {
let env_file = ".env"
if ($env_file | path exists) {
let env_content = (open $env_file | lines)
for line in $env_content {
# Skip comments and empty lines
if not ($line | str trim | str starts-with "#") and not ($line | str trim | is-empty) {
# Parse KEY=VALUE format with variable expansion
if ($line | str contains "=") {
let parts = ($line | split column "=" key value)
if ($parts | length) >= 2 {
let key = ($parts | first | get key | str trim)
let value = ($parts | first | get value | str trim)
# Remove quotes and expand ${VARIABLE} patterns
let clean_value = ($value | str replace -a '"' '' | str replace -a "'" '')
let expanded_value = expand_env_variables $clean_value
# Set environment variable
load-env {($key): $expanded_value}
}
}
}
}
}
}
# Expand ${VARIABLE} patterns in environment values
def expand_env_variables [value: string] {
mut result = $value
# Simple ${VAR} expansion
let var_pattern = '\\$\\{([A-Z_][A-Z0-9_]*)\\}'
let matches = ($result | parse --regex $var_pattern)
for match in $matches {
if "capture0" in ($match | columns) {
let var_name = ($match | get capture0)
if $var_name in $env {
let var_value = ($env | get $var_name)
$result = ($result | str replace $"\\${${var_name}}" $var_value)
}
}
}
$result
}
# Get environment variable with fallback
def get_env_var [var_name: string, default_value: string] {
if $var_name in $env {
return ($env | get $var_name)
}
return $default_value
}
def main [
--help(-h) # Show help message
--content-type(-t): string # Process specific content type only
--language(-l): string # Process specific language only
--use-rust-processor # Use the new Rust content processor (recommended)
] {
# Show help if requested
if $help {
print $"
(ansi blue)Build Localized Content Script(ansi reset)
Legacy Nushell content processor - DEPRECATED
(ansi yellow)⚠️ DEPRECATION NOTICE:(ansi reset)
This script is deprecated. Please use the new enhanced scripts:
- scripts/content/build-content.nu (Nushell)
- scripts/content/build-content.sh (Bash)
- scripts/content/content-processor.nu (Advanced wrapper)
(ansi green)MIGRATION:(ansi reset)
Old: ./build-localized-content.nu
New: ./build-content.nu
The new scripts use the Rust content_processor for better performance,
reliability, and granular processing options.
(ansi green)OPTIONS:(ansi reset)
-h, --help Show this help message
-t, --content-type TYPE Process specific content type only
-l, --language LANG Process specific language only
--use-rust-processor Use the new Rust processor (recommended)
(ansi green)EXAMPLES:(ansi reset)
# Use new Rust processor (recommended)
./build-localized-content.nu --use-rust-processor
# Use new enhanced script instead
./build-content.nu --help
"
return
}
# Use Rust processor if requested
if $use_rust_processor {
print $"(ansi blue)🚀 Using new Rust content processor...(ansi reset)"
mut cmd = ["cargo", "run", "--features=content-static", "--bin", "content_processor", "--"]
if $content_type != null {
$cmd = ($cmd | append ["--content-type", $content_type])
}
if $language != null {
$cmd = ($cmd | append ["--language", $language])
}
print $"(ansi blue)🔧 Running: ($cmd | str join ' ')(ansi reset)"
run-external $cmd.0 ...$cmd.1
print $"(ansi green)✅ Content processing completed!(ansi reset)"
print $"(ansi yellow)💡 Consider migrating to ./build-content.nu for full features(ansi reset)"
return
}
# Legacy processing (kept for compatibility but show deprecation warning)
print $"(ansi yellow)⚠️ WARNING: Using legacy content processor!(ansi reset)"
print $"(ansi yellow) Consider using --use-rust-processor or migrating to ./build-content.nu(ansi reset)"
# Load environment variables
load_env_from_file
# Configuration from environment
let content_dir = (get_env_var "SITE_CONTENT_PATH" "site/content")
let server_content_root = (get_env_var "SITE_SERVER_ROOT_CONTENT" "site/static-content")
let public_target = $"public/($server_content_root | path basename)"
let languages = ["en", "es"]
print $"(ansi blue)🌍 Building frontmatter-only content to HTML...(ansi reset)"
print $"(ansi blue)📁 Source: ($content_dir) - markdown + frontmatter only(ansi reset)"
print $"(ansi blue)📂 Target: ($public_target) - generated HTML + JSON only(ansi reset)"
# Get enabled content types
let content_types = get_enabled_content_types $content_dir
print $"(ansi blue)📋 Content types: ($content_types | str join ', ')(ansi reset)"
# Filter content types if specified
let filtered_content_types = if $content_type != null {
$content_types | where $it == $content_type
} else {
$content_types
}
# Filter languages if specified
let filtered_languages = if $language != null {
[$language]
} else {
$languages
}
# Build the markdown converter
build_markdown_converter
# Clean and create target directories
clean_and_create_target_directories $public_target $filtered_content_types
# Process content for each type and language
process_all_content $filtered_content_types $filtered_languages $content_dir $public_target
# Create backward compatibility links
create_compatibility_links $public_target $filtered_content_types
# Generate content statistics and manifest
generate_content_statistics $filtered_content_types $filtered_languages $content_dir $public_target
generate_content_manifest $public_target $filtered_content_types $filtered_languages $content_dir
show_completion_summary $public_target $filtered_content_types
}
# Get enabled content types from content-kinds.toml
def get_enabled_content_types [content_dir: string] {
let content_kinds_file = $"($content_dir)/content-kinds.toml"
if not ($content_kinds_file | path exists) {
print $"(ansi yellow)⚠️ Warning: content-kinds.toml not found, using defaults(ansi reset)"
return ["blog", "recipes"]
}
mut enabled_types = []
let toml_content = open $content_kinds_file
if "content_kinds" in $toml_content {
for content_kind in $toml_content.content_kinds {
if $content_kind.enabled {
let dir_path = $"($content_dir)/($content_kind.directory)"
if ($dir_path | path exists) {
$enabled_types = ($enabled_types | append $content_kind.directory)
print $"(ansi green)✅ Content type '($content_kind.name)' -> '($content_kind.directory)' enabled(ansi reset)"
} else {
print $"(ansi yellow)⚠️ WARNING: Content type '($content_kind.name)' enabled but directory missing - SKIPPING(ansi reset)"
}
}
}
}
if ($enabled_types | is-empty) {
print $"(ansi yellow)⚠️ No enabled content types, using defaults(ansi reset)"
return ["blog", "recipes"]
}
$enabled_types
}
# Build the markdown converter
def build_markdown_converter [] {
print $"(ansi blue)🔧 Building markdown converter...(ansi reset)"
try {
cargo build --bin markdown_converter --features content-static --quiet
print $"(ansi green)✅ Markdown converter built(ansi reset)"
} catch {
print $"(ansi red)❌ Failed to build markdown converter(ansi reset)"
exit 1
}
}
# Clean and create target directories
def clean_and_create_target_directories [target_dir: string, content_types: list<string>] {
print $"(ansi blue)🧹 Cleaning and creating target directories...(ansi reset)"
# Clean existing generated content
if ($target_dir | path exists) {
rm -rf $target_dir
}
# Create fresh target directories
mkdir $target_dir
for content_type in $content_types {
let dir_path = $"($target_dir)/($content_type)"
mkdir $dir_path
print $"(ansi blue)📁 Created: ($dir_path)(ansi reset)"
}
}
# Process all content for each type and language
def process_all_content [content_types: list<string>, languages: list<string>, content_dir: string, target_dir: string] {
print $"(ansi blue)📝 Processing frontmatter-only content...(ansi reset)"
for content_type in $content_types {
print $"(ansi blue)🔄 Processing: ($content_type)(ansi reset)"
for lang in $languages {
let content_source = $"($content_dir)/($content_type)/($lang)"
let content_target = $"($target_dir)/($content_type)/($lang)"
if ($content_source | path exists) {
print $"(ansi yellow) - Processing ($content_type) for ($lang)(ansi reset)"
mkdir $content_target
# Convert markdown files with frontmatter
convert_frontmatter_content $content_source $content_target $lang $content_type
# Generate content index from frontmatter
generate_content_index_from_frontmatter $content_source $content_target $content_type $lang
# Generate filter index for search/filtering
generate_filter_index $content_type $lang $content_source $content_target
# NOTE: meta.json generation handled by Rust content converter
let html_count = (glob $"($content_target)/**/*.html" | length)
print $"(ansi green) ✅ Converted ($html_count) ($content_type) items for ($lang)(ansi reset)"
} else {
print $"(ansi yellow) ⚠️ No ($content_type) content for ($lang)(ansi reset)"
}
}
}
}
# Convert markdown files with frontmatter
def convert_frontmatter_content [content_source: string, content_target: string, lang: string, content_type: string] {
let converter = "./target/debug/markdown_converter"
# Convert top-level markdown files
let top_level_files = (glob $"($content_source)/*.md")
for md_file in $top_level_files {
let filename = ($md_file | path basename | path parse | get stem)
let html_file = $"($content_target)/($filename).html"
convert_single_markdown $md_file $html_file $converter
}
# Process category directories
let category_dirs = (ls $content_source | where type == dir | get name)
for category_dir in $category_dirs {
let category = ($category_dir | path basename)
let category_target = $"($content_target)/($category)"
mkdir $category_target
print $"(ansi blue) 📁 Processing category: ($category)(ansi reset)"
# Convert markdown files in category
let category_files = (glob $"($category_dir)/*.md")
for md_file in $category_files {
let filename = ($md_file | path basename | path parse | get stem)
let html_file = $"($category_target)/($filename).html"
convert_single_markdown $md_file $html_file $converter
}
# Generate category index from frontmatter
let category_posts = (glob $"($category_dir)/*.md" | length)
if $category_posts > 0 {
generate_category_index_from_frontmatter $category_dir $category_target $category $content_type $lang
}
}
}
# Convert a single markdown file
def convert_single_markdown [input_file: string, output_file: string, converter: string] {
let input_basename = ($input_file | path basename)
let output_basename = ($output_file | path basename)
print $"(ansi yellow) Converting: ($input_basename) -> ($output_basename)(ansi reset)"
let output_dir = ($output_file | path dirname)
mkdir $output_dir
try {
^$converter $input_file -o $output_file
} catch {
print $"(ansi yellow) ⚠️ Warning: Failed to convert ($input_file)(ansi reset)"
}
}
# Generate content index from frontmatter data
def generate_content_index_from_frontmatter [content_source: string, content_target: string, content_type: string, lang: string] {
let index_file = $"($content_target)/index.json"
let generated_at = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
print $"(ansi blue) - Generating content index from frontmatter...(ansi reset)"
mut posts = []
# Process all markdown files
let md_files = (glob $"($content_source)/**/*.md")
for md_file in $md_files {
let frontmatter = extract_frontmatter $md_file
let filename = ($md_file | path basename | path parse | get stem)
# Extract frontmatter fields
let title = (extract_from_frontmatter $frontmatter "title" $filename)
let published = (extract_from_frontmatter $frontmatter "published" "true")
let category = (extract_from_frontmatter $frontmatter "category" "general")
let tags_raw = (extract_from_frontmatter $frontmatter "tags" "[]")
let excerpt = (extract_from_frontmatter $frontmatter "excerpt" "")
let date = (extract_from_frontmatter $frontmatter "date" "")
let author = (extract_from_frontmatter $frontmatter "author" "")
let featured = (extract_from_frontmatter $frontmatter "featured" "false")
# Only include published content
if $published == "true" {
# Determine HTML path relative to category
let relative_path = ($md_file | str replace $"($content_source)/" "")
let html_path = ($relative_path | str replace ".md" ".html")
$posts = ($posts | append {
id: $filename,
title: $title,
category: $category,
tags: $tags_raw,
excerpt: $excerpt,
date: $date,
author: $author,
featured: ($featured == "true"),
html_file: $html_path
})
}
}
let content_index = {
generated_at: $generated_at,
content_type: $content_type,
language: $lang,
($content_type): $posts
}
$content_index | to json | save --force $index_file
print $"(ansi green) ✅ Generated content index: ($index_file)(ansi reset)"
}
# Generate category index from frontmatter
def generate_category_index_from_frontmatter [category_dir: string, category_target: string, category: string, content_type: string, lang: string] {
let index_file = $"($category_target)/index.json"
let generated_at = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
print $"(ansi blue) 🔍 Generating category index: ($category)(ansi reset)"
mut posts = []
let md_files = (glob $"($category_dir)/*.md")
for md_file in $md_files {
let frontmatter = extract_frontmatter $md_file
let filename = ($md_file | path basename | path parse | get stem)
let title = (extract_from_frontmatter $frontmatter "title" $filename)
let published = (extract_from_frontmatter $frontmatter "published" "true")
let sort_order = (extract_from_frontmatter $frontmatter "sort_order" "0")
let css_class = (extract_from_frontmatter $frontmatter "css_class" "")
if $published == "true" {
$posts = ($posts | append {
id: $filename,
title: $title,
css_class: $css_class,
sort_order: ($sort_order | into int),
html_file: $"($filename).html"
})
}
}
# Sort by sort_order
$posts = ($posts | sort-by sort_order)
let category_index = {
generated_at: $generated_at,
content_type: $content_type,
language: $lang,
category: $category,
title: ($category | str title-case),
posts: $posts
}
$category_index | to json | save --force $index_file
print $"(ansi green) ✅ Category index: ($index_file)(ansi reset)"
}
# Using frontmatter functions from modular library (lib/frontmatter.nu)
# Generate filter index for search functionality
def generate_filter_index [content_type: string, lang: string, source_dir: string, target_dir: string] {
let filter_index_file = $"($target_dir)/filter-index.json"
let generated_at = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
print $"(ansi blue) - Generating filter index for ($content_type) ($lang)...(ansi reset)"
# Count total published files from frontmatter
mut total_files = 0
mut tags = []
mut categories = []
let md_files = (glob $"($source_dir)/**/*.md")
for md_file in $md_files {
let frontmatter = extract_frontmatter $md_file
let published = (extract_from_frontmatter $frontmatter "published" "true")
if $published == "true" {
$total_files = ($total_files + 1)
# Collect tags and categories for filtering
let category = (extract_from_frontmatter $frontmatter "category" "")
if not ($category | is-empty) and $category not-in $categories {
$categories = ($categories | append $category)
}
let tags_raw = (extract_from_frontmatter $frontmatter "tags" "")
# Simple tags extraction (could be improved for complex arrays)
if not ($tags_raw | is-empty) {
let extracted_tags = ($tags_raw | str replace -a '[' '' | str replace -a ']' '' | str replace -a '"' '' | split row ',' | each {|tag| $tag | str trim})
for tag in $extracted_tags {
if not ($tag | is-empty) and $tag not-in $tags {
$tags = ($tags | append $tag)
}
}
}
}
}
let filter_index = {
generated_at: $generated_at,
content_type: $content_type,
language: $lang,
total_posts: $total_files,
tags: $tags,
categories: $categories
}
$filter_index | to json | save --force $filter_index_file
print $"(ansi green) ✅ Filter index: ($filter_index_file)(ansi reset)"
}
# NOTE: meta.json generation now handled by Rust content converter
# Create backward compatibility links
def create_compatibility_links [target_dir: string, content_types: list<string>] {
print $"(ansi blue)🔗 Creating backward compatibility links...(ansi reset)"
for content_type in $content_types {
let en_index = $"($target_dir)/($content_type)/en/index.json"
let fallback_index = $"($target_dir)/($content_type)/index.json"
if ($en_index | path exists) {
try {
if ($fallback_index | path exists) {
rm $fallback_index
}
ln -s "en/index.json" $fallback_index
print $"(ansi green) ✅ Created fallback ($content_type) index(ansi reset)"
} catch {
print $"(ansi yellow) ⚠️ Failed to create symlink for ($content_type)(ansi reset)"
}
}
}
}
# Generate content statistics
def generate_content_statistics [content_types: list<string>, languages: list<string>, content_dir: string, target_dir: string] {
print $"(ansi blue)📊 Content Statistics:(ansi reset)"
for lang in $languages {
print $"(ansi blue) Language: ($lang)(ansi reset)"
for content_type in $content_types {
let html_count = (glob $"($target_dir)/($content_type)/($lang)/**/*.html" | length)
let md_count = (glob $"($content_dir)/($content_type)/($lang)/**/*.md" | length)
print $"(ansi yellow) 📝 ($content_type): ($md_count) MD → ($html_count) HTML(ansi reset)"
}
}
}
# Generate content manifest
def generate_content_manifest [target_dir: string, content_types: list<string>, languages: list<string>, content_dir: string] {
let manifest_file = $"($target_dir)/content-manifest.json"
let generated_at = (date now | format date "%Y-%m-%dT%H:%M:%SZ")
print $"(ansi blue)📋 Generating content manifest...(ansi reset)"
mut structure = {}
for content_type in $content_types {
mut lang_data = {}
for lang in $languages {
let html_count = (glob $"($target_dir)/($content_type)/($lang)/**/*.html" | length)
let md_count = (glob $"($content_dir)/($content_type)/($lang)/**/*.md" | length)
let has_index = ($"($target_dir)/($content_type)/($lang)/index.json" | path exists)
$lang_data = ($lang_data | upsert $lang {
html_count: $html_count,
markdown_count: $md_count,
has_index: $has_index
})
}
$structure = ($structure | upsert $content_type $lang_data)
}
let manifest = {
generated_at: $generated_at,
version: "2.0",
approach: "frontmatter-only",
languages: $languages,
content_types: $content_types,
content_source_path: $content_dir,
content_target_path: $target_dir,
build_info: {
source_format: "markdown + yaml frontmatter",
output_format: "html + json indexes",
converter: "rustelo_markdown_converter",
features: ["frontmatter-extraction", "syntax-highlighting", "localization"]
},
structure: $structure
}
$manifest | to json | save --force $manifest_file
print $"(ansi green) ✅ Content manifest: ($manifest_file)(ansi reset)"
}
# Show completion summary
def show_completion_summary [target_dir: string, content_types: list<string>] {
print ""
print $"(ansi green)🎉 Frontmatter-only content build complete!(ansi reset)"
print ""
print $"(ansi blue)✅ Clean separation achieved:(ansi reset)"
print $" 📝 Source: Only .md files with frontmatter metadata"
print $" 🌐 Generated: Only .html + .json files in ($target_dir)"
print ""
print $"(ansi blue)📁 Generated structure:(ansi reset)"
for content_type in $content_types {
print $" 📄 ($target_dir)/($content_type)/{{lang}}/*.html"
print $" 📋 ($target_dir)/($content_type)/{{lang}}/index.json"
print $" 🔍 ($target_dir)/($content_type)/{{lang}}/filter-index.json"
}
print $" 📊 ($target_dir)/content-manifest.json"
print ""
print $"(ansi green)🔄 To rebuild: Run this script after content changes(ansi reset)"
}