diff options
| author | Adam T. Carpenter <atc@53hor.net> | 2021-04-15 20:03:01 -0400 | 
|---|---|---|
| committer | Adam T. Carpenter <atc@53hor.net> | 2021-04-15 20:03:01 -0400 | 
| commit | d83fe68ed51016bbb87d83aa512ef8b9d3f0780e (patch) | |
| tree | 05f93d34464692c706288f7bb3497f55e4539b09 | |
| parent | 93d9a3b8d6984a8f113a30fea523607162e71589 (diff) | |
| download | twinh-d83fe68ed51016bbb87d83aa512ef8b9d3f0780e.tar.xz twinh-d83fe68ed51016bbb87d83aa512ef8b9d3f0780e.zip  | |
split config into modules that actually need it, started parsing args
| -rw-r--r-- | artifacts/layout.html | 28 | ||||
| -rw-r--r-- | artifacts/ui-demo.html | 210 | ||||
| -rw-r--r-- | src/config.rs | 54 | ||||
| -rw-r--r-- | src/error.rs | 23 | ||||
| -rw-r--r-- | src/main.rs | 76 | ||||
| -rw-r--r-- | src/repo/mod.rs | 25 | 
6 files changed, 83 insertions, 333 deletions
diff --git a/artifacts/layout.html b/artifacts/layout.html deleted file mode 100644 index a06d6d0..0000000 --- a/artifacts/layout.html +++ /dev/null @@ -1,28 +0,0 @@ -<!DOCTYPE html> -<html> -  <body> -    <ul> -      <li>/: root, filter, search</li> -      <li>/parts: raw parts table</li> -      <li>/parts.csv: CSV download of parts table</li> -      <li>/cars: raw cars table</li> -      <li>/cars.csv: CSV download of cars table</li> -      <li>/suggestions: suggestion input form and appoval list</li> -      <li> -        /login: all the logins -        <ul> -          <li>/login/facebook</li> -          <li>/login/google</li> -          <li>/login/microsoft</li> -          <li>/login/gitea</li> -        </ul> -      </li> -      <li>/api/v1: JSON API</li> -      <ul> -        <li>/api/v1/parts</li> -        <li>/api/v1/cars</li> -        <li>/api/v1/suggestions</li> -      </ul> -    </ul> -  </body> -</html> diff --git a/artifacts/ui-demo.html b/artifacts/ui-demo.html deleted file mode 100644 index 09dc9cb..0000000 --- a/artifacts/ui-demo.html +++ /dev/null @@ -1,210 +0,0 @@ -<!DOCTYPE html> -<html> -  <head> -    <style> -      :root { -        --balboa: #195970; -      } - -      body { -        margin: 0; -        padding: 0; -      } - -      .model::before { -        content: "➥"; -      } - -      .topbar { -        background-color: var(--balboa); -        box-shadow: 0 0 0.5em black; -        position: relative; -        height: 3em; -        z-index: 1; -      } - -      .topbar ul { -        margin: 0; -        padding: 0; -        overflow: hidden; -      } - -      .topbar li { -        float: left; -        display: block; -        padding: 14px 14px; -      } - -      .topbar li.right { -        float: right; -      } - -      .topbar li a { -        color: white; -        text-decoration: none; -      } - -      .sidebar { -        background-color: var(--balboa); -        box-shadow: inset -0.5em 0 0.5em -0.5em black; -        z-index: -1; -        position: fixed; -        top: 3em; -        left: 0; -        width: 20%; -        height: 100%; -        transition: 0.25s; -      } - -      .sidebar button { -        display: block; -        background: none; -        border: 0 none; -        cursor: pointer; -        color: white; -      } - -      .model { -        margin-left: 20px; -      } - -      .model::before { -        content: "➥"; -      } - -      #hidden:target .sidebar { -        left: -20%; -      } - -      .menuToggle a.openMenu { -        display: none; -      } - -      #hidden:target .menuToggle a.closeMenu { -        display: none; -      } - -      #hidden:target .menuToggle a.openMenu { -        display: block; -      } - -      #hidden:target section { -        margin-left: 0; -      } - -      .catalogTable { -        width: 100%; -        border-collapse: collapse; -      } -      .catalogTable td, -      .catalogTable th { -        padding: 0.5em; -      } - -      .catalogTable tr:nth-child(even) { -        background-color: #f2f2f2; -      } -      .catalogTable tr:hover { -        background-color: #ddd; -      } -      .catalogTable th { -        padding-top: 12px; -        padding-bottom: 12px; -        text-align: left; -        background-color: var(--balboa); -        color: white; -      } - -      input[type="search"] { -        text-align: center; -        width: 100%; -      } - -      section { -        margin-left: 20%; -        transition: margin-left 0.25s; -        padding-top: 2em; -        padding-left: 2em; -        padding-right: 2em; -      } -    </style> -  </head> -  <body id="hidden"> -    <nav class="topbar"> -      <ul> -        <li> -          <div class="menuToggle"> -            <a class="openMenu" href="#" -              ><span -                ><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"> -                  <path -                    fill="currentColor" -                    d="M3,6H21V8H3V6M3,11H21V13H3V11M3,16H21V18H3V16Z" -                  /></svg></span -            ></a> -            <a class="closeMenu" href="#hidden" -              ><span -                ><svg style="width: 24px; height: 24px" viewBox="0 0 24 24"> -                  <path -                    fill="currentColor" -                    d="M21,15.61L19.59,17L14.58,12L19.59,7L21,8.39L17.44,12L21,15.61M3,6H16V8H3V6M3,13V11H13V13H3M3,18V16H16V18H3Z" -                  /></svg></span -            ></a> -          </div> -        </li> -        <li><a href="#">Index</a></li> -        <li><a href="#">Parts</a></li> -        <li><a href="#">Cars</a></li> -        <li><a href="#">Suggestions</a></li> -        <li><a href="#">About</a></li> -        <li class="right"><a href="#">Login</a></li> -      </ul> -    </nav> - -    <nav class="sidebar"> -      {{#each makes}} -      <form action="/parts"> -        {{#if (eq this ../selected_make)}} -        <button disabled class="make"> -          {{ this }} -        </button> -        <input type="hidden" name="make" value="{{ ../selected_make }}" /> -        {{#each ../models}} -        {{#if (eq this ../../selected_model)}} -        <button disabled class="model">{{ this }}</button> -        {{else}} -        <button class="model" name="model" value="{{ this }}"> -          {{ this }} -        </button> -        {{/if}} -        {{else}} -        <button disabled class="model">(No models found)</button> -        {{/each}} -        {{else}} -        <button class="make" name="make" value="{{ this }}">{{ this }}</button> -        {{/if}} -      </form> -      {{else}} -      No makes found. -      {{/each}} -    </nav> - -    <section> -      <form> -        <input autofocus type="search" placeholder="hit enter to search" /> -      </form> -    </section> - -    <section> -      <table class="catalogTable"> -        <tbody> -          {{#each parts}} -          <tr> -            <td>{{ this }}</td> -          </tr> -          {{/each}} -        </tbody> -      </table> -    </section> -  </body> -</html> diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index da65385..0000000 --- a/src/config.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::TwinHError; -use once_cell::sync::Lazy; -use std::{env, error::Error, net::IpAddr, net::Ipv4Addr, net::SocketAddr}; - -pub static CONFIG_INSTANCE: Lazy<Config> = Lazy::new(|| match Config::new() { -    Ok(c) => c, -    Err(e) => panic!("twinh: config error: {}", e), -}); - -#[derive(Clone, Debug)] -pub struct Config { -    pub bind_addr: SocketAddr, -    pub db_path: String, -} - -impl Default for Config { -    fn default() -> Self { -        Self { -            bind_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5353), -            db_path: String::from("/var/db/twinh"), -        } -    } -} - -impl Config { -    fn new() -> Result<Self, Box<dyn Error>> { -        let mut args = env::args().skip(1); - -        let db_path = args -            .next() -            .ok_or_else(|| TwinHError(String::from("database directory not provided")))?; - -        let mut config = Config { -            bind_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5353), -            db_path, -        }; - -        while let Some(arg) = args.next() { -            match arg.as_str() { -                "--addr" => { -                    let addr = args.next().unwrap_or_default().parse()?; -                    config.bind_addr.set_ip(addr); -                } -                "--port" => { -                    let port = args.next().unwrap_or_default().parse()?; -                    config.bind_addr.set_port(port); -                } -                _ => {} -            }; -        } - -        Ok(config) -    } -} diff --git a/src/error.rs b/src/error.rs index 943c3ec..ef0ef1d 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,9 @@  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); @@ -9,7 +12,7 @@ impl std::error::Error for TwinHError {}  impl std::fmt::Display for TwinHError {      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { -        write!(f, "Twin H-Power: {}", self.0) +        write!(f, "twinh: {}", self.0)      }  } @@ -24,3 +27,21 @@ impl From<bincode_e> for TwinHError {          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/main.rs b/src/main.rs index 02fdc2e..0c7766a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ -use crate::{config::CONFIG_INSTANCE, error::TwinHError}; +use crate::error::TwinHError;  use hyper::{      service::{make_service_fn, service_fn},      Server,  }; -use std::env; +use std::{env, net::IpAddr, net::Ipv4Addr, net::SocketAddr}; -mod config;  mod error;  mod import;  mod models; @@ -14,9 +13,19 @@ mod routes;  mod templates;  #[tokio::main] -async fn main() -> Result<(), Box<dyn std::error::Error>> { -    // handle non-config args -    for arg in env::args().skip(1) { +async fn main() -> Result<(), TwinHError> { +    // print help if there are no arguments +    let mut args = env::args().skip(1).peekable(); +    if args.peek().is_none() { +        print_help(); +        return Ok(()); +    } + +    // set default bind addr +    let mut bind_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 5353); + +    // handle arguments and options +    while let Some(arg) = args.next() {          match arg.as_str() {              "--create-db" => {                  // create a fresh database and quit @@ -25,34 +34,27 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {              }              "--import" => {                  // import CSV data into database +                let _source = args +                    .next() +                    .ok_or_else(|| TwinHError(String::from("import source not provided")))?;                  todo!();              } -            "--help" | "-h" => { -                // print help -                print!( -                    "twinh: a home-grown classic car parts catalog\n\ -                    \nUsage: twinh <dir> [options]\n\ -                    <dir>           your database directory (e.g. /var/db/twinh)\n\ -                    \nOptions:\n\ -                    --help | -h     prints this message and exits\n\ -                    --addr          an ip address to bind to (e.g. 127.0.0.1)\n\ -                    --port          a port to bind to (e.g. 5353)\n\ -                    --create-db     creates a fresh empty database; <dir> cannot exist yet\n\ -                    --import-cars   imports CSV car data into the database\n\ -                    --import-parts  imports CSV parts data into the database\n\ -                    " -                ); -                return Ok(()); +            "--addr" => { +                bind_addr.set_ip(args.next().unwrap_or_default().parse()?); +            } +            "--port" => { +                bind_addr.set_port(args.next().unwrap_or_default().parse()?);              } -            unknown => { -                panic!("unknown option: {}", unknown); +            _ => { +                // if not the last argument (the database) then it's unknown +                if args.peek().is_some() { +                    print_help(); +                    return Ok(()); +                }              }          };      } -    // gather config -    let bind_addr = CONFIG_INSTANCE.bind_addr; -      // create primary listener      let make_svc =          make_service_fn(move |_conn| async { Ok::<_, TwinHError>(service_fn(routes::router)) }); @@ -62,10 +64,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {      let graceful = server.with_graceful_shutdown(shutdown_signal());      // start and run until signal -    if let Err(e) = graceful.await { -        eprintln!("server error: {}", e); -    } - +    graceful.await?;      Ok(())  } @@ -75,3 +74,18 @@ async fn shutdown_signal() {          .await          .expect("failed to install CTRL+C signal handler");  } + +fn print_help() { +    print!( +        "twinh: a home-grown classic car parts catalog\n\ +                    \nUsage: twinh [options] <dir>\n\ +                    <dir>           your database directory (e.g. /var/db/twinh)\n\ +                    \nOptions:\n\ +                    --addr          an ip address to bind to (e.g. 127.0.0.1)\n\ +                    --port          a port to bind to (e.g. 5353)\n\ +                    --create-db     creates a fresh empty database; <dir> cannot exist yet\n\ +                    --import-cars   imports CSV car data into the database\n\ +                    --import-parts  imports CSV parts data into the database\n\ +                    " +    ); +} diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 45e9ce3..c7992c9 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,4 +1,3 @@ -use crate::config::CONFIG_INSTANCE;  use crate::error::TwinHError;  use crate::models::Car;  use crate::models::Part; @@ -6,20 +5,28 @@ use bincode::deserialize;  use constants::*;  use once_cell::sync::Lazy;  use sled::{Config, Db}; +use std::env;  mod constants; -static REPO_INSTANCE: Lazy<Db> = Lazy::new(|| { -    Config::default() -        .path(&CONFIG_INSTANCE.db_path) -        .temporary(true) -        .open() -        .expect("Couldn't open DB!") +pub static DB_PATH_INSTANCE: Lazy<String> = Lazy::new(|| { +    env::args() +        .skip(1) +        .last() +        .expect("database directory not provided")  }); +static REPO_INSTANCE: Lazy<Db> = +    Lazy::new( +        || match Config::default().path(DB_PATH_INSTANCE.as_str()).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.db_path) +        .path(DB_PATH_INSTANCE.as_str())          .create_new(true)          .open()?;      let cars_tree = db.open_tree(CARS_TREE)?; @@ -28,7 +35,7 @@ pub fn create_demo_db() -> Result<(), TwinHError> {  pub fn create_new_db() -> Result<(), TwinHError> {      sled::Config::default() -        .path(&CONFIG_INSTANCE.db_path) +        .path(DB_PATH_INSTANCE.as_str())          .create_new(true)          .open()?;      Ok(())  |