package main import "base:runtime" import "core:fmt" import "core:strings" import "core:time" generate_rss :: proc(site: ^Site) -> string { sb := strings.builder_make() fmt.sbprintf( &sb, ` %s %s/ %s en-us `, xml_escape(site.title), site.base_url, xml_escape(site.description), site.base_url, ) for page in site.pages { if page.section == "" && page._is_index { continue } pub_date := "Mon, 01 Jan 0001 00:00:00 +0000" if page.date != "" { pub_date = format_rfc822(page.date) } fmt.sbprintf( &sb, ` %s %s %s %s %s `, xml_escape(page.title), page.url, pub_date, page.url, xml_escape(page.content), ) } strings.write_string(&sb, "\n") return strings.to_string(sb) } generate_sitemap :: proc(site: ^Site) -> string { sb := strings.builder_make() strings.write_string( &sb, ` `, ) for page in site.pages { fmt.sbprintf(&sb, "%s", page.url) if page.date != "" { fmt.sbprintf(&sb, "%s", page.date) } fmt.sbprintf(&sb, "\n") } // Section index pages (for sections without an index in content) sections := make(map[string]bool) defer delete(sections) for page in site.pages { if page.section != "" && !page._is_index { sections[page.section] = true } } for section in sections { has_index := false for page in site.pages { if page.section == section && page._is_index { has_index = true break } } if has_index { continue } section_lastmod := "" for page in site.pages { if page.section == section && !page._is_index && page.date > section_lastmod { section_lastmod = page.date } } fmt.sbprintf(&sb, "%s/%s/", site.base_url, section) if section_lastmod != "" { fmt.sbprintf(&sb, "%s", section_lastmod) } fmt.sbprintf(&sb, "\n") } strings.write_string(&sb, "") return strings.to_string(sb) } format_rfc822 :: proc(iso: string, allocator := context.temp_allocator) -> string { if len(iso) < 19 { // TODO: should indicate error somehow return iso } date, offset, _ := time.iso8601_to_time_and_offset(iso) weekday := fmt.tprintf("%s", time.weekday(date)) month := fmt.tprintf("%s", time.month(date)) buf: [8]byte t := time.to_string_hms(date, buf[:]) return fmt.aprintf( "%s, %02d %s %d %s %3d%2d", weekday[:3], time.day(date), month[:3], time.year(date), t, offset / 60, offset % 60, allocator = allocator, ) } // xml_escape escapes the XML metacharacters '&', '<' and '>' in s. Only these // ASCII characters are touched; all other bytes (including UTF-8 multibyte // sequences) pass through verbatim, so s must already be valid UTF-8. // // The result is allocated in `allocator`, which defaults to // context.temp_allocator — the returned string is only valid until the next // temp allocator reset, so callers must not retain it across frames. xml_escape :: proc( s: string, allocator := context.temp_allocator, ) -> ( escaped: string, err: runtime.Allocator_Error, ) #optional_allocator_error { sb := strings.builder_make_len_cap(0, len(s), allocator) or_return // Single pass over the bytes, copying runs of ordinary text in bulk and // emitting an entity for each metacharacter. The switch is both the // membership test and the replacement lookup, so no per-match scan. start := 0 for i := 0; i < len(s); i += 1 { esc: string switch s[i] { case '&': esc = "&" case '<': esc = "<" case '>': esc = ">" case: continue // ordinary byte: copied as part of the next run } strings.write_string(&sb, s[start:i]) // run before the metacharacter strings.write_string(&sb, esc) start = i + 1 } strings.write_string(&sb, s[start:]) // tail return strings.to_string(sb), nil }