update_metadata_in_posts :: (posts: [..]Markdown_File) { re_pattern_datetime=, ok := re.compile(PATTERN_RE_DATETIME); assert(ok); posts_updated: [..]string; for posts { metadata, post := entries_make_header_and_body(it.content); updated_metadata := update_metadata(metadata); joined := join_metadata_and_body(updated_metadata, post); array_add(*posts_updated, joined); } assert(posts_updated.count == posts.count, "This should not happen!"); for posts_updated { fp := posts[it_index].fn; file_write_or_exit(fp, it); log("> Updated: %", fp); } } is_correct_field_date_format :: (value: string) -> bool { matched, captures := re.match(value, re_pattern_datetime); this_allocation_is_not_a_leak(captures.data); return matched; } #scope_file re_pattern_datetime: re.Regexp; Input_Kind :: enum { EMPTY; MS_MAYBE; DATE_TIME; DATE_ONLY; DT_NOW; ALREADY_PARSED; } /** Note: If there are multiple occurrences of 'published' or 'updated' it will use the last found item. */ update_metadata :: (metadata: string) -> string { metadata_trimmed := trim(metadata); meta := split(metadata_trimmed, "\n"); published_count := count_occurrences_in_field(meta, PATTERN_PUBLISHED); updated_count := count_occurrences_in_field(meta, PATTERN_UPDATED); if published_count > 1 then log_metadata_error_and_exit(metadata, PATTERN_PUBLISHED, published_count); if updated_count> 1 then log_metadata_error_and_exit(metadata, PATTERN_UPDATED, updated_count); buf: [..]string; for meta { normalized := trim(it); if starts_with(normalized, PATTERN_PUBLISHED) { add_time_to_array(*buf, normalized, PATTERN_PUBLISHED); } else if starts_with(normalized, PATTERN_UPDATED) { add_time_to_array(*buf, normalized, PATTERN_UPDATED); } else { array_add(*buf, normalized); } } if !published_count then add_current_date_and_ms(*buf, PATTERN_PUBLISHED); if !updated_count then add_current_date_and_ms(*buf, PATTERN_UPDATED); s := join(..buf, "\n", after_last=true,, temp); return copy_string(s); } log_metadata_error_and_exit :: (metadata: string, pattern: string, counter: int) { log_error( "Field pattern `%` was found % time(s) in the following header:", pattern, counter ); log_error("-- Metadata Preview -----------\n%", slice(metadata, 0, 255)); exit(1); } count_occurrences_in_field :: (metadata: []string, pattern: string) -> count: int { counter: int; for metadata { trimmed := trim(it); if starts_with(trimmed, pattern) then counter += 1; } return counter; } join_metadata_and_body :: (metadata: string, body: string) -> string { return join(metadata, METADATA_MARKER, "\n\n", body); } make_metadata_field :: (pattern: string, value: string, ms: string) -> string { return tprint("%%(%)", pattern, value, ms); } add_time_to_array :: (buf: *[..]string, value: string, $pattern: string) { if is_correct_field_date_format(value) { array_add(buf, value); return; } value, ms := convert_input_to_ms(value, pattern.count); metadata_field := make_metadata_field(pattern, value, ms); array_add(buf, metadata_field); } convert_input_to_ms :: ( source: string, pattern_length: int ) -> (value: string, ms: string) { ms: int; value: string; sliced := slice(source, pattern_length, source.count); trimmed := trim(sliced); input_kind := determine_input_kind(trimmed); if #complete input_kind == { case .EMPTY; ms = current_time_as_ms(); value = milliseconds_to_date(ms); case .MS_MAYBE; ms = parse_to_int(trimmed); value = milliseconds_to_date(ms); case .DATE_TIME; ms = date_to_milliseconds(trimmed); value = trimmed; case .DATE_ONLY; ms = date_to_milliseconds(trimmed); value = trimmed; case .DT_NOW; ms = current_time_as_ms(); value = milliseconds_to_date(ms); case .ALREADY_PARSED; return "", ""; } ms_as_string := tprint("%", ms); return value, ms_as_string; } add_current_date_and_ms :: (buf: *[..]string, pattern: string) { ms := current_time_as_ms(); ms_as_string := tprint("%", ms); value := milliseconds_to_date(ms); metadata_field := make_metadata_field(pattern, value, ms_as_string); array_add(buf, metadata_field); } determine_input_kind :: (value: string) -> (kind: Input_Kind = .EMPTY) { kind: Input_Kind; if !value || is_whole_thing_whitespace(value) { return; } else if is_number_format(value) { kind = .MS_MAYBE; } else if is_date_time_format(value, PARSER_FORMAT_DATETIME_DELIMITER_COUNT) { kind = .DATE_TIME; } else if is_date_time_format(value, PARSER_FORMAT_DATE_DELIMITER_COUNT) { kind = .DATE_ONLY; } else if is_current_dt_format(value) { kind = .DT_NOW; } else { assert(false, "You shouldn't be here!"); } return kind; } current_time_as_ms :: () -> int { now := current_time_consensus(); ms := to_milliseconds(now); return ms; } date_to_milliseconds :: (s: string) -> int { ct: Calendar_Time; date := split(s, "-"); if date.count != 3 && date.count != 5 { log_error("Date string must have 3 or 5 items"); exit(1); } for date if it_index == { case 0; ct.year = parse_to_int(it, s32); case 1; ct.month_starting_at_0 = parse_to_int(it, s8) - 1; case 2; ct.day_of_month_starting_at_0 = parse_to_int(it, s8) - 1; case 3; ct.hour = parse_to_int(it, s8); case 4; ct.minute = parse_to_int(it, s8); } ct.time_zone = .UTC; apollo := calendar_to_apollo(ct); ms := to_milliseconds(apollo); return ms; } milliseconds_to_date :: (ms: int) -> string { a := milliseconds_to_apollo(ms); cal := to_calendar(a, .UTC); year := tprint("%", cal.year); month := string_pad_left( tprint("%", cal.month_starting_at_0 + 1), 2, "0" ); day := string_pad_left( tprint("%", cal.day_of_month_starting_at_0 + 1), 2, "0" ); joined := join(year, month, day, separator="-"); return joined; } is_whole_thing_whitespace :: (s: string) -> bool { for s if !is_space(it) return false; return true; } is_number_format :: (s: string) -> bool { for s if !is_digit(it) return false; return true; } is_date_time_format :: (value: string, $delimiter_count: int) -> bool { count_is: int; for value if it == PARSER_FORMAT_DT_DELIMITER then count_is += 1; return delimiter_count == count_is; } is_current_dt_format :: (value: string) -> bool { return value == METADATA_CMD_CURRENT_DT; }