/** Static html generator & search server. Copyright (C) 2026 dev@ptrace.dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #import "Basic"()(MEMORY_DEBUGGER = MEMORY_DEBUGGER_ENABLED); #import "File"; #import "File_Utilities"; #import "String"; #import "Sort"; #import "Print_Vars"; #import "POSIX"; #import "stb_image"; #import "stb_image_write"; #import "stb_image_resize"; #import "stringpad"; #import "htmltemplate"; #import "cmark-gfm"; re :: #import "uniform"; #load "constants.h"; #load "../marshal.jai"; #load "parser.jai"; #load "replace.jai"; #load "entries.jai"; #load "blog.jai"; #load "pages.jai"; #load "update.jai"; /** This projects uses the convention of 'header' files. Which are unusual for Jai. But since we have more than one program in the same project, it is easier to share structs and constants. The important bit is, that only indepentend objects can live there, with no reference to the other program. Every header file must be self-contained. Example: This is fine to include. Foo :: struct { a, b: int; } This is bad to include. Foo :: struct { a: int; b: My_Custom_Type; } Except you include `My_Custom_Type` too, which has no other dependencies! */ /** About memory: The current memory footprint is very small. Like ~0.2 MB RAM leaked, while processing ~0.3 MB of HTML documents. I do not expect to ever reach high numbers, so we can make use of the OS GC. */ /** TODO: The deployment to prod of blog posts and binary changes is quite annoying. Need a more streamlined approach so we have less friction. There are two workflows. 1. Deploy posts - Generate site: jai build.jai - run silent gen - Refresh search index: jai build.jai - run silent search :: store - Deploy: dev/push.py prod 2. Change something on the page - Generate site: jai build.jai - run silent gen - Refresh search index: jai build.jai - run silent search :: store - Rebuild index binary: jai build.jai - silent search release - Deploy: dev/push.py exe dev/push.py prod - SSH into server: service.sh restart */ nav_bar: [..]Navbar_Item; pages: [..]Page; pages_generated: [..]string; main :: () { #if MEMORY_DEBUGGER_ENABLED defer report_memory_leaks(); args := get_command_line_arguments(); if arg_find("help", "-h", "-help", "--help", "?") { log(ARGS_HELP); return; } defer print("\n"); create_project_directories(); /** Update headers */ posts_to_update := posts_open(DIR_POSTS); update_metadata_in_posts(posts_to_update); generic_pages_to_update := posts_open(DIR_GENERIC); update_metadata_in_posts(generic_pages_to_update); /** Generate files */ posts := posts_open(DIR_POSTS); entries := entries_make(posts); entries_raw_md := array_copy(entries); annotations_filter(*entries); markdown_generate(*entries); /** 2026.04.05 Currently, `entries_sorted_by_published` is not really used - except in `page_entries_to_disk` where the order does not matter. We might need it laterâ„¢ */ entries_sorted_by_update, entries_sorted_by_published := entries_sort(entries); rss := page_rss(entries_sorted_by_update); html_sorted_by_update_diet, // aka no images supplied, only links html_sorted_by_published := entries_make_html(entries_sorted_by_update, entries_sorted_by_published); page_entries_to_disk(entries_sorted_by_published, html_sorted_by_published); page_entries(html_sorted_by_update_diet); page_entries_limited(html_sorted_by_update_diet); page_overview(html_sorted_by_update_diet); page_overview_limited(html_sorted_by_update_diet); page_privacy(); page_search(); page_search_for_server(); /** If any operation above fails, it will exit and not write to file. */ entries_dump_data_or_exit(entries_raw_md, FP_DUMP_ENTRIES_MD); entries_dump_data_or_exit(entries, FP_DUMP_ENTRIES_HTML); copy_files_from_to(FILES_TO_COPY); generate_rss(rss); generate_pages(); if !pages_generated log("No pages generated."); for pages_generated log("> Generated: %", it); } #scope_file ARGS_HELP :: #string STR_END Options: -h, --help This help menu. STR_END; arg_find :: (words: ..string) -> bool #expand { for words if array_find(`args, it) return true; return false; }