friend 0.1.0

A little CLI that gives you a word of encouragement.
use std::fs;
use std::io::{self, Write};
use std::path::PathBuf;
use std::process::ExitCode;

use owo_colors::{OwoColorize, Style, Stream};
use rand::seq::IndexedRandom;

const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");

const BUILTIN: &[&str] = &[
    "You are doing better than you think.",
    "One small step today is still forward.",
    "The work you're doing matters.",
    "You belong here. Keep going.",
    "Hard does not mean impossible.",
    "Your future self is already grateful.",
    "Progress over perfection, always.",
    "You are allowed to rest and still be enough.",
    "Today's mess is tomorrow's mastery.",
    "You've survived 100% of your worst days so far.",
    "Be patient with yourself. You're growing.",
    "Done is better than perfect.",
    "Take the next small step. That's all.",
    "You are not behind. There is no schedule.",
    "Curiosity is a kind of courage.",
    "Trust the process you're building.",
    "Your kindness ripples further than you'll ever see.",
    "Bugs are puzzles. You like puzzles.",
    "Ship it. You can iterate.",
    "Whatever you're carrying today, you can set it down for a moment.",
    "You bring something to the room that no one else can.",
    "Slow is smooth. Smooth is fast.",
    "It's okay to ask for help. That's also strength.",
    "You're allowed to change your mind.",
    "The first draft is supposed to be rough.",
    "You don't have to do it all at once.",
    "Breathe. Then begin.",
    "Tiny consistent effort beats heroic bursts.",
    "Your work has value, even when no one's watching.",
    "You're more prepared than you feel.",
    "If it were easy, everyone would do it. You're doing it.",
    "Mistakes mean you're trying. Keep trying.",
    "Today doesn't have to be perfect to be good.",
    "Someone out there is rooting for you. (Hi.)",
    "Small wins count. Stack them up.",
    "You are not your worst moment.",
    "Keep your standards high and your self-talk kind.",
    "Compounding works on effort, too.",
    "Be the friend to yourself you'd be to someone you love.",
    "Whatever happens next, you'll figure it out. You always do.",
];

fn user_messages_path() -> Option<PathBuf> {
    if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME").filter(|v| !v.is_empty()) {
        return Some(PathBuf::from(xdg).join("friend").join("messages.txt"));
    }
    let home = std::env::var_os("HOME").filter(|v| !v.is_empty())?;
    Some(
        PathBuf::from(home)
            .join(".config")
            .join("friend")
            .join("messages.txt"),
    )
}

fn load_user_messages() -> Vec<String> {
    let Some(path) = user_messages_path() else {
        return Vec::new();
    };
    let Ok(contents) = fs::read_to_string(&path) else {
        return Vec::new();
    };
    contents
        .lines()
        .map(str::trim)
        .filter(|l| !l.is_empty() && !l.starts_with('#'))
        .map(str::to_owned)
        .collect()
}

fn pick_message<'a>(builtin: &'a [&'a str], user: &'a [String]) -> &'a str {
    let mut pool: Vec<&str> = builtin.iter().copied().collect();
    pool.extend(user.iter().map(String::as_str));
    let mut rng = rand::rng();
    pool.choose(&mut rng).copied().unwrap_or("You've got this.")
}

fn print_help() {
    let path_hint = user_messages_path()
        .map(|p| p.display().to_string())
        .unwrap_or_else(|| "<config dir not found>".to_string());

    println!(
        "{NAME} {VERSION}
A little CLI that gives you a word of encouragement.

USAGE:
    {NAME} [OPTIONS]

OPTIONS:
    -h, --help       Print this help and exit
    -V, --version    Print version and exit

CUSTOM MESSAGES:
    Add your own lines (one per line, # for comments) to:
        {path_hint}
    They'll be mixed into the random pool alongside the built-ins."
    );
}

fn main() -> ExitCode {
    for arg in std::env::args().skip(1) {
        match arg.as_str() {
            "-h" | "--help" => {
                print_help();
                return ExitCode::SUCCESS;
            }
            "-V" | "--version" => {
                println!("{NAME} {VERSION}");
                return ExitCode::SUCCESS;
            }
            other => {
                eprintln!("{NAME}: unknown option `{other}` (try --help)");
                return ExitCode::from(2);
            }
        }
    }

    let user = load_user_messages();
    let message = pick_message(BUILTIN, &user);

    let style = Style::new().bold().yellow();
    let prefix_style = Style::new().bright_magenta();

    let mut out = io::stdout().lock();
    let _ = writeln!(
        out,
        "{} {}",
        "".if_supports_color(Stream::Stdout, |t| t.style(prefix_style)),
        message.if_supports_color(Stream::Stdout, |t| t.style(style))
    );

    ExitCode::SUCCESS
}