From ac7ff81325431a45e746a6c750168a520235cd26 Mon Sep 17 00:00:00 2001 From: Jeb Bearer Date: Thu, 18 Aug 2022 08:28:17 -0700 Subject: [PATCH 1/3] Include API segment in route path documentation --- src/route.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/route.rs b/src/route.rs index 292e84a1..541d9aab 100644 --- a/src/route.rs +++ b/src/route.rs @@ -391,7 +391,7 @@ impl Route { .replace("{{NAME}}", &self.name()))) (PreEscaped(&meta.heading_routes)) @for path in self.patterns() { - (PreEscaped(meta.route_path.replace("{{PATH}}", path))) + (PreEscaped(meta.route_path.replace("{{PATH}}", &format!("/{}/{}", meta.name, path)))) } (PreEscaped(&meta.heading_parameters)) (PreEscaped(&meta.parameter_table_open)) From eb6b3bf390e386cb1beeb230765a4e43fabbd9dd Mon Sep 17 00:00:00 2001 From: Jeb Bearer Date: Thu, 18 Aug 2022 08:28:32 -0700 Subject: [PATCH 2/3] Indent code blocks in documentation --- public/media/css/style.css | 1 + 1 file changed, 1 insertion(+) diff --git a/public/media/css/style.css b/public/media/css/style.css index 2d35b28c..fa6ed765 100644 --- a/public/media/css/style.css +++ b/public/media/css/style.css @@ -12,6 +12,7 @@ body { } h1, h2, h3 { margin-left: 1.5em; } p, table, ul, ol { margin-left: 3em; margin-top: 0; } +pre { margin-left: 4em; margin-top: 0; } h1 { font-size: 1.4em; margin-top: 3em; color: gray; } h2 { font-size: 1.2em; margin-top: 3em; color: gray; font-variant: small-caps; } h3 { font-size: 1.1em; margin-top: 1em; margin-bottom: 0; color: gray; font-variant: small-caps; } From ba83d4a9b2bd75f193a3e620f409e947957d2f26 Mon Sep 17 00:00:00 2001 From: Jeb Bearer Date: Thu, 18 Aug 2022 08:48:16 -0700 Subject: [PATCH 3/3] Bundle the default public directory and serve it by default. It is important to have a default public directory, because the default HTML fragments for api.toml assume the existence of `style.css` and some icons. This can lead to confusing behavior where the user has not custom-configured anything, but the website doesn't display correctly out of the box, unless this directory is included whenever the default HTML is included. --- Cargo.toml | 1 + examples/hello-world/main.rs | 9 +-------- public/media/js/script.js | 0 src/app.rs | 33 ++++++++++++++++++++++++++++++--- 4 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 public/media/js/script.js diff --git a/Cargo.toml b/Cargo.toml index 5cdf60d3..3d3eec3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ edit-distance = "2.1.0" futures = "0.3.21" futures-util = "0.3.8" http = "0.2.7" +include_dir = "0.7" jf-utils = { features = ["std"], git = "https://github.com/EspressoSystems/jellyfish.git", tag = "0.1.1" } lazy_static = "1.4.0" libc = "0.2.126" diff --git a/examples/hello-world/main.rs b/examples/hello-world/main.rs index 3f7d67e6..13309316 100644 --- a/examples/hello-world/main.rs +++ b/examples/hello-world/main.rs @@ -3,8 +3,6 @@ use futures::FutureExt; use serde::{Deserialize, Serialize}; use snafu::Snafu; use std::io; -use std::path::PathBuf; -use std::str::FromStr; use tide_disco::{http::StatusCode, Api, App, Error, RequestError}; use tracing::info; @@ -40,12 +38,7 @@ async fn serve(port: u16) -> io::Result<()> { let mut api = Api::, HelloError>::from_file("examples/hello-world/api.toml").unwrap(); - api.with_version(env!("CARGO_PKG_VERSION").parse().unwrap()) - .with_public( - PathBuf::from_str(env!("CARGO_MANIFEST_DIR")) - .unwrap() - .join("public/media"), - ); + api.with_version(env!("CARGO_PKG_VERSION").parse().unwrap()); // Can invoke by browsing // `http://0.0.0.0:8080/hello/greeting/dude` diff --git a/public/media/js/script.js b/public/media/js/script.js new file mode 100644 index 00000000..e69de29b diff --git a/src/app.rs b/src/app.rs index cd514776..fe066122 100644 --- a/src/app.rs +++ b/src/app.rs @@ -23,6 +23,8 @@ use crate::{ }; use async_std::sync::Arc; use futures::future::BoxFuture; +use include_dir::{include_dir, Dir}; +use lazy_static::lazy_static; use maud::html; use semver::Version; use serde::{Deserialize, Serialize}; @@ -30,8 +32,11 @@ use serde_with::{serde_as, DisplayFromStr}; use snafu::{ResultExt, Snafu}; use std::collections::hash_map::{Entry, HashMap}; use std::convert::Infallible; +use std::env; +use std::fs; use std::io; use std::ops::{Deref, DerefMut}; +use std::path::PathBuf; use tide::{ http::{headers::HeaderValue, mime}, security::{CorsMiddleware, Origin}, @@ -196,15 +201,37 @@ impl App { } } +static DEFAULT_PUBLIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/public/media"); +lazy_static! { + static ref DEFAULT_PUBLIC_PATH: PathBuf = { + // The contents of the default public directory are included in the binary. The first time + // the default directory is used, if ever, we extract them to a directory on the host file + // system and return the path to that directory. + let path = dirs::data_local_dir() + .unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from("./"))) + .join("tide-disco/public/media"); + // If the path already exists, move it aside so we can update it. + let _ = fs::rename(&path, path.with_extension("old")); + DEFAULT_PUBLIC_DIR.extract(&path).unwrap(); + path + }; +} + impl App { /// Serve the [App] asynchronously. pub async fn serve>>(self, listener: L) -> io::Result<()> { let state = Arc::new(self); let mut server = tide::Server::with_state(state.clone()); for (name, api) in &state.apis { - if let Some(path) = api.public() { - server.at("/public").at(name).serve_dir(path)?; - } + // Clippy complains if the only non-trivial operation in an `unwrap_or_else` closure is + // a deref, but for `lazy_static` types, deref is an effectful operation that (in this + // case) causes a directory to be renamed and another extracted. We only want to execute + // this if we need to (if `api.public()` is `None`) so we disable the lint. + #[allow(clippy::unnecessary_lazy_evaluations)] + server + .at("/public") + .at(name) + .serve_dir(api.public().unwrap_or_else(|| &DEFAULT_PUBLIC_PATH))?; } server.with(add_error_body::<_, Error>); server.with(