spotify_cli/output/
mod.rs

1//! Output formatting for human and JSON modes.
2use crate::domain::album::Album;
3use crate::domain::artist::Artist;
4use crate::domain::auth::{AuthScopes, AuthStatus};
5use crate::domain::cache::CacheStatus;
6use crate::domain::device::Device;
7use crate::domain::player::PlayerStatus;
8use crate::domain::playlist::{Playlist, PlaylistDetail};
9use crate::domain::pin::PinnedPlaylist;
10use crate::domain::search::SearchResults;
11use crate::domain::settings::Settings;
12use crate::error::Result;
13
14pub mod cache;
15pub mod human;
16pub mod json;
17pub mod pin;
18pub mod settings;
19
20/// Output mode for CLI responses.
21#[derive(Debug, Clone, Copy)]
22pub enum OutputMode {
23    Human,
24    Json,
25}
26
27pub const DEFAULT_MAX_WIDTH: usize = 48;
28
29/// Table rendering configuration for human output.
30#[derive(Debug, Clone, Copy)]
31pub struct TableConfig {
32    pub max_width: Option<usize>,
33    pub truncate: bool,
34}
35
36/// Unified output facade for CLI commands.
37#[derive(Debug, Clone)]
38pub struct Output {
39    mode: OutputMode,
40    user_name: Option<String>,
41    table: TableConfig,
42}
43
44impl Output {
45    pub fn new(
46        json: bool,
47        user_name: Option<String>,
48        max_width: Option<usize>,
49        no_trunc: bool,
50    ) -> Self {
51        let mode = if json { OutputMode::Json } else { OutputMode::Human };
52        let table = TableConfig {
53            max_width,
54            truncate: !no_trunc,
55        };
56        Self {
57            mode,
58            user_name,
59            table,
60        }
61    }
62
63    pub fn auth_status(&self, status: AuthStatus) -> Result<()> {
64        match self.mode {
65            OutputMode::Human => human::auth_status(status),
66            OutputMode::Json => json::auth_status(status),
67        }
68    }
69
70    pub fn auth_scopes(&self, scopes: AuthScopes) -> Result<()> {
71        match self.mode {
72            OutputMode::Human => human::auth_scopes(scopes),
73            OutputMode::Json => json::auth_scopes(scopes),
74        }
75    }
76
77    pub fn player_status(&self, status: PlayerStatus) -> Result<()> {
78        match self.mode {
79            OutputMode::Human => human::player_status(status),
80            OutputMode::Json => json::player_status(status),
81        }
82    }
83
84    pub fn now_playing(&self, status: PlayerStatus) -> Result<()> {
85        match self.mode {
86            OutputMode::Human => human::now_playing(status),
87            OutputMode::Json => json::now_playing(status),
88        }
89    }
90
91    pub fn search_results(&self, results: SearchResults) -> Result<()> {
92        match self.mode {
93            OutputMode::Human => human::search_results(results, self.table),
94            OutputMode::Json => json::search_results(results),
95        }
96    }
97
98    pub fn cache_status(&self, status: CacheStatus) -> Result<()> {
99        match self.mode {
100            OutputMode::Human => cache::status_human(status),
101            OutputMode::Json => cache::status_json(status),
102        }
103    }
104
105    pub fn action(&self, event: &str, message: &str) -> Result<()> {
106        match self.mode {
107            OutputMode::Human => human::action(message),
108            OutputMode::Json => json::action(event, message),
109        }
110    }
111
112    pub fn album_info(&self, album: Album) -> Result<()> {
113        match self.mode {
114            OutputMode::Human => human::album_info(album, self.table),
115            OutputMode::Json => json::album_info(album),
116        }
117    }
118
119    pub fn artist_info(&self, artist: Artist) -> Result<()> {
120        match self.mode {
121            OutputMode::Human => human::artist_info(artist),
122            OutputMode::Json => json::artist_info(artist),
123        }
124    }
125
126    pub fn playlist_list(&self, playlists: Vec<Playlist>) -> Result<()> {
127        match self.mode {
128            OutputMode::Human => {
129                human::playlist_list(playlists, self.user_name.as_deref(), self.table)
130            }
131            OutputMode::Json => json::playlist_list(playlists),
132        }
133    }
134
135    pub fn playlist_list_with_pins(
136        &self,
137        playlists: Vec<Playlist>,
138        pins: Vec<PinnedPlaylist>,
139    ) -> Result<()> {
140        match self.mode {
141            OutputMode::Human => {
142                human::playlist_list_with_pins(
143                    playlists,
144                    pins,
145                    self.user_name.as_deref(),
146                    self.table,
147                )
148            }
149            OutputMode::Json => json::playlist_list_with_pins(playlists, pins),
150        }
151    }
152
153    pub fn playlist_info(&self, playlist: PlaylistDetail) -> Result<()> {
154        match self.mode {
155            OutputMode::Human => human::playlist_info(playlist, self.user_name.as_deref()),
156            OutputMode::Json => json::playlist_info(playlist),
157        }
158    }
159
160    pub fn device_list(&self, devices: Vec<Device>) -> Result<()> {
161        match self.mode {
162            OutputMode::Human => human::device_list(devices, self.table),
163            OutputMode::Json => json::device_list(devices),
164        }
165    }
166
167    pub fn settings(&self, settings: Settings) -> Result<()> {
168        match self.mode {
169            OutputMode::Human => settings::settings_human(settings),
170            OutputMode::Json => settings::settings_json(settings),
171        }
172    }
173
174    pub fn pin_list(&self, pins: Vec<PinnedPlaylist>) -> Result<()> {
175        match self.mode {
176            OutputMode::Human => pin::pin_list_human(pins, self.table),
177            OutputMode::Json => pin::pin_list_json(pins),
178        }
179    }
180
181    pub fn help(&self) -> Result<()> {
182        match self.mode {
183            OutputMode::Human => human::help(),
184            OutputMode::Json => json::help(),
185        }
186    }
187}