@@ -10,10 +10,110 @@ pub(crate) use json::Json;
10
10
pub ( crate ) use markdown:: Markdown ;
11
11
pub ( crate ) use raw:: Raw ;
12
12
13
+ use std:: {
14
+ collections:: { HashMap , HashSet } ,
15
+ fmt:: Display ,
16
+ } ;
17
+
13
18
use crate :: stats:: ResponseStats ;
14
19
use anyhow:: Result ;
20
+ use lychee_lib:: InputSource ;
15
21
16
22
pub ( crate ) trait StatsFormatter {
17
23
/// Format the stats of all responses and write them to stdout
18
24
fn format ( & self , stats : ResponseStats ) -> Result < Option < String > > ;
19
25
}
26
+
27
+ /// Convert a `ResponseStats` `HashMap` to a sorted Vec of key-value pairs
28
+ /// The returned keys and values are both sorted in natural, case-insensitive order
29
+ fn sort_stat_map < T > ( stat_map : & HashMap < InputSource , HashSet < T > > ) -> Vec < ( & InputSource , Vec < & T > ) >
30
+ where
31
+ T : Display ,
32
+ {
33
+ let mut entries: Vec < _ > = stat_map
34
+ . iter ( )
35
+ . map ( |( source, responses) | {
36
+ let mut sorted_responses: Vec < & T > = responses. iter ( ) . collect ( ) ;
37
+ sorted_responses. sort_by ( |a, b| {
38
+ let ( a, b) = ( a. to_string ( ) . to_lowercase ( ) , b. to_string ( ) . to_lowercase ( ) ) ;
39
+ human_sort:: compare ( & a, & b)
40
+ } ) ;
41
+
42
+ ( source, sorted_responses)
43
+ } )
44
+ . collect ( ) ;
45
+
46
+ entries. sort_by ( |( a, _) , ( b, _) | {
47
+ let ( a, b) = ( a. to_string ( ) . to_lowercase ( ) , b. to_string ( ) . to_lowercase ( ) ) ;
48
+ human_sort:: compare ( & a, & b)
49
+ } ) ;
50
+
51
+ entries
52
+ }
53
+
54
+ #[ cfg( test) ]
55
+ mod tests {
56
+ use super :: * ;
57
+
58
+ use lychee_lib:: { ErrorKind , Response , Status , Uri } ;
59
+ use url:: Url ;
60
+
61
+ fn make_test_url ( url : & str ) -> Url {
62
+ Url :: parse ( url) . expect ( "Expected valid Website URI" )
63
+ }
64
+
65
+ fn make_test_response ( url_str : & str , source : InputSource ) -> Response {
66
+ let uri = Uri :: from ( make_test_url ( url_str) ) ;
67
+
68
+ Response :: new ( uri, Status :: Error ( ErrorKind :: InvalidUrlHost ) , source)
69
+ }
70
+
71
+ #[ test]
72
+ fn test_sorted_stat_map ( ) {
73
+ let mut test_stats = ResponseStats :: default ( ) ;
74
+
75
+ // Sorted list of test sources
76
+ let test_sources = vec ! [
77
+ InputSource :: RemoteUrl ( Box :: new( make_test_url( "https://example.com/404" ) ) ) ,
78
+ InputSource :: RemoteUrl ( Box :: new( make_test_url( "https://example.com/home" ) ) ) ,
79
+ InputSource :: RemoteUrl ( Box :: new( make_test_url( "https://example.com/page/1" ) ) ) ,
80
+ InputSource :: RemoteUrl ( Box :: new( make_test_url( "https://example.com/page/10" ) ) ) ,
81
+ ] ;
82
+
83
+ // Sorted list of test responses
84
+ let test_response_urls = vec ! [
85
+ "https://example.com/" ,
86
+ "https://github.com/" ,
87
+ "https://itch.io/" ,
88
+ "https://youtube.com/" ,
89
+ ] ;
90
+
91
+ // Add responses to stats
92
+ // Responses are added to a HashMap, so the order is not preserved
93
+ for source in & test_sources {
94
+ for response in & test_response_urls {
95
+ test_stats. add ( make_test_response ( response, source. clone ( ) ) ) ;
96
+ }
97
+ }
98
+
99
+ // Sort error map and extract the sources
100
+ let sorted_errors = sort_stat_map ( & test_stats. error_map ) ;
101
+ let sorted_sources: Vec < InputSource > = sorted_errors
102
+ . iter ( )
103
+ . map ( |( source, _) | ( * source) . clone ( ) )
104
+ . collect ( ) ;
105
+
106
+ // Check that the input sources are sorted
107
+ assert_eq ! ( test_sources, sorted_sources) ;
108
+
109
+ // Check that the responses are sorted
110
+ for ( _, response_bodies) in sorted_errors {
111
+ let response_urls: Vec < & str > = response_bodies
112
+ . into_iter ( )
113
+ . map ( |response| response. uri . as_str ( ) )
114
+ . collect ( ) ;
115
+
116
+ assert_eq ! ( test_response_urls, response_urls) ;
117
+ }
118
+ }
119
+ }
0 commit comments