//! Shared formatting utilities used across TUI screens and commands. use crate::currency::Currency; /// Format a revenue amount in cents. Returns the bare symbol and "0" for zero. /// /// Takes the currency the amount is denominated in, because there is no such /// thing as a bare amount: the same integer is different money to a US and a /// British creator. Pass the currency the *number* came with — per-project /// revenue carries its own — falling back to the viewer's settlement currency /// only where the server sends no currency alongside the figure. pub(crate) fn format_cents(cents: i64, currency: Currency) -> String { if cents == 0 { return format!("{}0", currency.symbol()); } let sign = if cents < 0 { "-" } else { "" }; let abs = cents.unsigned_abs(); format!("{sign}{}{}.{:02}", currency.symbol(), abs / 100, abs % 100) } /// Format an item price in cents. Returns "Free" for zero. /// /// See [`format_cents`] on why the currency is not optional. A creator's prices /// are always in their own settlement currency. pub(crate) fn format_price(cents: i32, currency: Currency) -> String { if cents == 0 { return "Free".to_string(); } format_cents(i64::from(cents), currency) } /// Human-readable creator tier label. pub(crate) fn format_tier(tier: &str) -> &str { match tier { "basic" => "Basic", "small_files" => "Small Files", "big_files" => "Big Files", "everything" => "Everything", _ => tier, } } /// Human-readable project type label. pub(crate) fn format_project_type(pt: &str) -> &str { match pt { "software" => "Software", "music" => "Music", "blog" => "Blog", "video" => "Video", "audio" => "Audio", "art" => "Art", "writing" => "Writing", "education" => "Education", other => other, } } /// Human-readable item type label. pub(crate) fn format_item_type(it: &str) -> &str { match it { "audio" => "Audio", "text" => "Text", "video" => "Video", "image" => "Image", "plugin" => "Plugin", "preset" => "Preset", "sample" => "Sample", "course" => "Course", "template" => "Template", "digital" => "Digital", "bundle" => "Bundle", other => other, } } #[cfg(test)] mod tests { use super::*; use crate::currency::Currency; #[test] fn format_cents_zero() { assert_eq!(format_cents(0, Currency::Usd), "$0"); } #[test] fn format_cents_positive() { assert_eq!(format_cents(999, Currency::Usd), "$9.99"); assert_eq!(format_cents(100, Currency::Usd), "$1.00"); assert_eq!(format_cents(1050, Currency::Usd), "$10.50"); } #[test] fn format_cents_carries_the_sign() { // A refunded period is negative revenue, and dropping the minus turns a // loss into a gain on screen. assert_eq!(format_cents(-1050, Currency::Usd), "-$10.50"); assert_eq!(format_cents(-5, Currency::Gbp), "-\u{a3}0.05"); } #[test] fn format_cents_uses_the_currencys_own_symbol() { assert_eq!(format_cents(1050, Currency::Gbp), "\u{a3}10.50"); assert_eq!(format_cents(1050, Currency::Cad), "CA$10.50"); assert_eq!(format_cents(1050, Currency::Eur), "\u{20ac}10.50"); assert_eq!(format_cents(0, Currency::Nzd), "NZ$0"); } #[test] fn format_price_free() { // Free is free in every currency; no symbol belongs on it. assert_eq!(format_price(0, Currency::Usd), "Free"); assert_eq!(format_price(0, Currency::Gbp), "Free"); } #[test] fn format_price_nonzero() { assert_eq!(format_price(550, Currency::Usd), "$5.50"); assert_eq!(format_price(1299, Currency::Usd), "$12.99"); assert_eq!(format_price(1299, Currency::Aud), "A$12.99"); } #[test] fn format_tier_known() { assert_eq!(format_tier("basic"), "Basic"); assert_eq!(format_tier("small_files"), "Small Files"); assert_eq!(format_tier("everything"), "Everything"); } #[test] fn format_tier_unknown() { assert_eq!(format_tier("custom"), "custom"); } #[test] fn format_project_type_known() { assert_eq!(format_project_type("software"), "Software"); assert_eq!(format_project_type("music"), "Music"); } #[test] fn format_item_type_known() { assert_eq!(format_item_type("audio"), "Audio"); assert_eq!(format_item_type("plugin"), "Plugin"); } #[test] fn format_item_type_unknown() { assert_eq!(format_item_type("other_thing"), "other_thing"); } }