1 #!/usr/bin/zsh
   2 
   3 ###############################################################################
   4 ###############################################################################
   5 #
   6 # Quick Site Generator 2 is a static website generator inspired by Nikola.
   7 # It is written for the Z shell (zsh) because that's what I use and also because
   8 # I like it better than Bash.
   9 #
  10 # This script is an almost complete rewrite of my old script because it became
  11 # overly complicated and had way too many bugs, even though it worked on simple
  12 # sites.
  13 # 
  14 # https://github.com/kekePower/qsgen2/
  15 # 
  16 ###############################################################################
  17 ###############################################################################
  18 
  19 VERSION="0.4.3" # Sat-2024-02-24
  20 QSGEN="Quick Site Generator 2"
  21 
  22 # Set to true or false
  23 # This will show debug information from every function in this script
  24 # You can also set debug=true in a single function if you want to debug only that specific one.
  25 globaldebug=false
  26 
  27 # Use Zsh fpath to set the path to some extra functions
  28 fpath=(${HOME}/bin/include/common ${HOME}/bin/include/qsgen2/lang $fpath)
  29 # In this case, let's load the 'include' function
  30 autoload include
  31 autoload zini
  32 
  33 # Including some colors to the script
  34 include common/colors
  35 
  36 echo "${magenta}${blue_bg} ${QSGEN} ${end}${bold_white}${blue_bg}${VERSION} ${end}"
  37 
  38 # Check for, and source, the config file for this specific website
  39 if [[ -f $(pwd)/config ]]; then
  40   if (${globaldebug}); then echo "${red}Config file found and sourced${end}\n${yellow} - $(pwd)/config${end}"; fi
  41   zini $(pwd)/config
  42 else
  43         echo "${red}Cannot find configuration file.${end}"
  44         echo "${yellow} - Please create the file 'config' in your project directory.${end}"
  45   echo "${yellow} - See 'config.example' in the git source tree.${end}"
  46         exit
  47 fi
  48 
  49 # Load language as defined in config
  50 typeset -A qsgenlang
  51 lang_found=false
  52 for dir in $fpath; do
  53   if [[ -f "${dir}/${config[project_lang]}" ]]; then
  54     # echo "Language file: ${dir}/${config[project_lang]}"
  55     source "${dir}/${config[project_lang]}"
  56     lang_found=true
  57     break
  58   fi
  59 done
  60 if [[ ${lang_found} == "false" ]]; then
  61       # Fall back to en_US if defined language isn't found
  62       echo "Defined language, ${config[project_lang]}, not found. Using en_US."
  63       source "${HOME}/bin/include/qsgen2/lang/en_US"
  64     fi
  65 
  66 if (${globaldebug}); then
  67   qsconfig=$( cat $(pwd)/config | grep -v \# | awk '{print substr($0, index($0, " ") + 1)}' )
  68         echo "Content of Config file"
  69     for qslines in ${qsconfig}
  70       do
  71       echo "${yellow}${qslines}${end}"
  72     done
  73 fi
  74 
  75 function _msg() {
  76     local type=$1
  77     shift  # Remove the first argument so $@ now contains only keys or additional strings
  78 
  79     local full_msg=""
  80     for arg in "$@"; do
  81         if [[ -n "${qsgenlang[$arg]}" ]]; then
  82             full_msg+="${qsgenlang[$arg]}"
  83         else
  84             full_msg+="$arg"
  85         fi
  86     done
  87 
  88     # Determine the color based on the type
  89     local color="${end}"  # Default to no color if type is unrecognized
  90     case $type in
  91         std) color="${green}" ;;
  92         info) color="${yellow}" ;;
  93         debug) color="${red}" ;;
  94         other) color="${bold_yellow}" ;;
  95         sub) color="${magenta}" ;;
  96         main) color="${white}${green_bg}" ;;
  97     esac
  98 
  99     # Use printf with %b to allow backslash escape interpretation
 100     printf "${color}%b${end}\n" "${full_msg}"
 101 }
 102 
 103 function _version() {
 104   _msg info "_qsgen2_msg_7" "-$(strftime "%Y")"
 105   echo "${yellow}- https://github.com/kekePower/qsgen2/${end}"
 106   _msg info "_qsgen2_msg_8" " '${1} help' " "_qsgen2_msg_8.1"
 107   exit
 108 }
 109 
 110 function _help() {
 111   # This will also be translated some time in the future
 112   echo "This is where I'll write the Help documentation."
 113         exit
 114 }
 115 
 116 if [[ "$1" == "version" || "$1" == "-v" || "$1" == "--version" ]]; then
 117   _version ${0:t}
 118 elif [[ "$1" == "help" || "$1" == "-h" || "$1" == "--help" ]]; then
 119   _help ${0:t}
 120 fi
 121 
 122 # Define cache files for blogs and pages
 123 blog_cache_file="${config[project_root]}/.blog_cache"
 124 pages_cache_file="${config[project_root]}/.pages_cache"
 125 
 126 # Let's check if qsgen2 can generate this site by checking if 'generator' is available
 127 if [[ ! ${config[project_generator]} ]] || [[ -d $(pwd)/.git ]]; then
 128     _msg debug "_qsgen2_msg_3"
 129     exit
 130 fi
 131 
 132 # We define the variable 'engine' based on what's in the 'config' file.
 133 if [[ ${config[project_generator]} == "native" ]]; then
 134   # Usage: ${engine} ${1} - Where 1 is the file you want to convert
 135   engine=_qstags
 136   export file_ext="qst"
 137 elif [[ ${config[project_generator]} == "markdown" ]]; then
 138   if [[ ! -f /usr/local/bin/pandoc ]]; then
 139     _msg other "_qsgen2_msg_4"
 140     _msg other "https://github.com/jgm/pandoc/releases"
 141     exit
 142   else
 143     # Usage: ${engine} ${1} - Where 1 is the file you want parsed
 144     engine="/usr/local/bin/pandoc"
 145     engine_opts=
 146     export file_ext="md"
 147   fi
 148 else
 149   _msg debug "_qsgen2_msg_5"
 150   exit
 151 fi
 152 
 153 function _run_engine() {
 154   # Usage: _run_engine <input>
 155   local debug=false
 156 
 157   if [[ ${config[project_generator]} == "native" ]]; then
 158     ${engine} ${1}
 159   elif [[ ${config[project_generator]} == "markdown" ]]; then
 160     echo "${1} | ${engine} ${engine_opts}"
 161   else
 162     _msg debug "ERROR running engine: ${engine}!"
 163     _msg info "Usage: _run_engine <input>"
 164     exit
 165   fi
 166 }
 167 
 168 if (${globaldebug}); then _msg debug "_qsgen2_msg_6"; fi
 169 
 170 builtin cd ${config[project_root]}
 171 
 172 # Loading Zsh modules
 173 zmodload zsh/files
 174 zmodload zsh/datetime
 175 zmodload zsh/regex
 176 
 177 # Let's put these here for now.
 178 export today=$(strftime "%Y-%m-%d - %T")
 179 export blogdate=$(strftime "%a-%Y-%b-%d")
 180 
 181 # Let's create arrays of all the files we'll be working on
 182 
 183 function _list_pages() {
 184 
 185   if [[ ${globaldebug} == "true" ]]; then
 186     local debug=true
 187   else
 188     local debug=false
 189   fi
 190 
 191   # Initialize or clear the array to ensure it's empty before adding files
 192   pages_file_array=()
 193 
 194   export no_pages_found=false
 195 
 196   # Temporarily set null_glob for this function
 197   setopt local_options null_glob
 198 
 199   # Using an array to directly capture matching files
 200   local -a pages_files=(*.${file_ext})
 201 
 202   if (( ${#pages_files} == 0 )); then
 203     if ${debug}; then _msg debug "${0:t}_msg_1" " ${file_ext}."; fi
 204     export no_pages_found=true
 205     return
 206   else
 207     for file in "${pages_files[@]}"; do
 208       if ${debug}; then _msg debug "${0:t}_msg_2" " ${file}"; fi
 209       pages_file_array+=("$file")
 210     done
 211   fi
 212 
 213 }
 214 
 215 function _list_blogs() {
 216 
 217   if [[ ${globaldebug} == "true" ]]; then
 218     local debug=true
 219   else
 220     local debug=false
 221   fi
 222 
 223   # Initialize or clear the blogs array to ensure it's empty before adding files
 224   blogs_file_array=()
 225 
 226   export no_blogs_found=false
 227 
 228   # Temporarily set null_glob for this function
 229   setopt local_options null_glob
 230 
 231   # Directly capture matching blog files into an array
 232   local -a blog_files=(blog/*.blog(On))
 233 
 234   if (( ${#blog_files[@]} == 0 )); then
 235     if ${debug}; then _msg debug "${0:t}_msg_1"; fi
 236     export no_blogs_found=true
 237     return
 238   else
 239     for file in "${blog_files[@]}"
 240       do
 241         if ${debug}; then _msg debug "${0:t}_msg_2" " $file"; fi
 242           blogs_file_array+=("$file")
 243     done
 244   fi
 245 
 246 }
 247 
 248 
 249 # BLOG CACHE
 250 function _blog_cache() {
 251 
 252   if [[ ${globaldebug} == "true" ]]; then
 253     local debug=true
 254   else
 255     local debug=false
 256   fi
 257 
 258   _list_blogs
 259 
 260   # Create an associative array for the blog cache
 261   typeset -A blog_cache
 262 
 263   # Load the existing blog cache
 264   if [[ -f $blog_cache_file ]]; then
 265     while IFS=':' read -r name hash; do
 266       blog_cache[$name]=$hash
 267         if (${debug}) _msg debug "${0:t}_msg_1" " ${blog_cache[${name}]}"
 268     done < "$blog_cache_file"
 269   fi
 270 
 271   # Initialize the array for storing blog files to process
 272   make_blog_array=()
 273 
 274   # Process blog files
 275   for blog_file in ${blogs_file_array[@]}; do
 276     # Compute the current blog file hash
 277     current_hash=$(md5sum "$blog_file" | awk '{print $1}')
 278 
 279     if (${debug}) _msg debug "${0:t}_msg_2" " ${blog_file}"
 280     if (${debug}) _msg debug "${0:t}_msg_3" " ${current_hash}"
 281 
 282     # Check if the blog file is new or has changed
 283     if [[ ${blog_cache[$blog_file]} != "$current_hash" ]]; then
 284         if (${debug}) _msg debug "${0:t}_msg_4" " ${blog_file}"
 285         if (${debug}) _msg debug "${0:t}_msg_5" " ${current_hash}"
 286       # Blog file is new or has changed; add it to the processing array
 287       make_blog_array+=("$blog_file")
 288 
 289       # Update the blog cache with the new hash
 290       blog_cache[$blog_file]=$current_hash
 291     fi
 292   done
 293 
 294   # Rebuild the blog cache file from scratch
 295   : >| "$blog_cache_file"  # Truncate the file before writing
 296   for name in "${(@k)blog_cache}"; do
 297     echo "$name:${blog_cache[$name]}" >> "$blog_cache_file"
 298   done
 299 
 300 }
 301 
 302 
 303 # PAGES CACHE
 304 # Returns the array pages_array()
 305 function _pages_cache() {
 306 
 307   if [[ ${globaldebug} == "true" ]]; then
 308     local debug=true
 309   else
 310     local debug=false
 311   fi
 312 
 313   # Create an associative array for the pages cache
 314   typeset -A pages_cache
 315 
 316   _list_pages
 317 
 318   # Load the existing pages cache
 319   if [[ -f $pages_cache_file ]]; then
 320     while IFS=':' read -r name hash; do
 321       pages_cache[$name]=$hash
 322         if (${debug}) _msg debug "${0:t}_msg_1" " ${pages_cache[${name}]}"
 323     done < "$pages_cache_file"
 324   fi
 325 
 326   # Initialize the array for storing pages files to process
 327   pages_array=()
 328 
 329   # Process pages files
 330   for file in ${pages_file_array[@]}; do
 331     # Compute the current blog file hash
 332     current_hash=$(md5sum "$file" | awk '{print $1}')
 333 
 334     if (${debug}) _msg debug "${0:t}_msg_2" " ${pages_cache[$file]}"
 335     if (${debug}) _msg debug "${0:t}_msg_3" " current_cache: ${current_hash}"
 336 
 337     # Check if the pages file is new or has changed
 338     if [[ ${pages_cache[$file]} != "$current_hash" ]]; then
 339         if (${debug}) _msg debug "${0:t}_msg_4" " ${pages_cache[$file]}"
 340         if (${debug}) _msg debug "${0:t}_msg_5" " current_cache: ${current_hash}"
 341 
 342       # Pages file is new or has changed; add it to the processing array
 343       pages_array+=("$file")
 344 
 345       # Update the pages cache with the new hash
 346       pages_cache[$file]=$current_hash
 347     fi
 348   done
 349 
 350   # Rebuild the pages cache file from scratch
 351   : >| "$pages_cache_file"  # Truncate the file before writing
 352   for name in "${(@k)pages_cache}"; do
 353     echo "$name:${pages_cache[$name]}" >> "$pages_cache_file"
 354   done
 355 
 356 }
 357 
 358 function _last_updated() {
 359   # This function updates #updated and #version tags in the provided string for buffers
 360 
 361   if [[ ${globaldebug} == "true" ]]; then
 362     local debug=true
 363   else
 364     local debug=false
 365   fi
 366 
 367   local upd_msg="Last updated ${today} by <a href=\"https://blog.kekepower.com/qsgen2.html\">${QSGEN} ${VERSION}</a>"
 368 
 369   if (${debug}); then _msg debug "${0:t}_msg_1"; fi
 370   if (${debug}); then _msg debug "${0:t}_msg_2" " ${upd_msg}"; fi
 371 
 372   local content="${1}"
 373 
 374   # Perform the replacements
 375   local updated_content=$(echo "${content}" | sed \
 376     -e "s|#updated|${upd_msg}|")
 377 
 378   # Return the updated content
 379   echo "${updated_content}"
 380 
 381 }
 382 
 383 function _f_last_updated() {
 384   # Updates #updated and #version tags in the provided file using Zsh
 385 
 386   if [[ ${globaldebug} == "true" ]]; then
 387     local debug=true
 388   else
 389     local debug=false
 390   fi
 391 
 392   # local file_path="${1}"
 393   local upd_msg="Last updated ${today} by <a href=\"https://blog.kekepower.com/qsgen2.html\">${QSGEN} ${VERSION}</a>"
 394 
 395   if ${debug}; then
 396     _msg debug "${0:t}_msg_1" " ${1}"
 397     _msg debug "${0:t}_msg_2" " ${upd_msg}"
 398   fi
 399 
 400   # Read the file content into a variable
 401   local content="$(<${1})"
 402 
 403   # Perform the replacement
 404   content="${content//#updated/${upd_msg}}"
 405 
 406   if [[ -f "${1}" ]]; then
 407     sed -i -e "s|#updated|${upd_msg}|" "${1}"
 408   else
 409     _msg debug "${0:t}_msg_3" " '${1}' " "${0:t}_msg_3.1"
 410   fi
 411 
 412 }
 413 
 414 function _file_to_lower() {
 415 
 416   local filename="${1}"
 417 
 418   # Replace spaces with dashes
 419   filename="${filename// /-}"
 420 
 421   # Convert to lowercase and remove invalid characters
 422   filename=$(echo "${filename}" | sed -e 's/^[^a-zA-Z0-9_.]+//g' -e 's/[^a-zA-Z0-9_-]+/-/g')
 423 
 424   echo ${filename}
 425 
 426 }
 427 
 428 
 429 function _pages() {
 430     # This function generates all the new and updated Pages
 431 
 432   if [[ ${globaldebug} == "true" ]]; then
 433     local debug=true
 434   else
 435     local debug=false
 436   fi
 437 
 438   _msg main "${0:t}_msg_3"
 439 
 440   # Load the cache for Pages
 441   if (${debug}) _msg debug "${0:t}_msg_1"
 442   _pages_cache
 443 
 444   if [[ ${no_pages_found} == "true" ]]; then
 445     _msg sub "${0:t}_msg_1"
 446     return
 447   fi
 448 
 449   if (( ${#pages_array[@]} > 0 )); then
 450 
 451     # If pages_array is not empty, we do work
 452     if (${debug}) _msg debug "${0:t}_msg_4"
 453 
 454     for pages_in_array in ${pages_array[@]}
 455       do
 456         if (${debug}) _msg debug "${0:t}_msg_5"
 457         local pages=${config[project_root]}/themes/${config[site_theme]}/pages.tpl
 458 
 459         # Let's check if we can access the pages.tpl file.
 460         # It not, exit script.
 461         if [[ ! -f ${pages} ]]; then
 462           _msg info "${0:t}_msg_6" " ${pages}"
 463           exit
 464         else
 465           # Read template once
 466           if (${debug}) _msg debug "${0:t}_msg_7"
 467           local pages_tpl="$(<${pages})"
 468         fi
 469 
 470         # _msg std "  - ${pages_in_array%.*}.html"
 471         # Read the file once
 472         if (${debug}) _msg debug "${0:t}_msg_9" " ${pages_in_array}"
 473         local page_content="$(<${pages_in_array})"
 474 
 475         # Grab the title from the Page
 476         if (${debug}) _msg debug "${0:t}_msg_10"
 477         if [[ ${config[project_generator]} == "native" ]]; then
 478           while read -r line
 479             do
 480             if [[ "$line" =~ ^#title=(.*) ]]; then
 481               local page_title=${match[1]}
 482               break
 483               #local page_title=$( echo ${page_content} | head -2 | grep \#title | cut -d= -f2 )
 484             fi
 485           done <<< "$page_content"
 486         elif [[ ${config[project_generator]} == "markdown" ]]; then
 487           while IFS= read -r line
 488             do
 489             # Check if the line starts with '#' and capture the line
 490             if [[ "$line" == \#* ]]; then
 491               # Remove all leading '#' characters and the first space (if present)
 492               local page_title="${line#\#}" # Remove the first '#' character
 493               page_title="${page_title#\#}" # Remove the second '#' character if present
 494               page_title="${page_title#"${page_title%%[![:space:]]*}"}" # Trim leading whitespace
 495               break # Exit the loop after finding the first heading
 496             fi
 497           done <<< ${page_content}
 498         fi
 499         _msg std "  - ${page_title}"
 500         if (${debug}) _msg debug "${0:t}_msg_11" " ${page_title}"
 501 
 502         # Remove the #title line from the buffer. No longer needed.
 503         if (${debug}) _msg debug "${0:t}_msg_12"
 504         page_content=$( echo ${page_content} | grep -v \#title )
 505 
 506         # HTML'ify the page content
 507         if (${debug}) _msg debug "${0:t}_msg_13" " ${pages_in_array}"
 508           page_content=$( _run_engine "$page_content" )
 509           # Look for links, images and videos and convert them if present.
 510           if (${debug}) _msg debug "${0:t}_msg_14"
 511           if [[ $( echo ${page_content} | grep \#link ) ]]; then
 512             if (${debug}) _msg debug "${0:t}_msg_15"
 513             page_content=$( _link "${page_content}" )
 514             fi
 515           if [[ $( echo ${page_content} | grep \#showimg ) ]]; then
 516             if (${debug}) _msg debug "${0:t}_msg_16"
 517             page_content=$( _image "${page_content}" )
 518           fi
 519           if [[ $( echo ${page_content} | grep \#ytvideo ) ]]; then
 520             if (${debug}) _msg debug "${0:t}_msg_17"
 521             page_content=$( _youtube "${page_content}" )
 522           fi
 523 
 524         # Replace every #pagetitle in pages_tpl
 525         if (${debug}) _msg debug "${0:t}_msg_18"
 526         pages_tpl=$(echo "${pages_tpl}" | perl -pe "s|#pagetitle|${page_title}|gs; s|#tagline|${config[site_tagline]}|gs; s|#sitename|${config[site_name]}|gs")
 527 
 528         if (${debug}) _msg debug "${0:t}_msg_19"
 529         # Use awk for multi-line and special character handling
 530         pages_tpl=$( awk -v new_body="$page_content" '{sub(/BODY/, new_body)} 1' <(echo "${pages_tpl}") )
 531 
 532         # Replace #updated with today's date and #version with Name and Version to footer
 533         if (${debug}) _msg debug "${0:t}_msg_20"
 534         pages_tpl=$( _last_updated ${pages_tpl} )
 535 
 536         # Always use lowercase for file names
 537         if (${debug}) _msg debug "${0:t}_msg_21"
 538         pages_title_lower=$( _file_to_lower "${pages_in_array}" )
 539 
 540         # Clean up unused tags, if any
 541         if (${debug}) _msg debug "${0:t}_msg_22"
 542         pages_tpl=$( _cleanup "${pages_tpl}" )
 543 
 544         # Write pages_tpl to disk
 545         # _msg std "Writing ${config[site_root]}/${pages_title_lower%.*}.html to disk."
 546         echo "${pages_tpl}" > ${config[site_root]}/${pages_title_lower%.*}.html
 547 
 548         # Insert the blog to the front page is blog_in_index is true and the file in the array is index.file_ext
 549         # and if index.tmp.html exist and is not empty
 550         if [[ ${pages_in_array} == "index.${file_ext}" &&  ${config[site_blog]} == "true" && -s "${config[project_root]}/blog/index.tmp.html" ]]; then
 551           if (${debug}) _msg sub "${0:t}_msg_23" " ${pages_in_array}"
 552           if (${debug}) _msg sub "${0:t}_msg_24" " ${config[site_blog]}"
 553           if (${debug}) _msg sub "${0:t}_msg_25"
 554           if (${debug}) ls -l ${config[project_root]}/blog/index.tmp.html
 555           _add_blog_list_to_index
 556         fi
 557 
 558       done
 559 
 560       export new_updated_pages=true
 561 
 562     else
 563       # Insert the blog to the front page is blog_in_index is true and the file in the array is index.file_ext
 564       # and if index.tmp.html exist and is not empty
 565       if [[ ${config[site_blog]} == "true" && -s "${config[project_root]}/blog/index.tmp.html" ]]; then
 566         _msg std "${0:t}_msg_26"
 567         if (${debug}) _msg sub "${0:t}_msg_27" " ${pages_in_array}"
 568         if (${debug}) _msg sub "${0:t}_msg_28" " ${config[site_blog]}"
 569         if (${debug}) _msg sub "${0:t}_msg_25"
 570         if (${debug}) ls -l ${config[project_root]}/blog/index.tmp.html
 571         _add_blog_list_to_index
 572       fi
 573 
 574       _msg sub "${0:t}_msg_29"
 575       export new_updated_pages=false
 576 
 577   fi
 578 
 579 }
 580 
 581 function _blogs() {
 582   # This function either generates blog files or exports metadata based on the argument
 583 
 584   if [[ ${globaldebug} == "true" ]]; then
 585     local debug=true
 586   else
 587     local debug=false
 588   fi
 589 
 590   _msg main "${0:t}_msg_3"
 591 
 592   # Running function _list_blogs
 593   if (${debug}) _msg debug "${0:t}_msg_1"
 594   _list_blogs
 595 
 596   if [[ ${no_blogs_found} == "true" ]]; then
 597     _msg sub "${0:t}_msg_2"
 598     return
 599   fi
 600 
 601   # Running function _blog_cache
 602   if (${debug}) _msg debug "${0:t}_msg_4"
 603   _blog_cache
 604 
 605   if (( ${#make_blog_array[@]} > 0 )); then
 606 
 607     # Declare the array to hold metadata strings for each blog
 608     BLOG_META_STR_ARRAY=()
 609 
 610     # Regular blog creation process
 611 
 612     if [[ -f ${config[project_root]}/themes/${config[site_theme]}/blogs.tpl ]]; then
 613       local blog_tpl=$(<"${config[project_root]}/themes/${config[site_theme]}/blogs.tpl")
 614     else
 615       _msg info "${0:t}_msg_5"
 616       exit
 617     fi
 618 
 619     for blog in "${make_blog_array[@]}"; do
 620       if (${debug}) _msg info "*************************************************************************"
 621       if (${debug}) _msg info "**************************FOR LOOP START*********************************"
 622       if (${debug}) _msg info "*************************************************************************"
 623       if (${debug}) _msg debug "${0:t}_msg_6" " ${blog}"
 624 
 625       local content="$(<"${blog}")"
 626       local sdate btitle ingress body blog_index blog_dir blog_url
 627 
 628       # Initialize variables to track if DATE and BLOG_TITLE are found
 629       local date_found=false
 630       local title_found=false
 631 
 632       # Process content line by line
 633       while IFS= read -r line
 634         do
 635         # Check for the DATE line
 636         if [[ "${line}" == "DATE "* ]]; then
 637           if (${debug}) _msg debug "${0:t}_msg_7"
 638           date_found=true
 639         fi
 640         # Check for the BLOG_TITLE line
 641         if [[ "${line}" == "BLOG_TITLE "* ]]; then
 642           if (${debug}) _msg debug "${0:t}_msg_8"
 643           title_found=true
 644         fi
 645         # If both DATE and BLOG_TITLE are found, no need to continue checking
 646         if [[ "${date_found}" == true && "${title_found}" == true ]]; then
 647           break
 648         fi
 649       done <<< "${content}"
 650 
 651       # Check if DATE or BLOG_TITLE metadata is missing and log message
 652       if [[ "${date_found}" == false ]]; then
 653         if (${debug}) _msg debug "${0:t}_msg_9" " ${blog}."
 654         continue  # Skip this file and move to the next
 655       fi
 656       if [[ "${title_found}" == false ]]; then
 657         if (${debug}) _msg debug "${0:t}_msg_10" " ${blog}."
 658         continue  # Skip this file and move to the next
 659       fi
 660 
 661       # Extract blog information
 662       sdate=( $( echo ${content} | grep DATE | sed "s|DATE\ ||" | sed "s|\-|\ |g" ) )
 663       if [[ ${config[project_generator]} == "native" ]]; then
 664       if (${debug}) _msg debug "* qstags: Fetching BLOG_TITLE"
 665         while IFS= read -r line; do
 666           if [[ "$line" == "BLOG_TITLE "* ]]; then
 667             btitle="${line#BLOG_TITLE }"
 668             break
 669           fi
 670         done <<< "$content"
 671       elif [[ ${config[project_generator]} == "markdown" ]]; then
 672       if (${debug}) _msg debug "* markdown: Fetching BLOG_TITLE"
 673         while IFS= read -r line; do
 674           if [[ "$line" == \#* ]]; then
 675             btitle="${line#\#}" # Remove the first '#' character
 676             btitle="${btitle#\#}" # Remove the second '#' character if present
 677             btitle="${btitle#"${btitle%%[![:space:]]*}"}" # Trim leading whitespace
 678             break # Exit the loop after finding the first heading
 679           fi
 680         done <<< "$content"
 681       fi
 682       if (${debug}) _msg debug "* Fetching INGRESS"
 683       ingress=$( echo ${content} | sed "s/'/\\\'/g" | xargs | grep -Po "#INGRESS_START\K(.*?)#INGRESS_STOP" | sed "s|\ \#INGRESS_STOP||" | sed "s|^\ ||" )
 684       if (${debug}) _msg debug "* Fetching BODY"
 685       body=$( echo ${content} | sed "s/'/\\\'/g" | xargs | grep -Po "#BODY_START\K(.*?)#BODY_STOP" | sed "s|\ \#BODY_STOP||" | sed "s|^\ ||" )
 686 
 687       blog_index=$(echo "${btitle:l}" | sed 's/ /_/g; s/,//g; s/\.//g; s/://g; s/[()]//g')
 688 
 689       blog_dir="/blog/${sdate[2]}/${sdate[3]:l}/${sdate[4]}"
 690       blog_url="${blog_dir}/${blog_index}.html"
 691 
 692       if (${debug}) _msg debug "${0:t}_msg_11" " ${blog} " "${0:t}_msg_11.1"
 693 
 694         # Concatenate all metadata into a single string for the current blog
 695         local metadata_str="SDATE: ${sdate[@]}||BTITLE: ${btitle}||INGRESS: ${ingress}||URL: ${blog_url}"
 696         # Append this metadata string to the array
 697         BLOG_META_STR_ARRAY+=("${metadata_str}")
 698 
 699         if (${debug}) _msg debug "${0:t}_msg_12" " ${blog}"
 700 
 701           _msg std "  - ${btitle}"
 702 
 703           # Prepare the blog template
 704           if (${debug}) _msg debug "${0:t}_msg_14" " ${blog}"
 705           local blog_content=$(
 706             echo "${blog_tpl}" | \
 707             perl -pe "\
 708             s|BLOGTITLE|${btitle}|g; \
 709             s|BLOGURL|${blog_url}|g; \
 710             s|\QINGRESS\E|${ingress}|g; \
 711             s|\QBODY\E|${body}|g \
 712             ")
 713             blog_content="${blog_content//CALNDAY/${sdate[4]}}"
 714             blog_content="${blog_content//CALYEAR/${sdate[2]}}"
 715             blog_content="${blog_content//CALMONTH/${sdate[3]}}"
 716             blog_content="${blog_content//CALADAY/${sdate[1]}}"
 717 
 718             if (${debug}) _msg debug "${0:t}_msg_15" " ${engine} " "${0:t}_msg_15_1" " ${blog}"
 719               blog_content=$( _run_engine "${blog_content}" )
 720               # Look for links, images and videos and convert them if present.
 721               if (${debug}) _msg debug "${0:t}_msg_16"
 722               if [[ $( echo ${blog_content} | grep \#link ) ]]; then
 723                 if (${debug}) _msg debug "${0:t}_msg_17"
 724                 blog_content=$(_link "${blog_content}")
 725               fi
 726               if [[ $( echo ${blog_content} | grep \#showimg ) ]]; then
 727                 if (${debug}) _msg debug "${0:t}_msg_18"
 728                 blog_content=$(_image "${blog_content}")
 729               fi
 730               if [[ $( echo ${blog_content} | grep \#ytvideo ) ]]; then
 731                 if (${debug}) _msg debug "${0:t}_msg_19"
 732                 blog_content=$(_youtube "${blog_content}")
 733               fi
 734 
 735             # Replace every #tagline in blog_content
 736             if (${debug}) _msg debug "${0:t}_msg_20"
 737             blog_content=$( echo ${blog_content} | perl -pe "s|#tagline|${config[site_tagline]}|gs; s|#sitename|${config[site_name]}|gs; s|#pagetitle|${page_title}|gs" )
 738 
 739             if (${debug}) _msg debug "* Running _last_updated"
 740             blog_content=$(_last_updated "${blog_content}")
 741             if (${debug}) _msg debug "* Running _cleanup"
 742             blog_content=$(_cleanup "${blog_content}")
 743 
 744             # Create directory if it doesn't exist
 745             if (${debug}) _msg debug "${0:t}_msg_21" " ${config[site_root]}${blog_dir}"
 746             [[ ! -d "${config[site_root]}/${blog_dir}" ]] && mkdir -p "${config[site_root]}/${blog_dir}"
 747 
 748             # Write to file
 749             if (${debug}) _msg debug "${0:t}_msg_22" " ${config[site_root]}${blog_url}"
 750             echo "${blog_content}" > "${config[site_root]}${blog_url}"
 751 
 752             unset sdate btitle ingress body blog_index blog_dir blog_url
 753 
 754     done
 755     # Now BLOG_META_STR_ARRAY contains the metadata string for each blog post
 756     export BLOG_META_STR_ARRAY
 757     if (${debug}) _msg debug "${0:t}_msg_23"
 758     export new_updated_blogs=true
 759 
 760   else
 761     _msg sub "${0:t}_msg_24"
 762     export new_updated_blogs=false
 763   fi
 764 
 765   if [[ ${new_updated_blogs} == "true" ]]; then
 766     if (${debug}) _msg sub "${0:t}_msg_25"
 767     _blog_idx_for_index
 768     if (${debug}) _msg sub "${0:t}_msg_26"
 769     _blog_index
 770   fi
 771 
 772 }
 773 
 774 function _blog_idx_for_index() {
 775   # This function generates the file blog/index.tmp.html
 776 
 777   if [[ ${globaldebug} == "true" ]]; then
 778     local debug=true
 779   else
 780     local debug=false
 781   fi
 782 
 783   _msg sub "${0:t}_msg_1" " ${config[project_root]}/blog/index.tmp.html"
 784 
 785   if (${debug}) _msg debug "${0:t}_msg_2"
 786 
 787   local blog_list_tpl=$(<${config[project_root]}/themes/${config[site_theme]}/blog_list.tpl)
 788   local blog_list_content=""
 789 
 790   # Truncate file before writing new one
 791   : >| "${config[project_root]}/blog/index.tmp.html"
 792 
 793   # if (${debug}) _msg debug "${0:t}_msg_3" " ${BLOG_META_STR_ARRAY[@]}"
 794 
 795   for meta_str in ${BLOG_META_STR_ARRAY[@]}
 796     do
 797       if (${debug}) _msg debug "${0:t}_msg_4"
 798       if (${debug}) _msg debug "${0:t}_msg_5" " ${meta_str}"
 799 
 800       # Split meta_str into individual metadata components
 801       local -a meta_array=("${(@s/||/)meta_str}")
 802 
 803       # Initialize variables to store each component
 804       local sdate btitle ingress url
 805 
 806       # Iterate over each component and extract information
 807       if (${debug}) _msg debug "${0:t}_msg_6"
 808       for component in "${meta_array[@]}"
 809         do
 810           case "${component}" in
 811             SDATE:*) sdate=${component#SDATE: } ;;
 812             BTITLE:*) btitle=${component#BTITLE: } ;;
 813             INGRESS:*) ingress=${component#INGRESS: } ;;
 814             URL:*) url=${component#URL: } ;;
 815           esac
 816 
 817       done
 818 
 819           local adate=( $( echo ${sdate} ) )
 820           local caladay="${adate[1]}"
 821           local calyear="${adate[2]}"
 822           local calmonth="${adate[3]}"
 823           local calnday="${adate[4]}"
 824 
 825           local bdate="${adate[1]} - ${adate[4]}/${adate[3]}/${adate[2]}"
 826           blog_list_content+=$(
 827             echo "${blog_list_tpl}" | \
 828             perl -pe "\
 829             s|BLOGURL|${config[site_url]}${url}|g; \
 830             s|BLOGTITLE|${btitle}|g; \
 831             s|INGRESS|${ingress}|g; \
 832             s|BLOGDATE|${bdate}|g; \
 833             s|CALADAY|${caladay}|g; \
 834             s|CALNDAY|${calnday}|g; \
 835             s|CALMONTH|${calmonth}|g; \
 836             s|CALYEAR|${calyear}|g \
 837             ")
 838 
 839       unset sdate btitle ingress url adate caladay calyear calmonth calnday
 840 
 841   done
 842   if (${debug}) _msg debug "${0:t}_msg_7" " ${engine} " "${0:t}_msg_7.1"
 843   # Catch any QStags or Markdown in the Ingress
 844   blog_list_content=$( _run_engine ${blog_list_content} )
 845   if (${debug}) _msg debug "${0:t}_msg_8" " ${config[project_root]}/blog/index.tmp.html"
 846   #if (${debug}) _msg debug "${0:t}_msg_9" " ${blog_list_content}"
 847   echo ${blog_list_content} > ${config[project_root]}/blog/index.tmp.html
 848 
 849 }
 850 
 851 function _blog_index() {
 852 
 853   if [[ ${globaldebug} == "true" ]]; then
 854     local debug=true
 855   else
 856     local debug=false
 857   fi
 858 
 859   # This function generates the www_root/blog/index.html file that gets its data from _blog_list_for_index()
 860   # ${new_updated_blogs} comes from the function _blogs if anything new or updated is detected
 861   if [[ ${config[site_blog]} == "false" ]] && [[ ${new_updated_blogs} = "true" ]]; then
 862 
 863     if (${debug}) _msg debug "${0:t}_msg_1" "${config[site_blog]}"
 864     if (${debug}) _msg debug "${0:t}_msg_2" "${new_updated_blogs}"
 865     if (${debug}) _msg debug "${0:t}_msg_3"
 866     if (${debug}) _msg debug "${0:t}_msg_4" " ${config[site_blog]}"
 867 
 868     _msg std "${0:t}_msg_5" " ${config[site_root]}/blog/index.html"
 869 
 870     local blog_index_tpl=$(<${config[project_root]}/themes/${config[site_theme]}/blog_index.tpl)
 871     local blog_index_list=$(<${config[project_root]}/blog/index.tmp.html)
 872 
 873     if (${debug}) _msg debug "${0:t}_msg_6"
 874     local blog_index_content=$(echo "${blog_index_tpl}" | perl -pe "s|#sitename|${config[site_name]}|gs; s|#tagline|${config[site_tagline]}|gs")
 875     if (${debug}) _msg debug "${0:t}_msg_7" " ${config[project_root]}/blog/index.tmp.html"
 876     blog_index_content=$( awk -v new_body="$blog_index_list" '{sub(/BODY/, new_body)} 1' <(echo "${blog_index_content}") )
 877 
 878     if (${debug}); then
 879       _msg debug "${0:t}_msg_8" " ${config[site_root]}/blog/index.html"
 880       _msg debug "${0:t}_msg_9" " ${#blog_index_content}"
 881     fi
 882     echo "$blog_index_content" > ${config[site_root]}/blog/index.html
 883     _f_last_updated ${config[site_root]}/blog/index.html
 884 
 885   fi
 886 
 887 }
 888 
 889 function _add_blog_list_to_index() {
 890 
 891   if [[ ${globaldebug} == "true" ]]; then
 892     local debug=true
 893   else
 894     local debug=false
 895   fi
 896 
 897   # Let's find the file 'index.qst' and add the blog if blog_in_index is true
 898   if (${debug}) _msg debug "${0:t}_msg_1"
 899   local blog_index_list=$(<${config[project_root]}/blog/index.tmp.html)
 900   local site_index_file=$(<${config[site_root]}/index.html)
 901   echo "${site_index_file}" | awk -v new_body="${blog_index_list}" '{sub(/BLOGINDEX/, new_body)} 1' > "${config[site_root]}/index.html"
 902 
 903 }
 904 
 905 function _sitemap() {
 906 
 907   if [[ ${globaldebug} == "true" ]]; then
 908     local debug=true
 909   else
 910     local debug=false
 911   fi
 912 
 913   # Check if sitemap is set to true and if there are updated Blogs or Pages before updating the sitemap.xml file.
 914   if ([[ ${config[site_sitemap]} == "true" ]] && ( [[ ${new_updated_blogs} == "true" ]] || [[ ${new_updated_pages} == "true" ]] )) || [[ ${sitemap_force} == "true" ]]; then
 915 
 916     setopt extendedglob
 917 
 918     _msg main "${0:t}_msg_1"
 919 
 920     local sm_file="sitemap.xml"
 921     local b_file="sitemap-blogs.xml"
 922     local p_file="sitemap-pages.xml"
 923     local sitemap_file="${config[site_root]}/${sm_file}"
 924     local sitemap_blog="${config[site_root]}/${b_file}"
 925     local sitemap_page="${config[site_root]}/${p_file}"
 926 
 927     # Find all HTML files and store them in an array
 928     builtin cd ${config[site_root]}
 929     local -a html_files=(**/[a-z]*.html(.))
 930     local -a blog_files=()
 931     local -a page_files=()
 932     for file in "${html_files[@]}"; do
 933       if [[ $file == *blog* ]]; then
 934         blog_files+=("$file")
 935       else
 936         page_files+=("$file")
 937       fi
 938     done
 939 
 940     # Start of the XML file for BLOGS
 941     echo '<?xml version="1.0" encoding="UTF-8"?>' > ${sitemap_blog}
 942     echo "<!-- Sitemap generated by ${QSGEN} ${VERSION} - https://github.com/kekePower/qsgen2 -->" >> ${sitemap_blog}
 943     echo "<?xml-stylesheet type=\"text/xsl\" href=\"${config[site_url]}/css/default-sitemap.xsl?sitemap=page\"?>" >> ${sitemap_blog}
 944     echo '<urlset' >> ${sitemap_blog}
 945     echo '  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' >> ${sitemap_blog}
 946     echo '  xmlns:xhtml="http://www.w3.org/1999/xhtml"' >> ${sitemap_blog}
 947     echo '  xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"' >> ${sitemap_blog}
 948     echo '>' >> ${sitemap_blog}
 949 
 950     # Add each URL to the sitemap
 951     for file in "${blog_files[@]}"
 952       do
 953         # Remove www_root from the path and prepend site_url
 954         local url="${config[site_url]}/${file}"
 955         local lastmod=$(stat -c %y "${file}" 2>/dev/null | cut -d' ' -f1,2 | sed 's/ /T/' | sed 's/\..*$//')
 956 
 957         echo "  <url>" >> ${sitemap_blog}
 958         echo "    <loc>${url}</loc>" >> ${sitemap_blog}
 959         echo "    <lastmod><![CDATA[${lastmod}+01:00]]></lastmod>" >> ${sitemap_blog}
 960         echo "    <changefreq><![CDATA[always]]></changefreq>" >> ${sitemap_blog}
 961         echo "    <priority><![CDATA[1]]></priority>" >> ${sitemap_blog}
 962         echo "  </url>" >> ${sitemap_blog}
 963     done
 964 
 965     # End of the XML file
 966     echo '</urlset>' >> "${sitemap_blog}"
 967     _msg std "  - ${b_file}"
 968 
 969     # Start of the XML file for PAGES
 970     echo '<?xml version="1.0" encoding="UTF-8"?>' > ${sitemap_page}
 971     echo "<!-- Sitemap generated by ${QSGEN} ${VERSION} - https://github.com/kekePower/qsgen2 -->" >> ${sitemap_page}
 972     echo "<?xml-stylesheet type=\"text/xsl\" href=\"${config[site_url]}/css/default-sitemap.xsl?sitemap=page\"?>" >> ${sitemap_page}
 973     echo '<urlset' >> ${sitemap_page}
 974     echo '  xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' >> ${sitemap_page}
 975     echo '  xmlns:xhtml="http://www.w3.org/1999/xhtml"' >> ${sitemap_page}
 976     echo '  xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"' >> ${sitemap_page}
 977     echo '>' >> ${sitemap_page}
 978 
 979     # Add each URL to the sitemap
 980     for file in "${page_files[@]}"
 981       do
 982         # Remove www_root from the path and prepend site_url
 983         local url="${config[site_url]}/${file}"
 984         local lastmod=$(stat -c %y "${file}" 2>/dev/null | cut -d' ' -f1,2 | sed 's/ /T/' | sed 's/\..*$//')
 985 
 986         echo "  <url>" >> ${sitemap_page}
 987         echo "    <loc>${url}</loc>" >> ${sitemap_page}
 988         echo "    <lastmod><![CDATA[${lastmod}+01:00]]></lastmod>" >> ${sitemap_page}
 989         echo "    <changefreq><![CDATA[always]]></changefreq>" >> ${sitemap_page}
 990         echo "    <priority><![CDATA[1]]></priority>" >> ${sitemap_page}
 991         echo "  </url>" >> ${sitemap_page}
 992     done
 993 
 994     # End of the XML file
 995     echo '</urlset>' >> "${sitemap_page}"
 996     _msg std "  - ${p_file}"
 997 
 998     if (${debug}); then _msg debug "${0:t}_msg_2" " ${sitemap_file}"; fi
 999 
1000     # Start of the XML file for the main sitemap
1001     echo '<?xml version="1.0" encoding="UTF-8"?>' > "${sitemap_file}"
1002     echo "<sitemapindex xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">" >> "${sitemap_file}"
1003 
1004     # Add sitemap-blogs.xml to the sitemap
1005     echo "  <sitemap>" >> "${sitemap_file}"
1006     echo "    <loc>${config[site_url]}/${b_file}</loc>" >> "${sitemap_file}"
1007     local lastmod_b=$(stat -c %y "${b_file}" 2>/dev/null | cut -d' ' -f1,2 | sed 's/ /T/' | sed 's/\..*$//')
1008     echo "    <lastmod>${lastmod_b}</lastmod>" >> "${sitemap_file}"
1009     echo "  </sitemap>" >> "${sitemap_file}"
1010 
1011     # Add sitemap-pages.xml to the sitemap
1012     echo "  <sitemap>" >> "${sitemap_file}"
1013     echo "    <loc>${config[site_url]}/${p_file}</loc>" >> "${sitemap_file}"
1014     local lastmod_p=$(stat -c %y "${p_file}" 2>/dev/null | cut -d' ' -f1,2 | sed 's/ /T/' | sed 's/\..*$//')
1015     echo "    <lastmod>${lastmod_p}</lastmod>" >> "${sitemap_file}"
1016     echo "  </sitemap>" >> "${sitemap_file}"
1017 
1018     # End of the XML file
1019     echo "</sitemapindex>" >> "${sitemap_file}"
1020     _msg std "  - ${sm_file}"
1021 
1022     builtin cd ${config[project_root]}
1023 
1024   fi
1025 
1026 }
1027 
1028 function _link() {
1029   # This converts #link tags to actual clickable links in a provided string
1030 
1031   if [[ ${globaldebug} == "true" ]]; then
1032     local debug=true
1033   else
1034     local debug=false
1035   fi
1036 
1037   local content="${1}"
1038   local modified_content=""
1039 
1040   # Process the content line by line
1041   echo "${content}" | while IFS= read -r line; do
1042     if [[ ${line} == *"#link"* ]]; then
1043       if (${debug}) _msg debug "${0:t}_msg_1" " ${line}"
1044 
1045       # Extract the URL and the link text
1046       local url_full=$(echo "${line}" | awk -F'#link ' '{print $2}' | awk -F'¤' '{print $1 "¤" $2}')
1047       local url_dest=$(echo "${url_full}" | awk -F'¤' '{print $1}')
1048       local url_txt=$(echo "${url_full}" | awk -F'¤' '{print $2}')
1049 
1050       if (${debug}) _msg debug "${0:t}_msg_2" " ${url_dest}"
1051       if (${debug}) _msg debug "${0:t}_msg_3" " ${url_txt}"
1052 
1053       # Form the replacement HTML link
1054       local modified_link="<a href=\"${url_dest}\">${url_txt}"
1055       if [[ ${url_dest} =~ ^https?:// ]]; then
1056         # Add external link icon for external URLs
1057         modified_link+="<img class=\"exticon\" alt=\"External site icon\" src=\"/images/ext-black-top.svg\" width=\"12\" />"
1058       fi
1059       modified_link+="</a>"
1060       line=${line//"#link ${url_full}"/${modified_link}}
1061     fi
1062       modified_content+="${line}\n"
1063   done
1064 
1065   # Return the modified content
1066   echo -e "${modified_content}"
1067 
1068 }
1069 
1070 function _image() {
1071     # This replaces #showimg tags with actual HTML img tags in a provided string
1072 
1073   if [[ ${globaldebug} == "true" ]]; then
1074     local debug=true
1075   else
1076     local debug=false
1077   fi
1078 
1079   local content="${1}"
1080   local modified_content=""
1081 
1082   # Process the content line by line
1083   echo "${content}" | while IFS= read -r line; do
1084     if [[ ${line} == *"#showimg"* ]]; then
1085       if (${debug}) _msg debug "${0:t}_msg_1" " ${line}"
1086 
1087       # Extract image link and alt text
1088       local img_link=$(echo "${line}" | awk -F'#showimg ' '{print $2}')
1089       local image=$(echo "${img_link}" | awk -F'¤' '{print $1}')
1090       local img_alt=$(echo "${img_link}" | awk -F'¤' '{print $2}')
1091 
1092       # Determine the source of the image
1093       local real_image=""
1094       if [[ ${image} =~ ^https?:// ]]; then
1095         real_image=${image}
1096       elif [[ ${image} =~ ^\/ ]]; then
1097         real_image=${image}
1098       else
1099         real_image="/images/${image}"
1100       fi
1101 
1102       # Form the replacement HTML image tag
1103       local img_tag="<img src=\"${real_image}\" alt=\"${img_alt}\" width=\"500\" />"
1104       line=${line//"#showimg ${img_link}"/${img_tag}}
1105     fi
1106       modified_content+="${line}\n"
1107   done
1108 
1109   # Return the modified content
1110   echo -e "${modified_content}"
1111 
1112 }
1113 
1114 function _youtube() {
1115   # This embeds a YouTube video in a provided string
1116 
1117   if [[ ${globaldebug} == "true" ]]; then
1118     local debug=true
1119   else
1120     local debug=false
1121   fi
1122 
1123   local content="${1}"
1124   local modified_content=""
1125 
1126   # Process the content line by line
1127   echo "${content}" | while IFS= read -r line; do
1128     if [[ ${line} == *"#ytvideo"* ]]; then
1129       if (${debug}) _msg debug "${0:t}_msg_1" " ${line}"
1130 
1131       # Extract YouTube video ID
1132       local yt_id=$(echo "${line}" | awk -F'#ytvideo ' '{print $2}')
1133 
1134       # Form the replacement YouTube iframe embed
1135       local yt_iframe="<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/${yt_id}\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe>"
1136       line=${line//"#ytvideo ${yt_id}"/${yt_iframe}}
1137     fi
1138     modified_content+="${line}\n"
1139   done
1140 
1141   # Return the modified content
1142   echo -e "${modified_content}"
1143 
1144 }
1145 
1146 function _cleanup() {
1147   # This removes tags used in the templates that may be left over for some reason
1148 
1149   if [[ ${globaldebug} == "true" ]]; then
1150     local debug=true
1151   else
1152     local debug=false
1153   fi
1154 
1155   local content="${1}"
1156 
1157   if (${debug}) _msg debug "${0:t}_msg_1"
1158 
1159   # Perform the cleanup
1160   # -e "s|BLOGINDEX\ ||g"
1161   local cleaned_content=$(echo "${content}" | sed \
1162     -e "s|¤||g" \
1163     -e "s|#showimg\ ||g" \
1164     -e "s|#ytvideo\ ||g" \
1165     -e "s|#link\ ||g" \
1166     )
1167 
1168   # Return the cleaned content
1169   echo "${cleaned_content}"
1170 
1171 }
1172 
1173 function _p_qstags() {
1174 
1175   if [[ ${globaldebug} == "true" ]]; then
1176     local debug=true
1177   else
1178     local debug=false
1179   fi
1180 
1181   local content="${1}"
1182 
1183   if ${debug}; then
1184     _msg debug "${0:t}_msg_1"
1185   fi
1186 
1187   # Use perl to convert QStags to HTML
1188   perl -0777 -pe '
1189     BEGIN {
1190       @qstags = (
1191         "#BR", "<br/>\n",
1192         "#BD", "<b>", "#EBD", "</b>",
1193         "#I", "<i>", "#EI", "</i>\n",
1194         "#P", "<p>", "#EP", "</p>\n",
1195         "#Q", "<blockquote>", "#EQ", "</blockquote>\n",
1196         "#C", "<code>", "#EC", "</code>\n",
1197         "#H1", "<h1>", "#EH1", "</h1>\n",
1198         "#H2", "<h2>", "#EH2", "</h2>\n",
1199         "#H3", "<h3>", "#EH3", "</h3>\n",
1200         "#H4", "<h4>", "#EH4", "</h4>\n",
1201         "#H5", "<h5>", "#EH5", "</h5>\n",
1202         "#H6", "<h6>", "#EH6", "</h6>\n",
1203         "#STRONG", "<strong>", "#ESTRONG", "</strong>\n",
1204         "#EM", "<em>", "#SEM", "</em>\n",
1205         "#DV", "<div>", "#EDV", "</div>\n",
1206         "#SPN", "<span>", "#ESPN", "</span>\n",
1207         "#UL", "<ul>", "#EUL", "</ul>\n",
1208         "#OL", "<ol>", "#EOL", "</ol>\n",
1209         "#LI", "<li>", "#ELI", "</li>\n",
1210         "#UD", "<u>", "#EUD", "</u>\n",
1211         "#TBL", "<table>", "#ETBL", "</table>\n",
1212         "#TR", "<tr>", "#ETR", "</tr>\n",
1213         "#TD", "<td>", "#ETD", "</td>\n",
1214         "#TH", "<th>", "#ETH", "</th>\n",
1215         "#ART", "<article>", "#EART", "</article>\n",
1216         "#SEC", "<section>", "#ESEC", "</section>\n",
1217         "#ASIDE", "<aside>", "#EASIDE", "</aside>\n",
1218         "#NAV", "<nav>", "#ENAV", "</nav>\n",
1219         "#BTN", "<button>", "#EBTN", "</button>\n",
1220         "#SEL", "<select>", "#ESEL", "</select>\n",
1221         "#OPT", "<option>", "#EOPT", "</option>\n",
1222         "#LT", "&lt;", "#GT", "&gt;", "#NUM", "&num;"
1223       );
1224     }
1225 
1226     for (my $i = 0; $i < $#qstags; $i += 2) {
1227         my $qstag = $qstags[$i];
1228         my $html = $qstags[$i + 1];
1229         s/\Q$qstag\E/$html/g;
1230     }
1231     ' <<< "$content"
1232 
1233 }
1234 
1235 function _qstags() {
1236 
1237   # This function uses the regex module from Zsh to parse the QStags
1238 
1239   if [[ ${globaldebug} == "true" ]]; then
1240     local debug=true
1241   else
1242     local debug=false
1243   fi
1244 
1245   local content="${1}"
1246 
1247   if ${debug}; then
1248     _msg debug "${0:t}_msg_1"
1249   fi
1250 
1251   # Load regex module
1252   # zmodload zsh/regex
1253 
1254   # Define tag replacements as an associative array
1255   typeset -A qstags=(
1256     "#BR" "<br/>\n"
1257     "#BD" "<b>" "#EBD" "</b>"
1258     "#I" "<i>" "#EI" "</i>\n"
1259     "#P" "<p>" "#EP" "</p>\n"
1260     "#Q" "<blockquote>" "#EQ" "</blockquote>\n"
1261     "#C" "<code>" "#EC" "</code>\n"
1262     "#H1" "<h1>" "#EH1" "</h1>\n"
1263     "#H2" "<h2>" "#EH2" "</h2>\n"
1264     "#H3" "<h3>" "#EH3" "</h3>\n"
1265     "#H4" "<h4>" "#EH4" "</h4>\n"
1266     "#H5" "<h5>" "#EH5" "</h5>\n"
1267     "#H6" "<h6>" "#EH6" "</h6>\n"
1268     "#STRONG" "<strong>" "#ESTRONG" "</strong>\n"
1269     "#EM" "<em>" "#SEM" "</em>\n"
1270     "#DV" "<div>" "#EDV" "</div>\n"
1271     "#SPN" "<span>" "#ESPN" "</span>\n"
1272     "#UL" "<ul>" "#EUL" "</ul>\n"
1273     "#OL" "<ol>" "#EOL" "</ol>\n"
1274     "#LI" "<li class=\"libody\">" "#ELI" "</li>\n"
1275     "#UD" "<u>" "#EUD" "</u>\n"
1276     "#TBL" "<table>" "#ETBL" "</table>\n"
1277     "#TR" "<tr>" "#ETR" "</tr>\n"
1278     "#TD" "<td>" "#ETD" "</td>\n"
1279     "#TH" "<th>" "#ETH" "</th>\n"
1280     "#ART" "<article>" "#EART" "</article>\n"
1281     "#SEC" "<section>" "#ESEC" "</section>\n"
1282     "#ASIDE" "<aside>" "#EASIDE" "</aside>\n"
1283     "#NAV" "<nav>" "#ENAV" "</nav>\n"
1284     "#BTN" "<button>" "#EBTN" "</button>\n"
1285     "#SEL" "<select>" "#ESEL" "</select>\n"
1286     "#OPT" "<option>" "#EOPT" "</option>\n"
1287     "#LT" "&lt;" "#GT" "&gt;" "#NUM" "&num;"
1288     )
1289 
1290     #for qstag html (${(kv)qstags})
1291     #  do
1292     #    # Escape tag for regex use
1293     #    local escapedTag=$(printf '%s' "$qstag" | sed 's/[].\[^$*]/\\&/g')
1294     #    if [[ "$content" =~ "$escapedTag" ]]; then
1295     #        content=${content//($qstag)/$html}
1296     #    fi
1297     #done
1298     for qstag html (${(kv)qstags}); do
1299       # Direct replacement without regex check
1300       content=${content//${qstag}/${html}}
1301     done
1302 
1303     echo "${content}"
1304 
1305 }
1306 
1307 
1308 case ${1} in
1309     force)
1310         _msg sub "_qsgen2_msg_2"
1311         : >| "$blog_cache_file"  # Truncate the blog cache before doing update
1312         : >| "$pages_cache_file"  # Truncate the page cache before doing update
1313     ;;
1314     sitemap)
1315         _msg sub "Updating sitemaps"
1316         export sitemap_force=true
1317         _sitemap
1318         exit
1319     ;;
1320     *)
1321         # Nothing
1322     ;;
1323 esac
1324 
1325 _blogs
1326 _pages
1327 _sitemap