1use crate::error::Result;
2use crate::model::{
3 CandidateRecord, CollectorsOutput, DigOutput, ItemKind, LibraryOutput, ResolveOutput,
4};
5
6pub enum OutputFormat {
7 Table,
8 Json,
9 Csv,
10}
11
12pub fn print_resolve(output: &ResolveOutput, format: OutputFormat) -> Result<()> {
13 match format {
14 OutputFormat::Json => println!("{}", serde_json::to_string_pretty(output)?),
15 OutputFormat::Table | OutputFormat::Csv => {
16 println!("Platform: {}", output.seed.platform.as_str());
17 println!(
18 "Type : {}",
19 match output.seed.kind {
20 ItemKind::Album => "album",
21 ItemKind::Track => "track",
22 ItemKind::Playlist => "playlist",
23 }
24 );
25 println!("Title : {}", output.seed.title);
26 println!("Artist : {}", output.seed.artist);
27 println!("URL : {}", output.seed.url);
28 if !output.seed.tags.is_empty() {
29 println!("Tags : {}", output.seed.tags.join(", "));
30 }
31 }
32 }
33 Ok(())
34}
35
36pub fn print_collectors(output: &CollectorsOutput, format: OutputFormat) -> Result<()> {
37 match format {
38 OutputFormat::Json => println!("{}", serde_json::to_string_pretty(output)?),
39 OutputFormat::Csv => {
40 let mut writer = csv::Writer::from_writer(std::io::stdout());
41 writer.write_record(["handle", "url", "display_name"])?;
42 for collector in &output.collectors {
43 writer.write_record([
44 collector.handle.as_str(),
45 collector.url.as_str(),
46 collector.display_name.as_deref().unwrap_or(""),
47 ])?;
48 }
49 writer.flush()?;
50 }
51 OutputFormat::Table => {
52 println!("Seed: {} - {}", output.seed.artist, output.seed.title);
53 println!("Collectors discovered: {}", output.collectors_discovered);
54 for collector in &output.collectors {
55 println!(
56 "- {} {}",
57 collector.handle,
58 collector.display_name.as_deref().unwrap_or("")
59 );
60 }
61 }
62 }
63 Ok(())
64}
65
66pub fn print_library(output: &LibraryOutput, format: OutputFormat) -> Result<()> {
67 match format {
68 OutputFormat::Json => println!("{}", serde_json::to_string_pretty(output)?),
69 OutputFormat::Csv => {
70 let mut writer = csv::Writer::from_writer(std::io::stdout());
71 writer.write_record(["artist", "album", "url"])?;
72 for album in &output.albums {
73 writer.write_record([
74 album.artist.as_str(),
75 album.title.as_str(),
76 album.url.as_str(),
77 ])?;
78 }
79 writer.flush()?;
80 }
81 OutputFormat::Table => {
82 println!("Collector: {}", output.collector_url);
83 for album in &output.albums {
84 println!("- {} - {}", album.artist, album.title);
85 }
86 }
87 }
88 Ok(())
89}
90
91pub fn print_dig(output: &DigOutput, format: OutputFormat) -> Result<()> {
92 match format {
93 OutputFormat::Json => println!("{}", serde_json::to_string_pretty(output)?),
94 OutputFormat::Csv => print_results_csv(&output.results)?,
95 OutputFormat::Table => print_results_table(output),
96 }
97 Ok(())
98}
99
100fn print_results_csv(results: &[CandidateRecord]) -> Result<()> {
101 let mut writer = csv::Writer::from_writer(std::io::stdout());
102 writer.write_record([
103 "rank",
104 "artist",
105 "album",
106 "url",
107 "overlap_count",
108 "overlap_ratio",
109 "score",
110 "reason",
111 ])?;
112 for result in results {
113 writer.write_record([
114 result.rank.to_string(),
115 result.artist.clone(),
116 result.title.clone(),
117 result.url.clone(),
118 result.overlap_count.to_string(),
119 format!("{:.4}", result.overlap_ratio),
120 format!("{:.2}", result.score),
121 result.reason.clone(),
122 ])?;
123 }
124 writer.flush()?;
125 Ok(())
126}
127
128fn print_results_table(output: &DigOutput) {
129 println!("Seed: {} - {}", output.seed.artist, output.seed.title);
130 println!(
131 "Collectors: discovered={} sampled={} scanned={} skipped={}",
132 output.summary.collectors_discovered,
133 output.summary.collectors_sampled,
134 output.summary.collectors_scanned,
135 output.summary.collectors_skipped
136 );
137 println!();
138 println!(
139 "{:<4} {:<24} {:<28} {:>8} {:>8} {:>8} Reason",
140 "#", "Artist", "Album", "Overlap", "Pct", "Score"
141 );
142 for item in &output.results {
143 println!(
144 "{:<4} {:<24} {:<28} {:>8} {:>7.1}% {:>8.2} {}",
145 item.rank,
146 truncate(&item.artist, 24),
147 truncate(&item.title, 28),
148 item.overlap_count,
149 item.overlap_ratio * 100.0,
150 item.score,
151 item.reason
152 );
153 }
154}
155
156fn truncate(value: &str, width: usize) -> String {
157 let mut chars = value.chars();
158 let collected: String = chars.by_ref().take(width).collect();
159 if chars.next().is_some() && width > 1 {
160 format!("{}…", collected.chars().take(width - 1).collect::<String>())
161 } else {
162 collected
163 }
164}