667 lines
26 KiB
Text
667 lines
26 KiB
Text
|
|
#!/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)"
|
||
|
|
}
|