Updated website to use axum
+ Its better than warp and rocket so far... + Also switched to 2021 Rust + Needs more refactoring but I wanted to get it to a "working" state + clippy + fmt
This commit is contained in:
		
							parent
							
								
									36f78e2678
								
							
						
					
					
						commit
						33a6056445
					
				
							
								
								
									
										1626
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1626
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
									
									
									
									
								
							| @ -2,14 +2,23 @@ | |||||||
| name = "jsite" | name = "jsite" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| authors = ["Joey Hines <joey@ahines.net>"] | authors = ["Joey Hines <joey@ahines.net>"] | ||||||
| edition = "2018" | edition = "2021" | ||||||
| 
 | 
 | ||||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| rocket = "0.5.0-rc.1" | serde = {version = "1", features = ["derive"]} | ||||||
| serde = "1.0" | serde_json = "1" | ||||||
| serde_derive = "1.0" | regex = "1" | ||||||
| serde_json = "1.0" | tokio = { version = "1", features = ["full"] } | ||||||
| regex = "1.3.4" | axum = "0.6.1" | ||||||
| rocket_dyn_templates = { version="0.1.0-rc.1", features=["tera"] } | tera = "1.17.1" | ||||||
|  | tower = { version = "0.4", features = ["util", "timeout", "load-shed", "limit"] } | ||||||
|  | tower-http = { version = "0.3.0", features = [ | ||||||
|  |     "add-extension", | ||||||
|  |     "compression-full", | ||||||
|  |     "limit", | ||||||
|  |     "trace", | ||||||
|  | ] } | ||||||
|  | tower-layer = "0.3.2" | ||||||
|  | axum-extra = { version = "0.4.2", features = ["spa"]} | ||||||
|  | |||||||
							
								
								
									
										46
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | use axum::http::StatusCode; | ||||||
|  | use axum::response::{IntoResponse, Response}; | ||||||
|  | use std::fmt; | ||||||
|  | use std::fmt::{Display, Formatter}; | ||||||
|  | use std::path::PathBuf; | ||||||
|  | use tera::Error; | ||||||
|  | 
 | ||||||
|  | impl IntoResponse for JSiteError { | ||||||
|  |     fn into_response(self) -> Response { | ||||||
|  |         println!("Error occurred on route: {}", self); | ||||||
|  |         ( | ||||||
|  |             StatusCode::INTERNAL_SERVER_ERROR, | ||||||
|  |             "Internal error, try again later".to_string(), | ||||||
|  |         ) | ||||||
|  |             .into_response() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug)] | ||||||
|  | pub enum JSiteError { | ||||||
|  |     PageNotFound(PathBuf), | ||||||
|  |     IOError(std::io::Error), | ||||||
|  |     TemplateError(tera::Error), | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<std::io::Error> for JSiteError { | ||||||
|  |     fn from(e: std::io::Error) -> Self { | ||||||
|  |         JSiteError::IOError(e) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl From<tera::Error> for JSiteError { | ||||||
|  |     fn from(e: Error) -> Self { | ||||||
|  |         Self::TemplateError(e) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Display for JSiteError { | ||||||
|  |     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||||||
|  |         match self { | ||||||
|  |             JSiteError::PageNotFound(e) => write!(f, "Page not found: {}", e.display()), | ||||||
|  |             JSiteError::IOError(e) => write!(f, "IO Error: {}", e), | ||||||
|  |             JSiteError::TemplateError(e) => write!(f, "Template rendering error: {}", e), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										210
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										210
									
								
								src/main.rs
									
									
									
									
									
								
							| @ -1,59 +1,29 @@ | |||||||
| #![feature(proc_macro_hygiene, decl_macro)] | mod error; | ||||||
| 
 |  | ||||||
| #[macro_use] |  | ||||||
| extern crate rocket; |  | ||||||
| #[macro_use] |  | ||||||
| extern crate serde_derive; |  | ||||||
| 
 |  | ||||||
| mod tests; |  | ||||||
| mod rst_parser; | mod rst_parser; | ||||||
|  | mod tests; | ||||||
| 
 | 
 | ||||||
| use crate::rst_parser::parse_images; | use crate::rst_parser::parse_images; | ||||||
|  | use axum::error_handling::HandleErrorLayer; | ||||||
|  | use axum::extract::{Path, State}; | ||||||
|  | use axum::http::StatusCode; | ||||||
|  | use axum::response::{Html, IntoResponse}; | ||||||
|  | use axum::routing::get; | ||||||
|  | use axum::{BoxError, Router}; | ||||||
|  | use axum_extra::routing::SpaRouter; | ||||||
|  | use error::JSiteError; | ||||||
| use regex::Regex; | use regex::Regex; | ||||||
| use rocket::Request; |  | ||||||
| use rocket_dyn_templates::Template; |  | ||||||
| use rst_parser::parse_links; | use rst_parser::parse_links; | ||||||
| use std::collections::HashMap; | use serde::Serialize; | ||||||
| use std::error; | use std::borrow::Cow; | ||||||
| use std::fmt; | use std::net::SocketAddr; | ||||||
| use std::io::Error; | use std::path::PathBuf; | ||||||
| use std::path::{Path, PathBuf}; | use std::sync::Arc; | ||||||
| use std::{fs, io}; | use std::time::Duration; | ||||||
|  | use tera::{Context, Tera}; | ||||||
|  | use tower::ServiceBuilder; | ||||||
|  | use tower_http::trace::TraceLayer; | ||||||
| 
 | 
 | ||||||
| type PageResult<T> = std::result::Result<T, JSiteError>; | type PageResult<T> = Result<T, JSiteError>; | ||||||
| 
 |  | ||||||
| #[derive(Clone, Debug)] |  | ||||||
| struct PageNotFoundError; |  | ||||||
| 
 |  | ||||||
| impl fmt::Display for PageNotFoundError { |  | ||||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |  | ||||||
|         write!(f, "Page not found") |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl error::Error for PageNotFoundError { |  | ||||||
|     fn source(&self) -> Option<&(dyn error::Error + 'static)> { |  | ||||||
|         None |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| #[derive(Clone, Debug)] |  | ||||||
| enum JSiteError { |  | ||||||
|     PageNotFound(PageNotFoundError), |  | ||||||
|     IOError, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl std::convert::From<PageNotFoundError> for JSiteError { |  | ||||||
|     fn from(e: PageNotFoundError) -> Self { |  | ||||||
|         JSiteError::PageNotFound(e) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| impl std::convert::From<std::io::Error> for JSiteError { |  | ||||||
|     fn from(_: Error) -> Self { |  | ||||||
|         JSiteError::IOError |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| #[derive(Serialize)] | #[derive(Serialize)] | ||||||
| struct SiteFile { | struct SiteFile { | ||||||
| @ -71,16 +41,15 @@ struct PageData { | |||||||
| 
 | 
 | ||||||
| /// Returns the rendered template of the index page of the website. This includes links and rst
 | /// Returns the rendered template of the index page of the website. This includes links and rst
 | ||||||
| /// pages included in `static/raw_rst`
 | /// pages included in `static/raw_rst`
 | ||||||
| #[get("/")] | async fn index(State(state): State<Arc<Tera>>) -> PageResult<impl IntoResponse> { | ||||||
| fn index() -> Template { |     let mut ctx = Context::new(); | ||||||
|     let mut map: HashMap<&str, Vec<SiteFile>> = HashMap::new(); |  | ||||||
|     let mut links: Vec<SiteFile> = Vec::new(); |     let mut links: Vec<SiteFile> = Vec::new(); | ||||||
| 
 | 
 | ||||||
|     // Get the links to display on the main page
 |     // Get the links to display on the main page
 | ||||||
|     get_pages("static/raw_rst", &mut links).ok(); |     get_pages("static/raw_rst", &mut links)?; | ||||||
| 
 | 
 | ||||||
|     map.insert("links", links); |     ctx.insert("links", &links); | ||||||
|     Template::render("index", &map) |     Ok(Html(state.render("index.html.tera", &ctx)?)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Gets all the raw rst pages contained in a directory
 | /// Gets all the raw rst pages contained in a directory
 | ||||||
| @ -91,11 +60,11 @@ fn index() -> Template { | |||||||
| /// # Arguments
 | /// # Arguments
 | ||||||
| /// * `path` - the path to look for pages in
 | /// * `path` - the path to look for pages in
 | ||||||
| /// * `pages` - A vector where found pages will be inserted
 | /// * `pages` - A vector where found pages will be inserted
 | ||||||
| fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> io::Result<()> { | fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> PageResult<()> { | ||||||
|     let re = Regex::new(r"(?P<rank>^\d*)(?P<link_name>.+)").unwrap(); |     let re = Regex::new(r"(?P<rank>^\d*)(?P<link_name>.+)").unwrap(); | ||||||
| 
 | 
 | ||||||
|     // Find all files in the directory
 |     // Find all files in the directory
 | ||||||
|     for entry in fs::read_dir(path)? { |     for entry in std::fs::read_dir(path)? { | ||||||
|         let entry = entry?; |         let entry = entry?; | ||||||
|         let path = entry.path(); |         let path = entry.path(); | ||||||
|         let file_name = match path.file_stem() { |         let file_name = match path.file_stem() { | ||||||
| @ -142,9 +111,15 @@ fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> io::Result<()> { | |||||||
| /// # Arguments
 | /// # Arguments
 | ||||||
| /// * `path` - path to search in
 | /// * `path` - path to search in
 | ||||||
| /// * `page_name` - file to look for
 | /// * `page_name` - file to look for
 | ||||||
| fn get_page(path: &Path) -> PageResult<SiteFile> { | fn get_page(path: &std::path::Path) -> PageResult<SiteFile> { | ||||||
|     let file_name = path.file_name().ok_or(PageNotFoundError)?; |     let file_name = path | ||||||
|     let file_name = file_name.to_str().ok_or(PageNotFoundError)?.to_string(); |         .file_name() | ||||||
|  |         .ok_or(JSiteError::PageNotFound(path.to_path_buf()))?; | ||||||
|  |     let file_name = file_name | ||||||
|  |         .to_str() | ||||||
|  |         .ok_or(JSiteError::PageNotFound(path.to_path_buf()))? | ||||||
|  |         .to_string(); | ||||||
|  | 
 | ||||||
|     if path.exists() { |     if path.exists() { | ||||||
|         return Ok(SiteFile { |         return Ok(SiteFile { | ||||||
|             rank: 0, |             rank: 0, | ||||||
| @ -171,21 +146,14 @@ fn get_page(path: &Path) -> PageResult<SiteFile> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     Err(JSiteError::from(PageNotFoundError)) |     Err(JSiteError::PageNotFound(path.to_path_buf())) | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn error_page(page: &str) -> Template { |  | ||||||
|     let mut map = HashMap::new(); |  | ||||||
|     map.insert("error_page", page); |  | ||||||
|     Template::render("404", map) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Returns a rendered template of a raw rst page if it exists
 | /// Returns a rendered template of a raw rst page if it exists
 | ||||||
| ///
 | ///
 | ||||||
| /// # Arguments
 | /// # Arguments
 | ||||||
| /// * `page` - path to page
 | /// * `page` - path to page
 | ||||||
| #[get("/about/<page..>")] | async fn rst_page(tera: State<Arc<Tera>>, Path(page): Path<PathBuf>) -> PageResult<Html<String>> { | ||||||
| fn rst_page(page: PathBuf) -> Template { |  | ||||||
|     let mut path = PathBuf::from("static/raw_rst"); |     let mut path = PathBuf::from("static/raw_rst"); | ||||||
|     path.push(page); |     path.push(page); | ||||||
| 
 | 
 | ||||||
| @ -193,17 +161,17 @@ fn rst_page(page: PathBuf) -> Template { | |||||||
|     let site_page = match get_page(path.as_path()) { |     let site_page = match get_page(path.as_path()) { | ||||||
|         Ok(site_page) => site_page, |         Ok(site_page) => site_page, | ||||||
|         Err(_) => { |         Err(_) => { | ||||||
|             return error_page(path.to_str().unwrap()); |             return error_page(&tera, path.to_str().unwrap()).await; | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     if site_page.path.is_dir() { |     let page = if site_page.path.is_dir() { | ||||||
|         // If the file is a directory, list its contents instead
 |         // If the file is a directory, list its contents instead
 | ||||||
|         let mut map = HashMap::new(); |         let mut map = Context::new(); | ||||||
|         let mut sub_files: Vec<SiteFile> = Vec::new(); |         let mut sub_files: Vec<SiteFile> = Vec::new(); | ||||||
|         match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) { |         match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) { | ||||||
|             Ok(_) => (), |             Ok(_) => (), | ||||||
|             Err(_) => return error_page(&site_page.link_name), |             Err(_) => return error_page(&tera, &site_page.link_name).await, | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         let page_data = PageData { |         let page_data = PageData { | ||||||
| @ -211,18 +179,14 @@ fn rst_page(page: PathBuf) -> Template { | |||||||
|             site_file: site_page, |             site_file: site_page, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         map.insert("page_data", page_data); |         map.insert("page_data", &page_data); | ||||||
|         Template::render("listing", &map) |         tera.render("listing.html.tera", &map)? | ||||||
|     } else { |     } else { | ||||||
|         // Else, render the RST page
 |         // Else, render the RST page
 | ||||||
|         let mut map = HashMap::new(); |         let mut map = Context::new(); | ||||||
|         let contents = match fs::read_to_string(site_page.path) { |         let contents = match std::fs::read_to_string(site_page.path.clone()) { | ||||||
|             Ok(contents) => contents, |             Ok(contents) => contents, | ||||||
|             Err(_) => { |             Err(_) => return error_page(&tera, site_page.path.to_str().unwrap()).await, | ||||||
|                 let mut map = HashMap::new(); |  | ||||||
|                 map.insert("error_page", site_page.link_name); |  | ||||||
|                 return Template::render("404", map); |  | ||||||
|             } |  | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         // Render links
 |         // Render links
 | ||||||
| @ -230,34 +194,72 @@ fn rst_page(page: PathBuf) -> Template { | |||||||
|         contents = parse_images(contents.as_str()).unwrap(); |         contents = parse_images(contents.as_str()).unwrap(); | ||||||
| 
 | 
 | ||||||
|         // Ensure render will look good
 |         // Ensure render will look good
 | ||||||
|         contents = contents.replace("\n", "<br>"); |         contents = contents.replace('\n', "<br>"); | ||||||
|         contents = contents.replace("  ", "  "); |         contents = contents.replace("  ", "  "); | ||||||
| 
 | 
 | ||||||
|         map.insert("page", site_page.link_name); |         map.insert("page", &site_page.link_name); | ||||||
|         map.insert("content", contents); |         map.insert("content", &contents); | ||||||
|         Template::render("rst_page", &map) |         tera.render("rst_page.html.tera", &map)? | ||||||
|     } |     }; | ||||||
|  | 
 | ||||||
|  |     Ok(Html(page)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Catches 404 errors and displays an error message
 | async fn error_page(tera: &Tera, page: &str) -> PageResult<Html<String>> { | ||||||
| ///
 |     let mut map = Context::new(); | ||||||
| /// #Arguments
 |     map.insert("error_page", page); | ||||||
| /// * `req` - information on the original request
 |     Ok(Html(tera.render("404.html.tera", &map)?)) | ||||||
| #[catch(404)] | } | ||||||
| fn not_found(req: &Request<'_>) -> Template { |  | ||||||
|     let mut map = HashMap::new(); |  | ||||||
| 
 | 
 | ||||||
|     map.insert("error_page", String::from(req.uri().path().as_str())); | async fn handle_error(error: BoxError) -> impl IntoResponse { | ||||||
|  |     if error.is::<tower::timeout::error::Elapsed>() { | ||||||
|  |         return (StatusCode::REQUEST_TIMEOUT, Cow::from("request timed out")); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     Template::render("404", &map) |     if error.is::<tower::load_shed::error::Overloaded>() { | ||||||
|  |         return ( | ||||||
|  |             StatusCode::SERVICE_UNAVAILABLE, | ||||||
|  |             Cow::from("service is overloaded, try again later"), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ( | ||||||
|  |         StatusCode::INTERNAL_SERVER_ERROR, | ||||||
|  |         Cow::from(format!("Unhandled internal error: {}", error)), | ||||||
|  |     ) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /// Launches website
 | /// Launches website
 | ||||||
| #[launch] | #[tokio::main] | ||||||
| fn rocket() -> _ { | async fn main() { | ||||||
|     rocket::build() |     // Use globbing
 | ||||||
|         .mount("/", routes![index, rst_page]) |     let tera = match Tera::new("templates/*.tera") { | ||||||
|         .mount("/static", rocket::fs::FileServer::from("static")) |         Ok(t) => Arc::new(t), | ||||||
|         .attach(Template::fairing()) |         Err(e) => { | ||||||
|         .register("/",catchers![not_found]) |             println!("Parsing error(s): {}", e); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let app = Router::new() | ||||||
|  |         .route("/", get(index)) | ||||||
|  |         .route("/about/*path", get(rst_page)) | ||||||
|  |         .merge(SpaRouter::new("/static", "static")) | ||||||
|  |         .layer( | ||||||
|  |             ServiceBuilder::new() | ||||||
|  |                 // Handle errors from middleware
 | ||||||
|  |                 .layer(HandleErrorLayer::new(handle_error)) | ||||||
|  |                 .load_shed() | ||||||
|  |                 .concurrency_limit(1024) | ||||||
|  |                 .timeout(Duration::from_secs(10)) | ||||||
|  |                 .layer(TraceLayer::new_for_http()), | ||||||
|  |         ) | ||||||
|  |         .with_state(tera); | ||||||
|  | 
 | ||||||
|  |     let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||||||
|  |     println!("listening on {}", addr); | ||||||
|  |     axum::Server::bind(&addr) | ||||||
|  |         .serve(app.into_make_service()) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| pub mod rst_error; | pub mod rst_error; | ||||||
| 
 | 
 | ||||||
| use regex::{Captures, Regex}; | use regex::{Captures, Regex}; | ||||||
| use std::collections::HashMap; |  | ||||||
| use rst_error::RSTError; | use rst_error::RSTError; | ||||||
|  | use std::collections::HashMap; | ||||||
| use std::convert::TryFrom; | use std::convert::TryFrom; | ||||||
| 
 | 
 | ||||||
| /// RST Image
 | /// RST Image
 | ||||||
| @ -106,6 +106,8 @@ pub fn parse_images(string: &str) -> Result<String, RSTError> { | |||||||
|     )?; |     )?; | ||||||
| 
 | 
 | ||||||
|     Ok(re_image |     Ok(re_image | ||||||
|         .replace_all(string, |cap: &Captures| RSTImage::try_from(cap).unwrap().to_html()) |         .replace_all(string, |cap: &Captures| { | ||||||
|  |             RSTImage::try_from(cap).unwrap().to_html() | ||||||
|  |         }) | ||||||
|         .to_string()) |         .to_string()) | ||||||
| } | } | ||||||
|  | |||||||
| @ -25,4 +25,4 @@ impl std::fmt::Display for RSTError { | |||||||
|             RSTError::RegexError(e) => write!(f, "regex compile error: `{:?}`", e), |             RSTError::RegexError(e) => write!(f, "regex compile error: `{:?}`", e), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| use super::rst_parser::{parse_links, parse_images}; | use super::rst_parser::{parse_images, parse_links}; | ||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| fn test_link_parser() { | fn test_link_parser() { | ||||||
| @ -16,8 +16,7 @@ fn test_link_parser() { | |||||||
| 
 | 
 | ||||||
| #[test] | #[test] | ||||||
| fn test_image_parser() { | fn test_image_parser() { | ||||||
|     let input = |     let input = ".. image:: cool/image/123.png
 | ||||||
|         ".. image:: cool/image/123.png
 |  | ||||||
|             :width: 60% |             :width: 60% | ||||||
|             :height: auto |             :height: auto | ||||||
|             :alt: this is the alt text |             :alt: this is the alt text | ||||||
| @ -26,5 +25,4 @@ fn test_image_parser() { | |||||||
|     let output = parse_images(input).unwrap(); |     let output = parse_images(input).unwrap(); | ||||||
| 
 | 
 | ||||||
|     assert_eq!(output.trim_end(), "<img src=\"cool/image/123.png\" alt=\"this is the alt text\" style=\"height;60%;width:60%;\">") |     assert_eq!(output.trim_end(), "<img src=\"cool/image/123.png\" alt=\"this is the alt text\" style=\"height;60%;width:60%;\">") | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| {% extends "base" %} | {% extends "base.html.tera" %} | ||||||
| 
 | 
 | ||||||
| {% block command %} | {% block command %} | ||||||
| ./error.sh | ./error.sh | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| {% extends "base" %} | {% extends "base.html.tera" %} | ||||||
| 
 | 
 | ||||||
| {% block command %} | {% block command %} | ||||||
| ./about.sh | ./about.sh | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| {% extends "base" %} | {% extends "base.html.tera" %} | ||||||
| 
 | 
 | ||||||
| {% block command %} | {% block command %} | ||||||
| ls {{ page_data.site_file.link_name }} | ls {{ page_data.site_file.link_name }} | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| {% extends "base" %} | {% extends "base.html.tera" %} | ||||||
| 
 | 
 | ||||||
| {% block command %} | {% block command %} | ||||||
| cat {{ page }}.rst | cat {{ page }}.rst | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user