spotify_cli/io/formatters/
show.rs

1//! Show (podcast) formatting functions
2
3use serde_json::Value;
4
5use crate::io::common::{DurationFormat, format_duration_as, print_table, truncate};
6
7pub fn format_show_detail(payload: &Value) {
8    let name = payload
9        .get("name")
10        .and_then(|v| v.as_str())
11        .unwrap_or("Unknown");
12    let publisher = payload
13        .get("publisher")
14        .and_then(|v| v.as_str())
15        .unwrap_or("Unknown");
16    let description = payload
17        .get("description")
18        .and_then(|v| v.as_str())
19        .unwrap_or("");
20    let total_episodes = payload
21        .get("total_episodes")
22        .and_then(|v| v.as_u64())
23        .unwrap_or(0);
24    let explicit = payload
25        .get("explicit")
26        .and_then(|v| v.as_bool())
27        .unwrap_or(false);
28    let uri = payload.get("uri").and_then(|v| v.as_str()).unwrap_or("");
29
30    println!("{}", name);
31    println!("  Publisher: {}", publisher);
32    if !description.is_empty() {
33        let desc = if description.len() > 200 {
34            format!("{}...", &description[..200])
35        } else {
36            description.to_string()
37        };
38        println!("  Description: {}", desc);
39    }
40    println!("  Total Episodes: {}", total_episodes);
41    if explicit {
42        println!("  Explicit: Yes");
43    }
44    if !uri.is_empty() {
45        println!("  URI: {}", uri);
46    }
47}
48
49pub fn format_shows(items: &[Value], message: &str) {
50    println!("{}:", message);
51    println!();
52
53    let rows: Vec<Vec<String>> = items
54        .iter()
55        .enumerate()
56        .map(|(i, item)| {
57            // Handle both direct show objects and wrapped {"show": ...} objects
58            let show = item.get("show").unwrap_or(item);
59            let name = show
60                .get("name")
61                .and_then(|v| v.as_str())
62                .unwrap_or("Unknown");
63            let publisher = show
64                .get("publisher")
65                .and_then(|v| v.as_str())
66                .unwrap_or("Unknown");
67            let episodes = show
68                .get("total_episodes")
69                .and_then(|v| v.as_u64())
70                .unwrap_or(0);
71            vec![
72                (i + 1).to_string(),
73                truncate(name, 30),
74                truncate(publisher, 20),
75                episodes.to_string(),
76            ]
77        })
78        .collect();
79
80    print_table(
81        "Shows",
82        &["#", "Name", "Publisher", "Episodes"],
83        &rows,
84        &[3, 30, 20, 8],
85    );
86}
87
88pub fn format_show_episodes(items: &[Value], message: &str) {
89    println!("{}:", message);
90    println!();
91
92    let rows: Vec<Vec<String>> = items
93        .iter()
94        .enumerate()
95        .map(|(i, item)| {
96            let name = item
97                .get("name")
98                .and_then(|v| v.as_str())
99                .unwrap_or("Unknown");
100            let duration_ms = item
101                .get("duration_ms")
102                .and_then(|v| v.as_u64())
103                .unwrap_or(0);
104            let duration = format_duration_as(duration_ms, DurationFormat::Long);
105            let release = item
106                .get("release_date")
107                .and_then(|v| v.as_str())
108                .unwrap_or("");
109            vec![
110                (i + 1).to_string(),
111                truncate(name, 35),
112                duration,
113                release.to_string(),
114            ]
115        })
116        .collect();
117
118    print_table(
119        "Episodes",
120        &["#", "Name", "Duration", "Released"],
121        &rows,
122        &[3, 35, 10, 12],
123    );
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use serde_json::json;
130
131    #[test]
132    fn format_show_detail_full() {
133        let payload = json!({
134            "name": "Test Podcast",
135            "publisher": "Test Publisher",
136            "description": "A great podcast about testing",
137            "total_episodes": 100,
138            "explicit": true,
139            "uri": "spotify:show:abc123"
140        });
141        format_show_detail(&payload);
142    }
143
144    #[test]
145    fn format_show_detail_minimal() {
146        let payload = json!({});
147        format_show_detail(&payload);
148    }
149
150    #[test]
151    fn format_show_detail_long_description() {
152        let long_desc = "A".repeat(300);
153        let payload = json!({
154            "name": "Podcast",
155            "publisher": "Publisher",
156            "description": long_desc,
157            "total_episodes": 50
158        });
159        format_show_detail(&payload);
160    }
161
162    #[test]
163    fn format_show_detail_not_explicit() {
164        let payload = json!({
165            "name": "Family Podcast",
166            "publisher": "Family",
167            "explicit": false
168        });
169        format_show_detail(&payload);
170    }
171
172    #[test]
173    fn format_shows_with_items() {
174        let items = vec![
175            json!({
176                "name": "Podcast One",
177                "publisher": "Publisher A",
178                "total_episodes": 150
179            }),
180            json!({
181                "name": "Podcast Two",
182                "publisher": "Publisher B",
183                "total_episodes": 75
184            }),
185        ];
186        format_shows(&items, "Your Podcasts");
187    }
188
189    #[test]
190    fn format_shows_empty() {
191        let items: Vec<Value> = vec![];
192        format_shows(&items, "No Podcasts");
193    }
194
195    #[test]
196    fn format_shows_wrapped() {
197        let items = vec![json!({
198            "show": {
199                "name": "Wrapped Podcast",
200                "publisher": "Publisher",
201                "total_episodes": 50
202            }
203        })];
204        format_shows(&items, "Saved Shows");
205    }
206
207    #[test]
208    fn format_show_episodes_with_items() {
209        let items = vec![
210            json!({
211                "name": "Episode One",
212                "duration_ms": 3600000,
213                "release_date": "2024-01-15"
214            }),
215            json!({
216                "name": "Episode Two",
217                "duration_ms": 1800000,
218                "release_date": "2024-01-08"
219            }),
220        ];
221        format_show_episodes(&items, "Recent Episodes");
222    }
223
224    #[test]
225    fn format_show_episodes_empty() {
226        let items: Vec<Value> = vec![];
227        format_show_episodes(&items, "No Episodes");
228    }
229
230    #[test]
231    fn format_show_episodes_minimal() {
232        let items = vec![json!({})];
233        format_show_episodes(&items, "Episodes");
234    }
235}