1
1
//! Source code browser
2
2
3
- use crate :: {
4
- db:: Pool ,
5
- impl_webpage,
6
- web:: { error:: Nope , file:: File as DbFile , page:: WebPage , MetaData } ,
7
- Config ,
8
- } ;
9
- use iron:: { status:: Status , IronError , IronResult , Request , Response } ;
3
+ use super :: file:: File as DbFile ;
4
+ use super :: page:: Page ;
5
+ use super :: MetaData ;
6
+ use crate :: db:: Pool ;
7
+ use crate :: Config ;
8
+ use iron:: prelude:: * ;
10
9
use postgres:: Connection ;
11
10
use router:: Router ;
12
- use serde:: Serialize ;
11
+ use serde:: ser :: { Serialize , SerializeStruct , Serializer } ;
13
12
use serde_json:: Value ;
14
13
use std:: cmp:: Ordering ;
14
+ use std:: collections:: HashMap ;
15
15
16
- /// A source file's name and mime type
17
- #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Serialize ) ]
16
+ /// A source file's type
17
+ #[ derive( PartialEq , PartialOrd ) ]
18
+ enum FileType {
19
+ Dir ,
20
+ Text ,
21
+ Binary ,
22
+ RustSource ,
23
+ }
24
+
25
+ /// A source file
26
+ #[ derive( PartialEq , PartialOrd ) ]
18
27
struct File {
19
- /// The name of the file
20
28
name : String ,
21
- /// The mime type of the file
22
- mime : String ,
29
+ file_type : FileType ,
23
30
}
24
31
25
32
/// A list of source files
26
- #[ derive( Debug , Clone , PartialEq , Serialize ) ]
27
33
struct FileList {
28
34
metadata : MetaData ,
29
35
files : Vec < File > ,
30
36
}
31
37
38
+ impl Serialize for FileList {
39
+ fn serialize < S > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error >
40
+ where
41
+ S : Serializer ,
42
+ {
43
+ let mut state = serializer. serialize_struct ( "FileList" , 2 ) ?;
44
+ state. serialize_field ( "metadata" , & self . metadata ) ?;
45
+
46
+ let mut files = Vec :: with_capacity ( self . files . len ( ) ) ;
47
+ for file in & self . files {
48
+ let mut map = HashMap :: with_capacity ( 2 ) ;
49
+ map. insert ( "name" , Value :: String ( file. name . to_owned ( ) ) ) ;
50
+
51
+ let file_type = match file. file_type {
52
+ FileType :: Dir => "file_type_dir" ,
53
+ FileType :: Text => "file_type_text" ,
54
+ FileType :: Binary => "file_type_binary" ,
55
+ FileType :: RustSource => "file_type_rust_source" ,
56
+ } ;
57
+ map. insert ( file_type, Value :: Bool ( true ) ) ;
58
+
59
+ files. push ( map) ;
60
+ }
61
+ state. serialize_field ( "files" , & files) ?;
62
+
63
+ state. end ( )
64
+ }
65
+ }
66
+
32
67
impl FileList {
33
68
/// Gets FileList from a request path
34
69
///
@@ -91,15 +126,19 @@ impl FileList {
91
126
let path_splited: Vec < & str > = path. split ( '/' ) . collect ( ) ;
92
127
93
128
// if path have '/' it is a directory
94
- let mime = if path_splited. len ( ) > 1 {
95
- "dir" . to_owned ( )
129
+ let ftype = if path_splited. len ( ) > 1 {
130
+ FileType :: Dir
131
+ } else if mime. starts_with ( "text" ) && path_splited[ 0 ] . ends_with ( ".rs" ) {
132
+ FileType :: RustSource
133
+ } else if mime. starts_with ( "text" ) {
134
+ FileType :: Text
96
135
} else {
97
- mime . to_owned ( )
136
+ FileType :: Binary
98
137
} ;
99
138
100
139
let file = File {
101
140
name : path_splited[ 0 ] . to_owned ( ) ,
102
- mime ,
141
+ file_type : ftype ,
103
142
} ;
104
143
105
144
// avoid adding duplicates, a directory may occur more than once
@@ -116,9 +155,9 @@ impl FileList {
116
155
117
156
file_list. sort_by ( |a, b| {
118
157
// directories must be listed first
119
- if a. mime == "dir" && b. mime != "dir" {
158
+ if a. file_type == FileType :: Dir && b. file_type != FileType :: Dir {
120
159
Ordering :: Less
121
- } else if a. mime != "dir" && b. mime == "dir" {
160
+ } else if a. file_type != FileType :: Dir && b. file_type == FileType :: Dir {
122
161
Ordering :: Greater
123
162
} else {
124
163
a. name . to_lowercase ( ) . cmp ( & b. name . to_lowercase ( ) )
@@ -142,18 +181,6 @@ impl FileList {
142
181
}
143
182
}
144
183
145
- #[ derive( Debug , Clone , PartialEq , Serialize ) ]
146
- struct SourcePage {
147
- file_list : FileList ,
148
- show_parent_link : bool ,
149
- file_content : Option < String > ,
150
- is_rust_source : bool ,
151
- }
152
-
153
- impl_webpage ! {
154
- SourcePage = "crate/source.html" ,
155
- }
156
-
157
184
pub fn source_browser_handler ( req : & mut Request ) -> IronResult < Response > {
158
185
let router = extension ! ( req, Router ) ;
159
186
let name = cexpect ! ( req, router. find( "name" ) ) ;
@@ -195,7 +222,7 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
195
222
None
196
223
} ;
197
224
198
- let ( file_content , is_rust_source) = if let Some ( file) = file {
225
+ let ( content , is_rust_source) = if let Some ( file) = file {
199
226
// serve the file with DatabaseFileHandler if file isn't text and not empty
200
227
if !file. 0 . mime . starts_with ( "text" ) && !file. is_empty ( ) {
201
228
return Ok ( file. serve ( ) ) ;
@@ -211,21 +238,77 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
211
238
( None , false )
212
239
} ;
213
240
214
- let file_list = FileList :: from_path ( & conn, & name, & version, & req_path)
215
- . ok_or_else ( || IronError :: new ( Nope :: NoResults , Status :: NotFound ) ) ?;
241
+ let list = FileList :: from_path ( & conn, & name, & version, & req_path) ;
242
+ if list. is_none ( ) {
243
+ use super :: error:: Nope ;
244
+ use iron:: status;
245
+ return Err ( IronError :: new ( Nope :: NoResults , status:: NotFound ) ) ;
246
+ }
247
+
248
+ let page = Page :: new ( list)
249
+ . set_bool ( "show_parent_link" , !req_path. is_empty ( ) )
250
+ . set_true ( "javascript_highlightjs" )
251
+ . set_true ( "show_package_navigation" )
252
+ . set_true ( "package_source_tab" ) ;
216
253
217
- SourcePage {
218
- file_list,
219
- show_parent_link : !req_path. is_empty ( ) ,
220
- file_content,
221
- is_rust_source,
254
+ if let Some ( content) = content {
255
+ page. set ( "file_content" , & content)
256
+ . set_bool ( "file_content_rust_source" , is_rust_source)
257
+ . to_resp ( "source" )
258
+ } else {
259
+ page. to_resp ( "source" )
222
260
}
223
- . into_response ( req)
224
261
}
225
262
226
263
#[ cfg( test) ]
227
264
mod tests {
265
+ use super :: * ;
228
266
use crate :: test:: { assert_success, wrapper} ;
267
+ use serde_json:: json;
268
+
269
+ #[ test]
270
+ fn serialize_file_list ( ) {
271
+ let file_list = FileList {
272
+ metadata : MetaData {
273
+ name : "rcc" . to_string ( ) ,
274
+ version : "0.0.0" . to_string ( ) ,
275
+ description : Some ( "it compiles an unholy language" . to_string ( ) ) ,
276
+ target_name : None ,
277
+ rustdoc_status : true ,
278
+ default_target : "x86_64-unknown-linux-gnu" . to_string ( ) ,
279
+ } ,
280
+ files : vec ! [
281
+ File {
282
+ name: "main.rs" . to_string( ) ,
283
+ file_type: FileType :: RustSource ,
284
+ } ,
285
+ File {
286
+ name: "lib.rs" . to_string( ) ,
287
+ file_type: FileType :: RustSource ,
288
+ } ,
289
+ ] ,
290
+ } ;
291
+
292
+ let correct_json = json ! ( {
293
+ "metadata" : {
294
+ "name" : "rcc" ,
295
+ "version" : "0.0.0" ,
296
+ "description" : "it compiles an unholy language" ,
297
+ "target_name" : null,
298
+ "rustdoc_status" : true ,
299
+ "default_target" : "x86_64-unknown-linux-gnu"
300
+ } ,
301
+ "files" : [ {
302
+ "name" : "main.rs" ,
303
+ "file_type_rust_source" : true
304
+ } , {
305
+ "name" : "lib.rs" ,
306
+ "file_type_rust_source" : true
307
+ } ] ,
308
+ } ) ;
309
+
310
+ assert_eq ! ( correct_json, serde_json:: to_value( & file_list) . unwrap( ) , ) ;
311
+ }
229
312
230
313
#[ test]
231
314
fn cargo_ok_not_skipped ( ) {
0 commit comments