Skip to content

Commit 754b91d

Browse files
KixironJoshua Nelson
authored and
Joshua Nelson
committed
Source
1 parent d20e4cc commit 754b91d

File tree

3 files changed

+146
-162
lines changed

3 files changed

+146
-162
lines changed

src/web/source.rs

Lines changed: 41 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,34 @@
11
//! Source code browser
22
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::*;
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};
910
use postgres::Connection;
1011
use router::Router;
11-
use serde::ser::{Serialize, SerializeStruct, Serializer};
12+
use serde::Serialize;
1213
use serde_json::Value;
1314
use std::cmp::Ordering;
14-
use std::collections::HashMap;
1515

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)]
16+
/// A source file's name and mime type
17+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Serialize)]
2718
struct File {
19+
/// The name of the file
2820
name: String,
29-
file_type: FileType,
21+
/// The mime type of the file
22+
mime: String,
3023
}
3124

3225
/// A list of source files
26+
#[derive(Debug, Clone, PartialEq, Serialize)]
3327
struct FileList {
3428
metadata: MetaData,
3529
files: Vec<File>,
3630
}
3731

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-
6732
impl FileList {
6833
/// Gets FileList from a request path
6934
///
@@ -126,19 +91,15 @@ impl FileList {
12691
let path_splited: Vec<&str> = path.split('/').collect();
12792

12893
// if path have '/' it is a directory
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
94+
let mime = if path_splited.len() > 1 {
95+
"dir".to_owned()
13596
} else {
136-
FileType::Binary
97+
mime.to_owned()
13798
};
13899

139100
let file = File {
140101
name: path_splited[0].to_owned(),
141-
file_type: ftype,
102+
mime,
142103
};
143104

144105
// avoid adding duplicates, a directory may occur more than once
@@ -155,9 +116,9 @@ impl FileList {
155116

156117
file_list.sort_by(|a, b| {
157118
// directories must be listed first
158-
if a.file_type == FileType::Dir && b.file_type != FileType::Dir {
119+
if a.mime == "dir" && b.mime != "dir" {
159120
Ordering::Less
160-
} else if a.file_type != FileType::Dir && b.file_type == FileType::Dir {
121+
} else if a.mime != "dir" && b.mime == "dir" {
161122
Ordering::Greater
162123
} else {
163124
a.name.to_lowercase().cmp(&b.name.to_lowercase())
@@ -181,6 +142,18 @@ impl FileList {
181142
}
182143
}
183144

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+
184157
pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
185158
let router = extension!(req, Router);
186159
let name = cexpect!(req, router.find("name"));
@@ -222,7 +195,7 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
222195
None
223196
};
224197

225-
let (content, is_rust_source) = if let Some(file) = file {
198+
let (file_content, is_rust_source) = if let Some(file) = file {
226199
// serve the file with DatabaseFileHandler if file isn't text and not empty
227200
if !file.0.mime.starts_with("text") && !file.is_empty() {
228201
return Ok(file.serve());
@@ -238,77 +211,21 @@ pub fn source_browser_handler(req: &mut Request) -> IronResult<Response> {
238211
(None, false)
239212
};
240213

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");
214+
let file_list = FileList::from_path(&conn, &name, &version, &req_path)
215+
.ok_or_else(|| IronError::new(Nope::NoResults, Status::NotFound))?;
253216

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")
217+
SourcePage {
218+
file_list,
219+
show_parent_link: !req_path.is_empty(),
220+
file_content,
221+
is_rust_source,
260222
}
223+
.into_response(req)
261224
}
262225

263226
#[cfg(test)]
264227
mod tests {
265-
use super::*;
266228
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-
}
312229

313230
#[test]
314231
fn cargo_ok_not_skipped() {

templates/source.hbs

Lines changed: 0 additions & 38 deletions
This file was deleted.

tera-templates/crate/source.html

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
{%- extends "base.html" -%}
2+
{%- import "header/package_navigation.html" as navigation -%}
3+
4+
{%- block title -%}
5+
{{ macros::doc_title(name=file_list.metadata.name, version=file_list.metadata.version) }}
6+
{%- endblock title -%}
7+
8+
{%- block header -%}
9+
{# Set the active tab to the `source` tab #}
10+
{{ navigation::package_navigation(metadata=file_list.metadata, active_tab="source") }}
11+
{%- endblock header -%}
12+
13+
{%- block body -%}
14+
<div class="container package-page-container">
15+
<div class="pure-g">
16+
<div class="pure-u-1 {% if file_content %}pure-u-sm-7-24 pure-u-md-5-24{% endif %}">
17+
<div class="pure-menu package-menu">
18+
<ul class="pure-menu-list">
19+
{# If this isn't the root folder, show a 'back' button #}
20+
{%- if show_parent_link -%}
21+
<li class="pure-menu-item">
22+
<a href="../" class="pure-menu-link"><i class="fa fa-fw fa-folder-open-o"></i> ..</a>
23+
</li>
24+
{%- endif -%}
25+
26+
{%- for file in file_list.files -%}
27+
<li class="pure-menu-item">
28+
{#
29+
Show a link to the file with a fancy icon. If the file is a directory,
30+
`/` is appended to show the contents of the folder
31+
#}
32+
<a href="./{{ file.name }}{% if file.mime == 'dir' %}/{% endif %}" class="pure-menu-link">
33+
{# Directories #}
34+
{%- if file.mime == "dir" -%}
35+
<i class="fa fa-fw fa-folder-open-o"></i>
36+
37+
{# Rust files #}
38+
{%- elif file.mime == "text/rust" -%}
39+
<i class="fa fa-fw fa-file-code-o"></i>
40+
41+
{# Cargo.lock #}
42+
{%- elif file.mime == "text/plain" and file.name == "Cargo.lock" -%}
43+
<i class="fa fa-fw fa-lock"></i>
44+
45+
{#
46+
TODO: Font awesome v4.6 doesn't support these, upgrade and enable them
47+
{% elif file.mime == "text/markdown" %}
48+
<i class="fab fa-markdown"></i>
49+
50+
{% elif file.mime == "text/plain" and file.name == ".gitignore" %}
51+
<i class="fab fa-git-alt"></i>
52+
53+
More ideas
54+
FontAwesome v5:
55+
".application/x-bzip"
56+
| "application/gzip"
57+
| "application/x-bzip2"
58+
| "application/vnd.rar"
59+
| "application/x-tar"
60+
| "application/zip"
61+
| "application/x-7z-compressed" => https://fontawesome.com/icons/file-archive
62+
"text/javascript" => https://fontawesome.com/icons/js
63+
"application/java-archive" => https://fontawesome.com/icons/java
64+
DevOpticons (https://github.com/file-icons/DevOpicons):
65+
"text/rust" => https://github.com/file-icons/DevOpicons/blob/master/charmap.md#Rust
66+
"text/css" => https://github.com/file-icons/DevOpicons/blob/master/charmap.md#CSS3,%20Full
67+
"text/html" => https://github.com/file-icons/DevOpicons/blob/master/charmap.md#HTML5
68+
#}
69+
70+
{# Text files or files which mime starts with `text` #}
71+
{%- elif file.mime == "text/plain" or file.mime | split(pat="/") | first == "text" -%}
72+
<i class="fa fa-fw fa-file-text-o"></i>
73+
74+
{# Binary files and any unrecognized types #}
75+
{% else -%}
76+
<i class="fa fa-fw fa-file-archive-o"></i>
77+
{%- endif -%}
78+
79+
{{ file.name }}
80+
</a>
81+
</li>
82+
{%- endfor -%}
83+
</ul>
84+
</div>
85+
</div>
86+
87+
{# If the file has content, then display it in a codeblock #}
88+
{%- if file_content -%}
89+
<div class="pure-u-1 pure-u-sm-17-24 pure-u-md-19-24">
90+
<pre><code>{{ file_content }}</code></pre>
91+
</div>
92+
{%- endif -%}
93+
</div>
94+
</div>
95+
{%- endblock body -%}
96+
97+
{%- block css -%}
98+
{# Highlight.js CSS #}
99+
{{ macros::highlight_css() }}
100+
{%- endblock css -%}
101+
102+
{%- block javascript -%}
103+
{# Highlight.js JavaScript #}
104+
{{ macros::highlight_js(languages=["rust", "ini", "markdown"]) }}
105+
{%- endblock javascript -%}

0 commit comments

Comments
 (0)