Added the ability to have directory listings and moved to rst

+ Added projects/website.rst to describe the site
+ Updated templates
+ Added more comments
This commit is contained in:
Joey Hines 2020-02-21 21:54:17 -06:00
parent 5e161f9b35
commit 26575adb87
8 changed files with 190 additions and 51 deletions

View File

@ -10,22 +10,49 @@ use rocket_contrib::templates::Template;
use rocket_contrib::serve::StaticFiles;
use std::{fs, io};
use std::path::PathBuf;
use std::error;
use std::fmt;
#[derive(Serialize)]
struct MDFile {
file_name: String,
link_name: String,
type PageResult<T> = std::result::Result<T, PageNotFoundError>;
#[derive(Clone, Debug)]
struct PageNotFoundError;
impl fmt::Display for PageNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Page not found")
}
}
/// Returns the rendered template of the index page of the website. This includes links and md
/// pages included in `static/raw_md`
impl error::Error for PageNotFoundError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
None
}
}
#[derive(Serialize)]
struct SiteFile {
file_name: String,
link_name: String,
path: PathBuf,
}
#[derive(Serialize)]
struct PageData {
site_file: SiteFile,
links: Vec<SiteFile>,
}
/// Returns the rendered template of the index page of the website. This includes links and rst
/// pages included in `static/raw_rst`
#[get("/")]
fn index() -> Template {
let mut map: HashMap<&str, Vec<SiteFile>> = HashMap::new();
let mut links: Vec<SiteFile> = Vec::new();
let mut map: HashMap<&str, Vec<MDFile>> = HashMap::new();
let mut links: Vec<MDFile> = Vec::new();
match get_pages(&mut links) {
// Get the links to display on the main page
match get_pages("static/raw_rst", &mut links) {
Err(_) => (),
Ok(_) => (),
}
@ -34,23 +61,23 @@ fn index() -> Template {
Template::render("index", &map)
}
/// Gets all the raw md pages contained in static/raw_md/
/// Gets all the raw rst pages contained in static/raw_rst/
///
/// The md page can start with a number
/// The rst page can start with a number
///
/// # Arguments
///
/// * `links` - A reference to a vector of string to insert the links into
fn get_pages(links: &mut Vec<MDFile>) -> io::Result<()> {
// Gather all of the md files in static/raw_md/
let mut entries: Vec<PathBuf> = fs::read_dir("static/raw_md/")?
fn get_pages(path: &str, links: &mut Vec<SiteFile>) -> io::Result<()> {
// Gather all of the rst files in static/raw_rst/
let mut entries: Vec<PathBuf> = fs::read_dir(path)?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, io::Error>>()?;
// Sort so they are always in the same order
entries.sort();
//
// Find all files in the directory
for entry in entries {
let file_name = entry.file_stem().unwrap().to_str().unwrap();
let link_name;
@ -61,55 +88,105 @@ fn get_pages(links: &mut Vec<MDFile>) -> io::Result<()> {
link_name = file_name;
}
let md_file = MDFile {
let rst_file = SiteFile {
file_name: String::from(file_name),
link_name: String::from(link_name),
path: entry.to_owned()
};
links.push(md_file);
links.push(rst_file);
}
Ok(())
}
/// Returns a rendered template of a raw md page if it exists
/// Gets a page matching `page_name` in directory `path`
///
/// #Arguments
/// # Arguments
///
/// * `page` - a string containing the name of the md file to look for
#[get("/<page>")]
fn md_page(page: String) -> Template {
let mut map = HashMap::new();
let mut md_files: Vec<MDFile> = Vec::new();
/// * `path` - path to search in
/// * `page_name` - file to look for
fn get_page(path: &str, page_name: &str) -> Result<SiteFile, PageNotFoundError> {
let mut pages: Vec<SiteFile> = Vec::new();
match get_pages(&mut md_files) {
Err(_) => (),
// Get pages
match get_pages(path, &mut pages) {
Err(_) => return Err(PageNotFoundError),
Ok(_) => (),
};
let mut file_name = String::new();
for md_file in md_files {
if md_file.link_name.eq_ignore_ascii_case(page.as_str()) {
file_name = md_file.file_name.clone();
// Look for the page in the directory
for page in pages {
if page.link_name.eq_ignore_ascii_case(page_name) {
return Ok(page)
}
}
let mut contents = match fs::read_to_string(format!("static/raw_md/{}.md", file_name)) {
Ok(contents) => contents,
Err(_) => {
map.insert("error_page", page);
return Template::render("404", map)
},
};
contents = contents.replace("\n", "<br>");
contents = contents.replace(" ", "&nbsp;&nbsp;");
map.insert("page", page);
map.insert("md_data", contents);
Template::render("md_page", &map)
Err(PageNotFoundError)
}
/// Returns a rendered template of a raw rst page if it exists
///
/// # Arguments
///
/// * `page` - a string containing the name of the rst file to look for
#[get("/about/<page..>")]
fn rst_page(page: PathBuf) -> Template {
// Try and get the page
let site_page = match get_page(format!("static/raw_rst/{}", page.parent().unwrap().to_str().unwrap()).as_str(), &page.file_name().unwrap().to_str().unwrap()) {
Ok(site_page) => site_page,
Err(_) => {
let mut map = HashMap::new();
map.insert("error_page", page);
return Template::render("404", map)
}
};
if site_page.path.is_dir() {
// If the file is a directory, list its contents instead
let mut map = HashMap::new();
let mut sub_files: Vec<SiteFile> = Vec::new();
match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) {
Err(_) => (),
Ok(_) => (),
};
let page_data = PageData {
links: sub_files,
site_file: site_page
};
map.insert("page_data", page_data);
return Template::render("listing", &map);
}
else {
// Else, render the RST page
let mut map = HashMap::new();
let mut contents = match fs::read_to_string(site_page.path) {
Ok(contents) => contents,
Err(_) => {
let mut map = HashMap::new();
map.insert("error_page", page);
return Template::render("404", map)
},
};
// Ensure render will look good
contents = contents.replace("\n", "<br>");
contents = contents.replace(" ", "&nbsp;&nbsp;");
map.insert("page", site_page.link_name);
map.insert("content", contents);
Template::render("rst_page", &map)
}
}
/// Catches 404 errors and displays an error message
///
/// #Arguments
///
/// * `req` - information on the original request
#[catch(404)]
fn not_found(req: &Request<'_>) -> Template {
let mut map = HashMap::new();
@ -120,14 +197,16 @@ fn not_found(req: &Request<'_>) -> Template {
}
/// Launches website
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![index, md_page], )
.mount("/", routes![index, rst_page], )
.mount("/static", StaticFiles::from("static"))
.attach(Template::fairing())
.register(catchers![not_found])
}
/// Main
fn main() {
rocket().launch();
}

View File

@ -0,0 +1,45 @@
This Website
============
This is the 2nd version of my personal website, the first version was very similar to this but written in plain HTML
using Bootstrap. I was never very happy with how it looked and it being raw HTML limited what I could do with it. To
improve it, I set out with a few goals:
* Use a web framework to improve capabilities
* Be easy to deploy
* Allow things, like the resume, to be easily editable
* Improve aesthetics
* Ensure mobile version also looks good
Picking Rust and Rocket
-----------------------
As I am not much of a UI person, the website update was put on the back burner for sometime. The old site was "good
enough." I originally considered doing it in Python and using Django. But that fails the criteria of being easily
deployable. In early 2020, I began learning Rust out of curiosity and the need to use it for research.
I decided to tackle the website backend in Rust as a learning exercise. I then went with the Rocket framework. It
seemed to suit my needs and supported Tera templates. Tera templates are very similar to Django's templates that I
had already had experience in.
Easily Editable
---------------
As the style of the site is a simplistic linux terminal, I decided it would be nice to have the longer pages be in
raw reStructuredText. This fits the aesthetic of the site well and unlike HTML files, they are raw text and are easy to
write and read while editing them. RST files can also easily be built into PDFs, which is great for generating a resume.
To create new pages, I simply have to add new .rst files to a directory on the website. When the templates are rendered,
the Rust backend looks for all pages and lists them. To control ordering, I implemented a ranking system where the first
character of a file name is its rank. For example, my resume is in a file called 1resume.rst. This gives it rank of
1 so it shows up on the main page before other links.
Improving Aesthetics
--------------------
Like stated early, I am no UI designer. The terminal style meant I could put very little effort into the design of the
actual website. Somehow on version one, I still managed to mess that up. This was mainly due to hacking together
bootstrap to give me something that looked like a terminal. This time, I dumped Bootstrap and wrote my own CSS. I should
have done that the first place as it was easy to do and produced a far better result. This also by side effect improved
on the site looked on mobile.
Future Improvements
-------------------
As I continue to learn Rust, I plan to implement more features of RST into the raw file displays. For example, doing
links. I want to strike a balance between the aesthetic of a terminal and usefulness. Being able to embedded images
would be nice.

View File

@ -24,5 +24,5 @@
'--' `---` '--'
</pre>
<p class="text body">
<br>Page: {{ error_page }} Not found...<br>
<br>Page "{{ error_page }}" Not found...<br>
{% endblock content %}

View File

@ -1,8 +1,8 @@
<!DOCTYPE html>
<link rel="stylesheet" type="text/css" href="/static/style.css" xmlns="http://www.w3.org/1999/html">
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="/static/style.css" xmlns="http://www.w3.org/1999/html">
<meta charset="UTF-8">
<title>Joey Hines.</title>
<link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono&display=swap" rel="stylesheet">

View File

@ -5,6 +5,7 @@
{% endblock command %}
{% block content %}
<br/>
> Aspiring Embedded Software Engineer <br/>
> Electrical and Computer Engineering Masters Student at Auburn University <br/>
> Graduated from the University of Texas at Dallas with a BS in Computer Engineering <br/>
@ -12,8 +13,9 @@
<br/>
<span class="prompt">joey@ahines:~$</span> ls <br/>
<br/>
{% for link in links %}
<a class="link" href="/{{ link.link_name }}">{{ link.link_name }}</a>&nbsp;&nbsp;
<a class="link" href="about/{{ link.link_name }}">{{ link.link_name }}</a>&nbsp;&nbsp;
{% endfor %}
<a class="link" href="https://www.github.com/joeyahines"> github</a>&nbsp;&nbsp;
<a class="link" href="mailto:joey@ahines.net"> email</a>&nbsp;&nbsp;

View File

@ -0,0 +1,13 @@
{% extends "base" %}
{% block command %}
ls {{ page_data.site_file.link_name }}
{% endblock command %}
{% block content %}
{% for link in page_data.links %}
<br>
<a class="link" href="{{ page_data.site_file.link_name}}/{{ link.link_name }}">{{ link.link_name }}</a>&nbsp;&nbsp;
{% endfor %}
<br>
{% endblock content %}

View File

@ -1,11 +1,11 @@
{% extends "base" %}
{% block command %}
cat {{ page }}.md
cat {{ page }}.rst
{% endblock command %}
{% block content %}
<br/>
{{ md_data | safe }}
{{ content | safe }}
{% endblock content %}