summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author53hornet <atc@53hor.net>2022-01-22 08:45:24 -0500
committerAdam T. Carpenter <atc@53hor.net>2022-01-22 15:18:04 -0500
commitba49aa806a48839b61fb261f7ccd007a507d8d50 (patch)
tree222855376c301972a10bb8656c94731fc40ded76
parentd7472320a00fa0bfd6b9be904e0730461f093f61 (diff)
downloadtwinh-ba49aa806a48839b61fb261f7ccd007a507d8d50.tar.xz
twinh-ba49aa806a48839b61fb261f7ccd007a507d8d50.zip
feat: experimental cgi forkcgi
-rw-r--r--.gitignore20
-rw-r--r--Cargo.toml12
-rw-r--r--Makefile5
-rw-r--r--TODO0
-rw-r--r--src/config.rs73
-rw-r--r--src/error.rs51
-rw-r--r--src/import/mod.rs1
-rw-r--r--src/main.rs80
-rw-r--r--src/models/mod.rs63
-rw-r--r--src/repo/constants.rs2
-rw-r--r--src/repo/mod.rs107
-rw-r--r--src/routes/mod.rs194
-rw-r--r--src/templates/base.hbs10
-rw-r--r--src/templates/favicon.svg5
-rw-r--r--src/templates/index.hbs7
-rw-r--r--src/templates/login.hbs5
-rw-r--r--src/templates/mod.rs25
-rw-r--r--src/templates/suggestions.hbs2
-rw-r--r--twinh.1.txt0
-rw-r--r--twinh.css0
-rw-r--r--twinhrc.5.txt12
21 files changed, 112 insertions, 562 deletions
diff --git a/.gitignore b/.gitignore
index f504fd3..79382fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,20 +1,4 @@
-# ---> Rust
-# Generated by Cargo
-# will have compiled files and executables
-debug/
-target/
-
-# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
-# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
-Cargo.lock
-
-# These are backup files generated by rustfmt
**/*.rs.bk
-
-
-
-#Added by cargo
-
+/run
/target
-
-.env
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
index 7deeff7..b376c93 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,18 +3,14 @@ name = "twinh"
version = "0.1.0"
authors = ["Adam T. Carpenter <atc@53hor.net>"]
description = "Twin H-Power: A dead simple classic car parts catalog and cross-reference tool"
-edition = "2018"
+edition = "2021"
[dependencies]
-bincode = "1"
env_logger = { version = "0.8", default-features = false }
handlebars = "3"
-hyper = { version = "0.14", default-features = false, features = ["server", "client", "http1", "http2"] }
-hyper-rustls = "0.22"
log = "0.4"
-lazy_static = "1.4"
+rusqlite = "0.26.3"
serde = { version = "1", features = ["derive"]}
serde_urlencoded = "0.7"
-sled = "0.34"
-structopt = { version = "0.3", default-features = false }
-tokio = { version = "1", default-features = false, features = ["rt-multi-thread", "macros", "signal"] }
+totp-lite = "1"
+anyhow = "1"
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5763e77
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+
+all:
+ cargo build
+ mkdir -p run
+ mv target/debug/twinh run/twinh.cgi
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/TODO
diff --git a/src/config.rs b/src/config.rs
index c4396d4..f7d391f 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,48 +1,37 @@
-use std::{net::SocketAddrV4, path::PathBuf};
-use structopt::StructOpt;
+use anyhow::{Context, Result};
+use std::{
+ collections::HashMap,
+ env,
+ fs::File,
+ io::{BufRead, BufReader},
+};
-lazy_static! {
- pub static ref INSTANCE: Config = Config::from_args();
-}
-
-#[derive(Debug, StructOpt)]
-#[structopt(author, about)]
-pub struct Config {
- #[structopt(
- name = "DIR",
- required = true,
- parse(from_str),
- help = "The path to your data directory"
- )]
- pub data_dir: PathBuf,
-
- #[structopt(
- name = "ADDR",
- long = "bind-addr",
- required = false,
- default_value = "127.0.0.1:5353",
- help = "Sets the IP address and port to bind to"
- )]
- pub bind_addr: SocketAddrV4,
+const DEFAULT_RCFILE: &str = "/usr/local/etc/twinhrc"; // TODO: windows users?
+const RCFILE: &str = "TWINHRC";
+const PREFIX: &str = "TWINH_";
+pub const LOGLEVEL: &str = "TWINH_LOGLEVEL";
- #[structopt(
- long,
- short,
- multiple = true,
- parse(from_occurrences),
- help = "Increases log level"
- )]
- pub verbose: usize,
+pub fn init() -> Result<HashMap<String, String>> {
+ let rcpath = env::var(RCFILE).unwrap_or_else(|_| DEFAULT_RCFILE.to_owned());
+ let rcfile = File::open(rcpath).with_context(|| "Failed to open rcfile")?;
- #[structopt(
- long,
- help = "Creates a fresh data directory at DIR and exits; Fails if DIR exists"
- )]
- pub create_dir: bool,
+ // read rcfile into config map
+ let mut config = HashMap::new();
+ for line in BufReader::new(rcfile).lines() {
+ let line = line.with_context(|| "Failed to parse line in rcfile")?;
+ if !line.starts_with('#') {
+ if let Some((key, val)) = line.split_once('=') {
+ config.insert(key.into(), val.into());
+ }
+ }
+ }
- #[structopt(long, help = "Imports CSV car data into the data directory")]
- pub import_cars: bool,
+ // override rcfile config with any present env vars
+ for (key, val) in env::vars() {
+ if let Some(key) = key.strip_prefix(PREFIX) {
+ config.insert(key.into(), val);
+ }
+ }
- #[structopt(long, help = "Imports CSV parts data into the data directory")]
- pub import_parts: bool,
+ Ok(config)
}
diff --git a/src/error.rs b/src/error.rs
deleted file mode 100644
index f987d66..0000000
--- a/src/error.rs
+++ /dev/null
@@ -1,51 +0,0 @@
-use bincode::Error as bincode_e;
-use hyper::Error as hyper_e;
-use sled::Error as sled_e;
-use std::fmt;
-use std::net::AddrParseError;
-use std::num::ParseIntError;
-
-#[derive(Debug)]
-pub struct TwinHError(pub String);
-
-impl TwinHError {
- fn bail(self) {}
-}
-
-impl std::error::Error for TwinHError {}
-
-impl std::fmt::Display for TwinHError {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- write!(f, "twinh: {}", self.0)
- }
-}
-
-impl From<sled_e> for TwinHError {
- fn from(e: sled_e) -> Self {
- Self(format!("database error: {}", e))
- }
-}
-
-impl From<bincode_e> for TwinHError {
- fn from(e: bincode_e) -> Self {
- Self(format!("(de)serialization error: {}", e))
- }
-}
-
-impl From<AddrParseError> for TwinHError {
- fn from(e: AddrParseError) -> Self {
- Self(format!("failed to parse addr: {}", e))
- }
-}
-
-impl From<ParseIntError> for TwinHError {
- fn from(e: ParseIntError) -> Self {
- Self(format!("failed to parse port: {}", e))
- }
-}
-
-impl From<hyper_e> for TwinHError {
- fn from(e: hyper_e) -> Self {
- Self(format!("server error: {}", e))
- }
-}
diff --git a/src/import/mod.rs b/src/import/mod.rs
deleted file mode 100644
index bac9966..0000000
--- a/src/import/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-const test: i32 = 4;
diff --git a/src/main.rs b/src/main.rs
index a17e2b2..edf8be1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,47 +1,47 @@
-use crate::error::TwinHError;
-use hyper::{
- service::{make_service_fn, service_fn},
- Server,
-};
-use log::LevelFilter;
-
-#[macro_use]
-extern crate lazy_static;
+use anyhow::Result;
+use log::{debug, error};
+use std::env;
+use std::io::prelude::*;
mod config;
-mod error;
-mod import;
-mod models;
-mod repo;
-mod routes;
mod templates;
-#[tokio::main]
-async fn main() -> Result<(), TwinHError> {
- // configure logger
- let level = match config::INSTANCE.verbose {
- 1 => LevelFilter::Info,
- 2 => LevelFilter::Debug,
- _ => LevelFilter::Warn,
- };
- env_logger::builder().filter_level(level).init();
-
- // create HTTP listener
- let make_svc =
- make_service_fn(move |_conn| async { Ok::<_, TwinHError>(service_fn(routes::router)) });
-
- // bind server with signal
- let server = Server::bind(&config::INSTANCE.bind_addr.into()).serve(make_svc);
- let graceful = server.with_graceful_shutdown(shutdown_signal());
-
- // start and wait for shutdown
- graceful.await?;
- Ok(())
+fn main() {
+ if let Err(e) = || -> Result<()> {
+ env_logger::Builder::from_env(config::LOGLEVEL).init();
+ let config = config::init()?;
+
+ let reg = templates::init();
+ let vars = env::vars();
+
+ let mut input = String::new();
+ std::io::stdin().read_to_string(&mut input).unwrap();
+ debug!("Final starting input: {input}");
+ debug!("Final starting config: {:?}", config);
+
+ if (env::var("PATH_INFO")? == "/status") {
+ status();
+ } else {
+ println!("content-type: text/html\n");
+ println!("{}", reg.render("index", &String::new())?);
+ }
+ Ok(())
+ }() {
+ error!("{:#}", e);
+ println!("content-type: text/plain\n\n500");
+ }
}
-async fn shutdown_signal() {
- // Wait for CTRL+C
- tokio::signal::ctrl_c()
- .await
- .expect("failed to install CTRL+C signal handler");
+fn status() {
+ let vars = env::vars();
+ let mut input = String::new();
+
+ // begin writing
+ println!("Content-Type: text/html\n\n<html><body><ul>");
+
+ for each in vars {
+ println!("<li>{:?}</li>", &each);
+ }
+
+ println!("{:?}", &input);
}
diff --git a/src/models/mod.rs b/src/models/mod.rs
deleted file mode 100644
index ddef71f..0000000
--- a/src/models/mod.rs
+++ /dev/null
@@ -1,63 +0,0 @@
-use crate::error::TwinHError;
-use bincode::deserialize;
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
-use sled::IVec;
-
-pub struct Entity<M: DeserializeOwned + Serialize> {
- pub umrn: u64,
- pub model: M,
-}
-
-impl<M: DeserializeOwned + Serialize> Entity<M> {
- pub fn new(t: (IVec, IVec)) -> Result<Self, TwinHError> {
- let (umrn, model) = t;
- let umrn = deserialize(&umrn)?;
- let model = deserialize(&model)?;
- Ok(Entity { umrn, model })
- }
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct Part {
- pub key: u64,
- pub number: String,
- pub name: String,
- pub sources: Vec<u64>,
- pub categories: Vec<u64>,
- pub fits_cars: Vec<u64>,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct Car {
- pub doors: u8,
- //pub engine: Engine,
- pub make: String,
- pub model: String,
- //pub transmission: Transmission,
- pub trim: String,
- pub year: u16,
-}
-
-#[derive(Serialize, Deserialize)]
-pub enum Source {
- Web(String),
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct Engine {
- cylinders: u8,
- displacement: u16,
- layout: Layout,
-}
-
-#[derive(Serialize, Deserialize)]
-pub enum Layout {
- I,
- V,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct Transmission(String);
-
-#[derive(Serialize, Deserialize)]
-pub struct Category(String);
diff --git a/src/repo/constants.rs b/src/repo/constants.rs
deleted file mode 100644
index 6da641f..0000000
--- a/src/repo/constants.rs
+++ /dev/null
@@ -1,2 +0,0 @@
-pub const PARTS_TREE: &str = "parts";
-pub const CARS_TREE: &str = "cars";
diff --git a/src/repo/mod.rs b/src/repo/mod.rs
deleted file mode 100644
index 4c55696..0000000
--- a/src/repo/mod.rs
+++ /dev/null
@@ -1,107 +0,0 @@
-use crate::config;
-use crate::error::TwinHError;
-use crate::models::Car;
-use crate::models::Part;
-use bincode::deserialize;
-use constants::*;
-use sled::{Config, Db};
-
-mod constants;
-
-lazy_static! {
- static ref REPO_INSTANCE: Db = match Config::default().path(&config::INSTANCE.data_dir).open() {
- Err(e) => panic!("failed to open database: {}", e),
- Ok(db) => db,
- };
-}
-
-pub fn create_demo_db() -> Result<(), TwinHError> {
- let db = sled::Config::default()
- .path(&config::INSTANCE.data_dir)
- .create_new(true)
- .open()?;
- let cars_tree = db.open_tree(CARS_TREE)?;
- Ok(())
-}
-
-pub fn create_new_db() -> Result<(), TwinHError> {
- sled::Config::default()
- .path(&config::INSTANCE.data_dir)
- .create_new(true)
- .open()?;
- Ok(())
-}
-
-pub fn get_all_cars() -> Result<Vec<Car>, TwinHError> {
- let cars = REPO_INSTANCE
- .open_tree(CARS_TREE)?
- .iter()
- .collect::<Result<Vec<_>, _>>()?
- .into_iter()
- .map(|e| crate::models::Entity::new(e))
- .map(|e| e.map(|e| e.model))
- .collect::<Result<Vec<_>, _>>()?;
- // let cars = REPO_INSTANCE
- // .open_tree(CARS_TREE)?
- // .into_iter()
- // .values()
- // .collect::<Result<Vec<_>, _>>()?
- // .iter()
- // .map(|c| deserialize::<Car>(&c))
- // .collect::<Result<Vec<_>, _>>()?;
- Ok(cars)
-}
-
-pub fn insert_part(part: Part) -> Result<u64, TwinHError> {
- todo!()
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::models::{Car, Entity, Part};
- use bincode::serialize;
- use std::error::Error;
-
- #[test]
- fn test_insert_part() -> Result<(), Box<dyn Error>> {
- let db = sled::Config::default()
- .mode(sled::Mode::HighThroughput)
- .temporary(true)
- .open()?;
-
- let car = Car {
- make: "Hudson".into(),
- model: "Hornet".into(),
- trim: "Sedan".into(),
- doors: 4,
- year: 1953,
- };
-
- let entity = Entity(1, &car);
-
- let tree = db.open_tree(CARS_TREE)?;
- let key = entity.0.to_be_bytes();
- let val = serialize(&car)?;
- tree.insert(key, val)?;
-
- let part = Part {
- key: 2,
- number: "ABC123".into(),
- name: "Rear Wheel Bearing".into(),
- fits_cars: vec![entity.0],
- categories: Vec::new(),
- sources: Vec::new(),
- };
-
- let tree = db.open_tree(PARTS_TREE)?;
- let (key, val) = (serialize(&part.key)?, serialize(&part)?);
- tree.insert(key.clone(), val)?;
-
- let part_out = tree.get(key)?;
- let part_out = deserialize::<Part>(&part_out.unwrap())?;
- assert_eq!(part.key, part_out.key);
-
- Ok(())
- }
-}
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
deleted file mode 100644
index 1948657..0000000
--- a/src/routes/mod.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-use crate::error::TwinHError;
-//use crate::repo;
-use crate::templates;
-use hyper::body::HttpBody as _;
-use hyper::Method;
-use hyper::StatusCode;
-use hyper::{Body, Request, Response};
-use serde::{Deserialize, Serialize};
-
-pub async fn router(req: Request<Body>) -> Result<Response<Body>, TwinHError> {
- match (req.method(), req.uri().path()) {
- (&Method::GET, "/favicon.ico") | (&Method::GET, "/favicon.svg") => favicon(),
- (&Method::GET, "/") => index(req).await,
- (&Method::GET, "/cars") => cars(req).await,
- (&Method::GET, "/login") => login().await,
- (&Method::GET, "/login/google") => login_google().await,
- (&Method::GET, "/login/google/code") => login_google_code(req).await,
- (&Method::GET, "/suggestions") => suggestions().await,
- _ => Ok(Response::builder()
- .status(StatusCode::NOT_FOUND)
- .body("Not found.".into())
- .unwrap()),
- }
-}
-
-async fn suggestions() -> Result<Response<Body>, TwinHError> {
- todo!()
-}
-
-async fn login() -> Result<Response<Body>, TwinHError> {
- Ok(Response::new(
- templates::REGISTRY.render("login", &"").unwrap().into(),
- ))
-}
-
-async fn login_google() -> Result<Response<Body>, TwinHError> {
- let uri = hyper::Uri::builder()
- .scheme("https")
- .authority("accounts.google.com")
- .path_and_query(format!(
- "{}?client_id={}&redirect_uri={}&response_type={}&scope={}&state={}",
- "/o/oauth2/v2/auth",
- "???",
- "http://localhost:3000/login/google/code",
- "code",
- "openid%20profile%20email",
- "BLARGH"
- ))
- .build()
- .unwrap();
- let resp = Response::builder()
- .header(hyper::header::LOCATION, uri.to_string())
- .status(StatusCode::TEMPORARY_REDIRECT)
- .body("".into())
- .unwrap();
- Ok(resp)
-}
-
-#[derive(Serialize)]
-struct AuthRequest {
- client_id: String,
- client_secret: String,
- grant_type: &'static str,
- redirect_uri: String,
- code: String,
-}
-
-impl Default for AuthRequest {
- fn default() -> Self {
- Self {
- client_id: "???".into(),
- client_secret: "???".into(),
- grant_type: "authorization_code",
- redirect_uri: String::new(),
- code: String::new(),
- }
- }
-}
-
-async fn login_google_code(req: Request<Body>) -> Result<Response<Body>, TwinHError> {
- let query = req.uri().query().unwrap_or_default();
- let query = serde_urlencoded::de::from_str::<GoogleOAuthQuery>(query).unwrap();
- if query.state != "BLARGH" {
- dbg!("tampering?");
- }
-
- // get access token
- let auth_body = AuthRequest {
- code: query.code,
- redirect_uri: "http://localhost:3000/login/google/code".to_owned(),
- ..Default::default()
- };
-
- let https = hyper_rustls::HttpsConnector::with_native_roots();
- let client: hyper::Client<_, hyper::Body> = hyper::Client::builder().build(https);
-
- let req = Request::builder()
- .method(Method::POST)
- .uri("https://oauth2.googleapis.com/token")
- .header(
- hyper::header::CONTENT_TYPE,
- "application/x-www-form-urlencoded",
- )
- .body(serde_urlencoded::ser::to_string(auth_body).unwrap().into())
- .unwrap();
- let mut resp = client.request(req).await.unwrap();
- use std::io::prelude::*;
- while let Some(chunk) = resp.body_mut().data().await {
- std::io::stdout().write_all(&chunk.unwrap()).unwrap();
- }
-
- let resp = Response::builder()
- .header(hyper::header::LOCATION, "/".to_string())
- .status(StatusCode::TEMPORARY_REDIRECT)
- .body("Successful login, redirecting...".into())
- .unwrap();
- Ok(resp)
-}
-
-pub struct AccessToken {
- access_token: String,
- expires_in: String,
- scope: String,
- token_type: String,
-}
-
-async fn cars(req: Request<Body>) -> Result<Response<Body>, TwinHError> {
- //let cars = repo::get_all_cars().unwrap();
- todo!()
-}
-
-fn favicon() -> Result<Response<Body>, TwinHError> {
- let resp = Response::builder()
- .header(hyper::header::CONTENT_TYPE, "image/svg+xml")
- .body(templates::FAVICON.into())
- .unwrap();
- Ok(resp)
-}
-
-async fn index(req: Request<Body>) -> Result<Response<Body>, TwinHError> {
- let query = req.uri().query().unwrap_or_default();
- let filter = serde_urlencoded::de::from_str::<PartsQuery>(query).unwrap();
- let mut data = PartsData::default();
- data.makes = Some(vec!["Hudson".into(), "Essex".into(), "Terraplane".into()]);
-
- if let Some(make) = filter.make {
- if make.eq("Hudson") {
- data.models = Some(vec!["Hornet".into(), "Wasp".into(), "Jet".into()]);
- } else if make.eq("Essex") {
- data.models = Some(vec!["Super Six".into()]);
- }
- data.selected_make = Some(make);
- }
-
- data.parts = Some(Vec::new());
- if let Some(model) = filter.model {
- data.parts = Some(vec!["1".into(), "2".into(), "3".into()]);
- data.selected_model = Some(model);
- }
-
- let body = templates::REGISTRY.render("index", &data).unwrap();
- let resp = Response::builder()
- .header(hyper::header::CONTENT_TYPE, "text/html; charset=utf-8")
- .body(body.into())
- .unwrap();
- Ok(resp)
-}
-
-#[derive(Debug, Deserialize)]
-struct GoogleOAuthQuery {
- code: String,
- state: String,
-}
-
-#[derive(Debug, Deserialize)]
-struct PartsQuery {
- make: Option<String>,
- year: Option<String>,
- model: Option<String>,
- engine: Option<String>,
-}
-
-#[derive(Debug, Serialize, Default)]
-struct PartsData {
- makes: Option<Vec<String>>,
- years: Option<Vec<String>>,
- models: Option<Vec<String>>,
- engines: Option<Vec<String>>,
- selected_make: Option<String>,
- selected_year: Option<String>,
- selected_model: Option<String>,
- selected_engine: Option<String>,
- parts: Option<Vec<String>>,
-}
diff --git a/src/templates/base.hbs b/src/templates/base.hbs
index 34f435b..410a5b2 100644
--- a/src/templates/base.hbs
+++ b/src/templates/base.hbs
@@ -42,12 +42,10 @@
flex-direction: column;
background-color: var(--balboa);
padding: 0.5em;
- color: white;
}
aside button {
font-size: 1em;
- padding: 0.5em 0.5em;
display: block;
background: none;
border: 0 none;
@@ -63,11 +61,11 @@
padding: 0.5em;
}
- #hidemenu:target aside {
+ #hidden:target aside {
display: none;
}
- #hidemenu:target a.openMenu {
+ #hidden:target a.openMenu {
display: block;
}
@@ -75,7 +73,7 @@
display: none;
}
- #hidemenu:target a.closeMenu {
+ #hidden:target a.closeMenu {
display: none;
}
@@ -150,7 +148,7 @@
</style>
</head>
-<body id="hidemenu">
+<body id="hidden">
<nav>
<a href="/">Index</a>
<a href="#">Parts</a>
diff --git a/src/templates/favicon.svg b/src/templates/favicon.svg
deleted file mode 100644
index 4ad400c..0000000
--- a/src/templates/favicon.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24">
- <path fill="#195970" d="M17.5 14.33C18.29 14.33 19.13 14.41 20 14.57V16.07C19.38 15.91 18.54 15.83 17.5 15.83C15.6 15.83 14.11 16.16 13 16.82V15.13C14.17 14.6 15.67 14.33 17.5 14.33M13 12.46C14.29 11.93 15.79 11.67 17.5 11.67C18.29 11.67 19.13 11.74 20 11.9V13.4C19.38 13.24 18.54 13.16 17.5 13.16C15.6 13.16 14.11 13.5 13 14.15M17.5 10.5C15.6 10.5 14.11 10.82 13 11.5V9.84C14.23 9.28 15.73 9 17.5 9C18.29 9 19.13 9.08 20 9.23V10.78C19.26 10.59 18.41 10.5 17.5 10.5M21 18.5V7C19.96 6.67 18.79 6.5 17.5 6.5C15.45 6.5 13.62 7 12 8V19.5C13.62 18.5 15.45 18 17.5 18C18.69 18 19.86 18.16 21 18.5M17.5 4.5C19.85 4.5 21.69 5 23 6V20.56C23 20.68 22.95 20.8 22.84 20.91C22.73 21 22.61 21.08 22.5 21.08C22.39 21.08 22.31 21.06 22.25 21.03C20.97 20.34 19.38 20 17.5 20C15.45 20 13.62 20.5 12 21.5C10.66 20.5 8.83 20 6.5 20C4.84 20 3.25 20.36 1.75 21.07C1.72 21.08 1.68 21.08 1.63 21.1C1.59 21.11 1.55 21.12 1.5 21.12C1.39 21.12 1.27 21.08 1.16 21C1.05 20.89 1 20.78 1 20.65V6C2.34 5 4.18 4.5 6.5 4.5C8.83 4.5 10.66 5 12 6C13.34 5 15.17 4.5 17.5 4.5Z" />
-</svg>
diff --git a/src/templates/index.hbs b/src/templates/index.hbs
index 4c07db4..aa708e1 100644
--- a/src/templates/index.hbs
+++ b/src/templates/index.hbs
@@ -1,13 +1,12 @@
{{#> base}}
{{#*inline "main"}}
<aside>
- <a class="closeMenu" href="#hidemenu">
+ <a class="closeMenu" href="#hidden">
<svg style="width: 24px; height: 24px" viewBox="0 0 24 24">
<path fill="#ffffff"
d="M21,15.61L19.59,17L14.58,12L19.59,7L21,8.39L17.44,12L21,15.61M3,6H16V8H3V6M3,13V11H13V13H3M3,18V16H16V18H3Z" />
</svg>
</a>
-
{{#each makes}}
<form action="/">
{{#if (eq this ../selected_make)}}
@@ -44,8 +43,6 @@
</svg>
</a>
- <h2>Catalog</h2>
-
<section>
<form>
{{#if selected_make}}
@@ -73,3 +70,5 @@
{{/inline}}
{{/base}}
+
+
diff --git a/src/templates/login.hbs b/src/templates/login.hbs
deleted file mode 100644
index afdc0da..0000000
--- a/src/templates/login.hbs
+++ /dev/null
@@ -1,5 +0,0 @@
-{{#> base}}
-{{#*inline "main"}}
-<a href="/login/google">Login with Google</a>
-{{/inline}}
-{{/base}}
diff --git a/src/templates/mod.rs b/src/templates/mod.rs
index 99e3bf3..e0e194a 100644
--- a/src/templates/mod.rs
+++ b/src/templates/mod.rs
@@ -1,19 +1,12 @@
use handlebars::Handlebars;
-lazy_static! {
- pub static ref REGISTRY: Handlebars<'static> = {
- let mut handlebars = Handlebars::new();
- handlebars
- .register_template_string("index", include_str!("index.hbs"))
- .unwrap();
- handlebars
- .register_template_string("login", include_str!("login.hbs"))
- .unwrap();
- handlebars
- .register_template_string("base", include_str!("base.hbs"))
- .unwrap();
- handlebars
- };
+pub fn init() -> Handlebars<'static> {
+ let mut handlebars = Handlebars::new();
+ handlebars
+ .register_template_string("index", include_str!("index.hbs"))
+ .unwrap();
+ handlebars
+ .register_template_string("base", include_str!("base.hbs"))
+ .unwrap();
+ handlebars
}
-
-pub static FAVICON: &str = include_str!("favicon.svg");
diff --git a/src/templates/suggestions.hbs b/src/templates/suggestions.hbs
index f979be7..6fb76cc 100644
--- a/src/templates/suggestions.hbs
+++ b/src/templates/suggestions.hbs
@@ -7,3 +7,5 @@
</section>
{{/inline}}
{{/base}}
+
+
diff --git a/twinh.1.txt b/twinh.1.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/twinh.1.txt
diff --git a/twinh.css b/twinh.css
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/twinh.css
diff --git a/twinhrc.5.txt b/twinhrc.5.txt
new file mode 100644
index 0000000..c97492e
--- /dev/null
+++ b/twinhrc.5.txt
@@ -0,0 +1,12 @@
+:man source: twinh
+:man manual: twinh
+
+TWINHRC(5)
+
+NAME
+----
+BLARGH
+
+SYNOPSIS
+--------
+Blargh blargh.