litime 1.0.2

A command line tool to display the current time ish with a literature quote
Documentation
import datetime as dt
import html
import json
import os
from collections import defaultdict

times = defaultdict(list)
# folder containing XX:XX.json files
path_to_files = "/Users/axel/repos/literature-clock/docs/times"

TEMPLATE = """/// DO NOT MODIFY
/// THIS FILE IS GENERATED BY RUNNING ./script.py FROM REPO ROOT

use anyhow::{{bail, Context, Result}};
use rand::seq::SliceRandom;

pub struct Minute<'a> {{
    pub title: &'a str,
    pub author: &'a str,
    pub start: &'a str,
    pub time: &'a str,
    pub end: &'a str,
}}

{minutes}

pub fn get_minute<'a>(time: &str, sfw: bool) -> Result<&'a Minute> {{
    let options = match (time, sfw) {{
{matches}
        (_, _) => bail!("Couldn't match timestamp!"),
    }};

    let quote = options.choose(&mut rand::thread_rng()).context("Unable to choose a random quote")?;

    Ok(quote)
}}
"""

REPLACES = [
    ('"', '\\"'),
    ("<br>", "\\n"),
    ("<br/>", "\\n"),
    ("<br />", "\\n"),
    ("\\n ", "\\n"),
]


def prep(section: str) -> str:
    value = html.unescape(section)
    for from_str, to_str in REPLACES:
        value = value.replace(from_str, to_str)

    return value


malformed_timestamps = []

for json_file in sorted(os.listdir(path_to_files)):
    with open(os.path.join(path_to_files, json_file), "r") as f:
        timestamp = json_file.split(".")[0].replace("_", ":")
        for d in sorted(json.load(f), key=lambda q: q["title"]):
            times[timestamp].append(
                {
                    "start": prep(d["quote_first"]),
                    "time": prep(d["quote_time_case"]),
                    "end": prep(d["quote_last"]),
                    "title": prep(d["title"]),
                    "author": prep(d["author"]),
                    "sfw": d["sfw"] == "yes",
                }
            )


minutes = []
matches = []


def get_next_ts(ts: str) -> str:
    """Return a timestamp for minute later

    For example:
        00:13 -> 00:14
        00:59 -> 01:00
        23:59 -> 00:00
    """
    dummy_dt = dt.datetime.fromisoformat(f"2000-01-01T{ts}") + dt.timedelta(minutes=1)
    return dummy_dt.strftime("%H:%M")


matches_pre_process = []

for timestamp, quotes in times.items():
    var_names = []
    sfw_var_names = []
    for idx, quote in enumerate(quotes):
        var_name = f"QUOTE_{timestamp.replace(':', '_')}_{idx}"
        var_names.append(f"&{var_name}")
        if quote["sfw"]:
            sfw_var_names.append(f"&{var_name}")

        minutes.append(
            f"static {var_name}: Minute = Minute"
            "{"
            f"title: \"{quote['title']}\", "
            f"author: \"{quote['author']}\", "
            f"start: \"{quote['start']}\", "
            f"time: \"{quote['time']}\", "
            f"end: \"{quote['end']}\""
            "};",
        )

    # Check if we need to fill future gaps
    timestamps = [timestamp]
    timestamp = get_next_ts(timestamp)
    while timestamp not in times:
        timestamps.append(timestamp)
        timestamp = get_next_ts(timestamp)
    joined_timestamps = '" | "'.join(timestamps)

    if len(var_names) == len(sfw_var_names):
        # We only have SFW quotes, so just point timestamps at all quotes

        joined_varnames = ", ".join(var_names)

        # matches.append(f'        ("{joined_timestamps}", _) => vec![{joined_varnames}],')
        matches_pre_process.append((joined_timestamps, "_", joined_varnames))
    elif len(sfw_var_names) == 0:
        # We have no SFW quotes. Add the quotes for NSFW and then add the timestamp to previous step for SFW
        # Pop off the last value added
        previous_joined_timestamps, sfw, varnames = matches_pre_process.pop()
        if sfw == "true":
            # We can just add it
            # Add all the timestamps to the previous one
            previous_joined_timestamps += '" | "' + joined_timestamps

            # Push it back on
            matches_pre_process.append((previous_joined_timestamps, sfw, varnames))
        elif sfw == "_":
            # We have to split it
            # Push on nsfw to be the normal
            matches_pre_process.append((previous_joined_timestamps, "false", varnames))

            # Push on new sfw to be normal + new
            previous_joined_timestamps += '" | "' + joined_timestamps
            matches_pre_process.append((previous_joined_timestamps, "true", varnames))
            pass
        else:
            raise Exception("Last preprocessed was nsfw??")

        # And then deal with nsfw as normal
        joined_varnames = ", ".join(var_names)
        matches_pre_process.append((joined_timestamps, "false", joined_varnames))
    else:
        # It's a mix

        # Point at all var names for NSFW
        joined_varnames = ", ".join(var_names)
        # matches.append(f'        ("{joined_timestamps}", false) => vec![{joined_varnames}],')
        matches_pre_process.append((joined_timestamps, "false", joined_varnames))

        # Point at the SFW var names for SFW
        joined_sfw_varnames = ", ".join(sfw_var_names)
        # matches.append(f'        ("{joined_timestamps}", true) => vec![{joined_sfw_varnames}],')
        matches_pre_process.append((joined_timestamps, "true", joined_sfw_varnames))

# Turn the preprocessed list into proper entries
for (joined_timestamps, sfw, varnames) in matches_pre_process:
    matches.append(f'        ("{joined_timestamps}", {sfw}) => vec![{varnames}],')

with open("./src/minute.rs", "w") as f:
    f.write(TEMPLATE.format(minutes="\n".join(minutes), matches="\n".join(matches)))