#![allow(clippy::print_stdout)]
use serde::Serialize;
use serde_json::Value;
#[allow(clippy::expect_used)]
pub fn print_json<T: Serialize>(value: &T, fields: Option<&[String]>) {
let json_value = serde_json::to_value(value).expect("value must be serializable to JSON");
let output = match fields {
Some(field_list) => {
if field_list.is_empty() {
json_value
} else {
let mut filtered = serde_json::Map::new();
if let Value::Object(map) = &json_value {
for field in field_list {
if let Some(val) = map.get(field) {
filtered.insert(field.clone(), val.clone());
}
}
}
Value::Object(filtered)
}
}
None => json_value,
};
let pretty = serde_json::to_string_pretty(&output).expect("value must be pretty-printable");
println!("{pretty}");
}
#[must_use]
pub fn format_date(iso_date: &str) -> String {
let date_part = iso_date.get(..10).unwrap_or(iso_date);
let parts: Vec<&str> = date_part.split('-').collect();
if parts.len() != 3 {
return iso_date.to_string();
}
let year = parts[0];
let month: u32 = parts[1].parse().unwrap_or(0);
let day = parts[2];
let month_name = match month {
1 => "Jan",
2 => "Feb",
3 => "Mar",
4 => "Apr",
5 => "May",
6 => "Jun",
7 => "Jul",
8 => "Aug",
9 => "Sep",
10 => "Oct",
11 => "Nov",
12 => "Dec",
_ => return iso_date.to_string(),
};
let day_stripped = day.trim_start_matches('0');
format!("{month_name} {day_stripped}, {year}")
}
#[must_use]
pub fn format_count(n: u64) -> String {
let s = n.to_string();
let mut result = String::with_capacity(s.len() + s.len() / 3);
for (i, c) in s.chars().enumerate() {
if i > 0 && (s.len() - i) % 3 == 0 {
result.push(',');
}
result.push(c);
}
result
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn print_json_full_output() {
let data = json!({"name": "hello-world", "stars": 42, "forks": 7});
print_json(&data, None::<&[String]>);
}
#[test]
fn print_json_with_fields() {
let data = json!({"name": "hello-world", "stars": 42, "forks": 7});
let fields = vec!["name".to_string(), "stars".to_string()];
print_json(&data, Some(&fields));
}
#[test]
fn print_json_with_empty_fields() {
let data = json!({"name": "hello-world", "stars": 42});
let fields: Vec<String> = vec![];
print_json(&data, Some(&fields));
}
#[test]
fn print_json_with_missing_field() {
let data = json!({"name": "hello-world"});
let fields = vec!["name".to_string(), "missing".to_string()];
print_json(&data, Some(&fields));
}
#[test]
fn format_date_standard() {
assert_eq!(format_date("2024-01-15T10:30:00Z"), "Jan 15, 2024");
}
#[test]
fn format_date_december() {
assert_eq!(format_date("2023-12-25T00:00:00Z"), "Dec 25, 2023");
}
#[test]
fn format_date_single_digit_day() {
assert_eq!(format_date("2024-03-05T12:00:00Z"), "Mar 5, 2024");
}
#[test]
fn format_date_invalid() {
assert_eq!(format_date("not-a-date"), "not-a-date");
}
#[test]
fn format_date_empty() {
assert_eq!(format_date(""), "");
}
#[test]
fn format_date_bad_month() {
assert_eq!(format_date("2024-13-01"), "2024-13-01");
}
#[test]
fn format_count_zero() {
assert_eq!(format_count(0), "0");
}
#[test]
fn format_count_small() {
assert_eq!(format_count(42), "42");
}
#[test]
fn format_count_thousands() {
assert_eq!(format_count(1_234), "1,234");
}
#[test]
fn format_count_millions() {
assert_eq!(format_count(1_000_000), "1,000,000");
}
#[test]
fn format_count_billions() {
assert_eq!(format_count(1_234_567_890), "1,234,567,890");
}
#[test]
fn format_count_ten_thousands() {
assert_eq!(format_count(10_000), "10,000");
}
#[test]
fn format_count_hundred_thousands() {
assert_eq!(format_count(100_000), "100,000");
}
}