crates/terminal-pet/src/main.rs
85 lines · 2804 bytes
1//! # terminal-pet
2//!
3//! A pet whose mood follows the shell and the machine. Like every renderer it
4//! is thin: connect to the socket, read frames, redraw. It never touches a
5//! sensor and never reads the shell.
6//!
7//! Usage:
8//! ```text
9//! terminal-pet [--socket <path>]
10//! terminal-pet --help | --version
11//! ```
12//! Socket defaults to `$XDG_RUNTIME_DIR/signald.sock`, falling back to
13//! `~/.local/state/signald/sock`.
14
15use std::collections::BTreeMap;
16use std::path::{Path, PathBuf};
17
18use signal_schema::Signal;
19use terminal_pet::{pet_from_signals, render};
20
21const USAGE: &str = "\
22Usage: terminal-pet [--socket <path>]
23
24 --socket <path> signald's socket (default $XDG_RUNTIME_DIR/signald.sock,
25 else ~/.local/state/signald/sock)
26 -h, --help print this and exit
27 -V, --version print the version and exit
28";
29
30fn main() {
31 let socket = parse_socket();
32 if let Err(e) = run(&socket) {
33 eprintln!("terminal-pet: could not read signald at {}: {e}", socket.display());
34 eprintln!("terminal-pet: is signald running? (signald --socket {})", socket.display());
35 std::process::exit(1);
36 }
37}
38
39/// Connect and render live. Frames this build cannot decode are skipped by the
40/// iterator, so a metric appended after this renderer was built does not end
41/// the stream.
42fn run(socket: &Path) -> std::io::Result<()> {
43 let frames = signal_client::Frames::connect(socket)?;
44 // Keyed by (name, source) so a metric and the health of the collector that
45 // produced it stay distinct, matching the daemon's own cache.
46 let mut latest: BTreeMap<(u8, u8), Signal> = BTreeMap::new();
47 for sig in frames {
48 let sig = sig?;
49 latest.insert((sig.name.to_u8(), sig.source.to_u8()), sig);
50 let snapshot: Vec<Signal> = latest.values().cloned().collect();
51 let pet = pet_from_signals(&snapshot, now_millis());
52 print!("\x1b[2J\x1b[H{}", render(&pet));
53 }
54 Ok(())
55}
56
57fn now_millis() -> u64 {
58 std::time::SystemTime::now()
59 .duration_since(std::time::UNIX_EPOCH)
60 .map(|d| d.as_millis() as u64)
61 .unwrap_or(0)
62}
63
64fn parse_socket() -> PathBuf {
65 let mut args = std::env::args().skip(1);
66 while let Some(arg) = args.next() {
67 match arg.as_str() {
68 "--help" | "-h" => {
69 print!("{USAGE}");
70 std::process::exit(0);
71 }
72 "--version" | "-V" => {
73 println!("terminal-pet {}", env!("CARGO_PKG_VERSION"));
74 std::process::exit(0);
75 }
76 "--socket" => {
77 if let Some(path) = args.next() {
78 return PathBuf::from(path);
79 }
80 }
81 _ => {}
82 }
83 }
84 signal_client::default_socket_path()
85}