11//! Source code browser
22
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:: * ;
109use postgres:: Connection ;
1110use router:: Router ;
12- use serde:: Serialize ;
11+ use serde:: ser :: { Serialize , SerializeStruct , Serializer } ;
1312use serde_json:: Value ;
1413use std:: cmp:: Ordering ;
14+ use std:: collections:: HashMap ;
1515
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 ) ]
1827struct File {
19- /// The name of the file
2028 name : String ,
21- /// The mime type of the file
22- mime : String ,
29+ file_type : FileType ,
2330}
2431
2532/// A list of source files
26- #[ derive( Debug , Clone , PartialEq , Serialize ) ]
2733struct FileList {
2834 metadata : MetaData ,
2935 files : Vec < File > ,
3036}
3137
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+
3267impl FileList {
3368 /// Gets FileList from a request path
3469 ///
@@ -91,15 +126,19 @@ impl FileList {
91126 let path_splited: Vec < & str > = path. split ( '/' ) . collect ( ) ;
92127
93128 // 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
96135 } else {
97- mime . to_owned ( )
136+ FileType :: Binary
98137 } ;
99138
100139 let file = File {
101140 name : path_splited[ 0 ] . to_owned ( ) ,
102- mime ,
141+ file_type : ftype ,
103142 } ;
104143
105144 // avoid adding duplicates, a directory may occur more than once
@@ -116,9 +155,9 @@ impl FileList {
116155
117156 file_list. sort_by ( |a, b| {
118157 // 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 {
120159 Ordering :: Less
121- } else if a. mime != "dir" && b. mime == "dir" {
160+ } else if a. file_type != FileType :: Dir && b. file_type == FileType :: Dir {
122161 Ordering :: Greater
123162 } else {
124163 a. name . to_lowercase ( ) . cmp ( & b. name . to_lowercase ( ) )
@@ -142,18 +181,6 @@ impl FileList {
142181 }
143182}
144183
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-
157184pub fn source_browser_handler ( req : & mut Request ) -> IronResult < Response > {
158185 let router = extension ! ( req, Router ) ;
159186 let name = cexpect ! ( req, router. find( "name" ) ) ;
@@ -195,7 +222,7 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
195222 None
196223 } ;
197224
198- let ( file_content , is_rust_source) = if let Some ( file) = file {
225+ let ( content , is_rust_source) = if let Some ( file) = file {
199226 // serve the file with DatabaseFileHandler if file isn't text and not empty
200227 if !file. 0 . mime . starts_with ( "text" ) && !file. is_empty ( ) {
201228 return Ok ( file. serve ( ) ) ;
@@ -211,21 +238,77 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
211238 ( None , false )
212239 } ;
213240
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" ) ;
216253
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" )
222260 }
223- . into_response ( req)
224261}
225262
226263#[ cfg( test) ]
227264mod tests {
265+ use super :: * ;
228266 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+ }
229312
230313 #[ test]
231314 fn cargo_ok_not_skipped ( ) {
0 commit comments