1use crate::analyze::{Analysis, ComponentAnalysis, ComponentStatus, ComponentType};
4use crate::error::{Error, Result};
5use comfy_table::{
6 Attribute, Cell, CellAlignment, Color, ContentArrangement, Table, presets::UTF8_FULL,
7};
8use std::io::Write;
9use std::str::FromStr;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum OutputFormat {
14 #[default]
16 Human,
17 Json,
19 None,
21}
22
23impl FromStr for OutputFormat {
24 type Err = Error;
25
26 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
27 match s.to_lowercase().as_str() {
28 "human" => Ok(Self::Human),
29 "json" => Ok(Self::Json),
30 "none" => Ok(Self::None),
31 _ => Err(Error::InvalidOutputFormat(s.to_string())),
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub enum OutputSort {
39 #[default]
41 Type,
42 Name,
44 Status,
46}
47
48impl FromStr for OutputSort {
49 type Err = Error;
50
51 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
52 match s.to_lowercase().as_str() {
53 "type" => Ok(Self::Type),
54 "name" => Ok(Self::Name),
55 "status" => Ok(Self::Status),
56 _ => Err(Error::InvalidOutputSort(s.to_string())),
57 }
58 }
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct OutputConfig {
64 pub format: OutputFormat,
66 pub sort: OutputSort,
68}
69
70impl OutputConfig {
71 pub fn new(format: OutputFormat, sort: OutputSort) -> Self {
73 Self { format, sort }
74 }
75}
76
77pub fn output_analysis<W: Write>(
79 analysis: &Analysis,
80 config: &OutputConfig,
81 writer: &mut W,
82) -> Result<()> {
83 match config.format {
84 OutputFormat::Human => output_human(analysis, config, writer),
85 OutputFormat::Json => output_json(analysis, writer),
86 OutputFormat::None => Ok(()),
87 }
88}
89
90fn output_json<W: Write>(analysis: &Analysis, writer: &mut W) -> Result<()> {
92 serde_json::to_writer_pretty(&mut *writer, analysis)?;
93 writeln!(writer).map_err(Error::OutputFailed)?;
94 Ok(())
95}
96
97fn output_human<W: Write>(
99 analysis: &Analysis,
100 config: &OutputConfig,
101 writer: &mut W,
102) -> Result<()> {
103 let mut table = Table::new();
104 table
105 .load_preset(UTF8_FULL)
106 .set_content_arrangement(ContentArrangement::Dynamic)
107 .set_header(vec![
108 Cell::new("Type").add_attribute(Attribute::Bold),
109 Cell::new("Name").add_attribute(Attribute::Bold),
110 Cell::new("Version").add_attribute(Attribute::Bold),
111 Cell::new("Latest").add_attribute(Attribute::Bold),
112 Cell::new("Status").add_attribute(Attribute::Bold),
113 ]);
114
115 let no_plugins = ComponentAnalysis {
117 component_type: ComponentType::Plugin,
118 name: "-".to_string(),
119 version: "-".to_string(),
120 latest_version: "-".to_string(),
121 status: ComponentStatus::NotDetected,
122 };
123
124 let mut components: Vec<&ComponentAnalysis> = Vec::new();
126 components.push(&analysis.wordpress);
127 components.push(&analysis.theme);
128 if analysis.plugins.is_empty() {
129 components.push(&no_plugins);
130 } else {
131 for component in analysis.plugins.values() {
132 components.push(component);
133 }
134 }
135
136 let type_order = |t: ComponentType| -> u8 {
138 match t {
139 ComponentType::Core => 0,
140 ComponentType::Theme => 1,
141 ComponentType::Plugin => 2,
142 }
143 };
144
145 match config.sort {
147 OutputSort::Type => {
149 components.sort_by(|a, b| {
150 type_order(a.component_type)
151 .cmp(&type_order(b.component_type))
152 .then_with(|| a.name.cmp(&b.name))
153 });
154 }
155 OutputSort::Name => {
157 components.sort_by(|a, b| a.name.cmp(&b.name));
158 }
159 OutputSort::Status => {
161 components.sort_by(|a, b| {
162 b.status
163 .cmp(&a.status)
164 .then_with(|| type_order(a.component_type).cmp(&type_order(b.component_type)))
165 .then_with(|| a.name.cmp(&b.name))
166 });
167 }
168 }
169
170 for component in components {
172 add_component_row(&mut table, component);
173 }
174
175 writeln!(writer, "{}", table).map_err(Error::OutputFailed)
176}
177
178fn add_component_row(table: &mut Table, component: &ComponentAnalysis) {
180 let status_cell = match component.status {
181 ComponentStatus::Ok => Cell::new("Ok")
182 .fg(Color::Green)
183 .set_alignment(CellAlignment::Center),
184 ComponentStatus::Outdated => Cell::new("Outdated")
185 .fg(Color::Yellow)
186 .set_alignment(CellAlignment::Center),
187 ComponentStatus::Unknown => Cell::new("Unknown")
188 .fg(Color::DarkGrey)
189 .set_alignment(CellAlignment::Center),
190 ComponentStatus::NotDetected => Cell::new("Not Found")
191 .fg(Color::DarkGrey)
192 .set_alignment(CellAlignment::Center),
193 };
194
195 table.add_row(vec![
196 Cell::new(component.component_type.to_string()),
197 Cell::new(&component.name),
198 Cell::new(&component.version),
199 Cell::new(&component.latest_version),
200 status_cell,
201 ]);
202}