#!/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] { 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, languages: list, 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] { 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, languages: list, 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, languages: list, 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] { 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)" }