1+ //! Report command implementations
2+
3+ use anyhow:: Result ;
4+ use std:: path:: PathBuf ;
5+ use code_guardian_storage:: SqliteStorage ;
6+ use code_guardian_output:: formatters:: { JsonFormatter , TextFormatter , HtmlFormatter , MarkdownFormatter } ;
7+ use crate :: utils:: get_db_path;
8+
9+ pub struct ReportCommand {
10+ pub id : Option < i64 > ,
11+ pub format : String ,
12+ pub db : Option < PathBuf > ,
13+ pub output : Option < PathBuf > ,
14+ }
15+
16+ impl ReportCommand {
17+ pub fn new ( format : String ) -> Self {
18+ Self {
19+ id : None ,
20+ format,
21+ db : None ,
22+ output : None ,
23+ }
24+ }
25+
26+ pub fn with_id ( mut self , id : i64 ) -> Self {
27+ self . id = Some ( id) ;
28+ self
29+ }
30+
31+ pub fn with_output ( mut self , output : PathBuf ) -> Self {
32+ self . output = Some ( output) ;
33+ self
34+ }
35+
36+ pub async fn execute ( & self ) -> Result < ( ) > {
37+ let db_path = get_db_path ( self . db . clone ( ) ) ;
38+ let storage = SqliteStorage :: new ( & db_path) ?;
39+
40+ let results = if let Some ( id) = self . id {
41+ vec ! [ storage. get_scan_results( id) . await ?]
42+ } else {
43+ // Get latest scan results
44+ storage. get_latest_scan_results ( 1 ) . await ?
45+ } ;
46+
47+ if results. is_empty ( ) {
48+ println ! ( "No scan results found" ) ;
49+ return Ok ( ( ) ) ;
50+ }
51+
52+ let formatted_output = match self . format . as_str ( ) {
53+ "json" => {
54+ let formatter = JsonFormatter :: new ( ) ;
55+ formatter. format ( & results[ 0 ] ) ?
56+ }
57+ "html" => {
58+ let formatter = HtmlFormatter :: new ( ) ;
59+ formatter. format ( & results[ 0 ] ) ?
60+ }
61+ "markdown" => {
62+ let formatter = MarkdownFormatter :: new ( ) ;
63+ formatter. format ( & results[ 0 ] ) ?
64+ }
65+ _ => {
66+ let formatter = TextFormatter :: new ( ) ;
67+ formatter. format ( & results[ 0 ] ) ?
68+ }
69+ } ;
70+
71+ if let Some ( output_path) = & self . output {
72+ std:: fs:: write ( output_path, formatted_output) ?;
73+ println ! ( "📄 Report written to {}" , output_path. display( ) ) ;
74+ } else {
75+ println ! ( "{}" , formatted_output) ;
76+ }
77+
78+ Ok ( ( ) )
79+ }
80+ }
81+
82+ pub struct HistoryCommand {
83+ pub db : Option < PathBuf > ,
84+ pub limit : Option < usize > ,
85+ }
86+
87+ impl HistoryCommand {
88+ pub fn new ( ) -> Self {
89+ Self {
90+ db : None ,
91+ limit : Some ( 10 ) ,
92+ }
93+ }
94+
95+ pub async fn execute ( & self ) -> Result < ( ) > {
96+ let db_path = get_db_path ( self . db . clone ( ) ) ;
97+ let storage = SqliteStorage :: new ( & db_path) ?;
98+
99+ let history = storage. get_scan_history ( self . limit . unwrap_or ( 10 ) ) . await ?;
100+
101+ if history. is_empty ( ) {
102+ println ! ( "No scan history found" ) ;
103+ return Ok ( ( ) ) ;
104+ }
105+
106+ println ! ( "📊 Scan History:" ) ;
107+ println ! ( "{:<5} {:<20} {:<15} {:<10}" , "ID" , "Timestamp" , "Path" , "Issues" ) ;
108+ println ! ( "{}" , "-" . repeat( 60 ) ) ;
109+
110+ for entry in history {
111+ println ! (
112+ "{:<5} {:<20} {:<15} {:<10}" ,
113+ entry. id,
114+ entry. timestamp. format( "%Y-%m-%d %H:%M:%S" ) ,
115+ entry. path. display( ) ,
116+ entry. issue_count
117+ ) ;
118+ }
119+
120+ Ok ( ( ) )
121+ }
122+ }
123+
124+ #[ cfg( test) ]
125+ mod tests {
126+ use super :: * ;
127+
128+ #[ test]
129+ fn test_report_command_creation ( ) {
130+ let cmd = ReportCommand :: new ( "json" . to_string ( ) ) ;
131+ assert_eq ! ( cmd. format, "json" ) ;
132+ assert ! ( cmd. id. is_none( ) ) ;
133+ }
134+
135+ #[ test]
136+ fn test_report_command_builder ( ) {
137+ let cmd = ReportCommand :: new ( "html" . to_string ( ) )
138+ . with_id ( 123 )
139+ . with_output ( PathBuf :: from ( "output.html" ) ) ;
140+
141+ assert_eq ! ( cmd. format, "html" ) ;
142+ assert_eq ! ( cmd. id, Some ( 123 ) ) ;
143+ assert_eq ! ( cmd. output, Some ( PathBuf :: from( "output.html" ) ) ) ;
144+ }
145+
146+ #[ test]
147+ fn test_history_command_creation ( ) {
148+ let cmd = HistoryCommand :: new ( ) ;
149+ assert_eq ! ( cmd. limit, Some ( 10 ) ) ;
150+ assert ! ( cmd. db. is_none( ) ) ;
151+ }
152+ }
0 commit comments