Skip to content

Commit

Permalink
Fix directory issue (#9)
Browse files Browse the repository at this point in the history
* Update dependencies

* Incorporate changes due tokio reorg & hyper response API change

* Fix startup failure where md file in current workdir.
Root cause was PathBuf::parent() returning an Option<empty String> instead of None when providing a relative path to something in the working directory. The code now takes this (unintuitive) behaviour into account.

* Bump patch version
  • Loading branch information
razorheadfx authored Nov 7, 2021
1 parent a39722f commit e39bc06
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 40 deletions.
16 changes: 6 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "grup"
description = "an offline github markdown previewer"
version = "0.2.0"
version = "0.2.1"
authors = ["razorheadfx <\"[email protected]\">"]
keywords = ["grip", "offline", "markdown", "github", "preview"]
categories = ["command-line-utilities", "visualization"]
Expand All @@ -13,20 +13,16 @@ license-file = "LICENSE"
[dependencies]
# logging
log = "0.4"
env_logger = "0.6"
env_logger = "0.9"
# cmdline parsing
structopt = "0.3"
# md parser
comrak = "0.6"
comrak = "0.12"
# monitor files
notify = "5.0.0-pre.2"
notify = "5.0.0-pre.13"
# http server
hyper = "0.13.0-alpha.4"
# tokio = { version = "0.2", features = ["full"] }
tokio-fs = "0.2.0-alpha.6"
tokio-io = "0.2.0-alpha.6"
tokio-sync = "0.2.0-alpha.6"
tokio = { version = "=0.2.0-alpha.6" }
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1.13", features = ["full"] }

[[bin]]
name = "grup"
Expand Down
73 changes: 43 additions & 30 deletions src/grup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ use std::sync::{Arc, Mutex};
use comrak::ComrakOptions;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Request, Response, Server, StatusCode};

use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use structopt::StructOpt;

use tokio_fs::File;
use tokio_io::AsyncReadExt;
use tokio_sync::oneshot::{self, Sender};
// #[macro_use]
// use tokio::prelude::*;
// use tokio_fs::File;
use tokio::fs::File;
use tokio::io::AsyncReadExt;
use tokio::sync::oneshot::{self, Sender};

#[derive(Debug, StructOpt)]
/// grup - an offline github markdown previewer
Expand Down Expand Up @@ -56,18 +54,17 @@ type SenderListPtr = Arc<Mutex<Vec<Sender<()>>>>;
const DEFAULT_CSS: &[u8] = include_bytes!("../resource/github-markdown.css");

fn not_found() -> Result<Response<Body>, hyper::Error> {
let mut response = Response::builder();
response.status(StatusCode::NOT_FOUND);
let response = Response::builder().status(StatusCode::NOT_FOUND);
Ok(response
.body(Body::from(""))
.expect("invalid response builder"))
}

async fn update(updaters: SenderListPtr) -> Result<Response<Body>, hyper::Error> {
let mut response = Response::builder();
response.header("Cache-Control", "no-cache, no-store, must-revalidate");
response.header("Pragma", "no-cache");
response.header("Expires", "0");
let response = Response::builder()
.header("Cache-Control", "no-cache, no-store, must-revalidate")
.header("Pragma", "no-cache")
.header("Expires", "0");

let (tx, rx) = oneshot::channel();
if let Ok(mut updaters) = updaters.lock() {
Expand All @@ -83,14 +80,13 @@ async fn update(updaters: SenderListPtr) -> Result<Response<Body>, hyper::Error>
}

async fn md_file(cfg: CfgPtr) -> Result<Response<Body>, hyper::Error> {
let mut response = Response::builder();
response.header("Content-type", "text/html");
let response = Response::builder().header("Content-type", "text/html");

let content = if let Ok(mut file) = File::open(&cfg.md_file).await {
let mut buf = String::new();
if file.read_to_string(&mut buf).await.is_ok() {
let mut options = ComrakOptions::default();
options.hardbreaks = true;
options.render.hardbreaks = true;
comrak::markdown_to_html(&buf, &options)
} else {
return not_found();
Expand Down Expand Up @@ -161,17 +157,17 @@ async fn md_file(cfg: CfgPtr) -> Result<Response<Body>, hyper::Error> {
.expect("invalid response builder"))
}

// serve the standard CSS
async fn css() -> Result<Response<Body>, hyper::Error> {
let mut response = Response::builder();
response.header("Content-type", "text/css");
let response = Response::builder().header("Content-type", "text/css");
Ok(response
.body(Body::from(DEFAULT_CSS))
.expect("invalid response builder"))
}

// Will only serve files relative to the md file
async fn static_file(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
let mut response = Response::builder();
let response = Response::builder();
let cwd = std::env::current_dir().expect("no working dir");
if req.uri().path().len() > 1 {
let mut fullpath = cwd.clone();
Expand All @@ -195,6 +191,7 @@ async fn static_file(req: Request<Body>) -> Result<Response<Body>, hyper::Error>
not_found()
}

// route the different URIs
async fn router(
cfg: CfgPtr,
updaters: SenderListPtr,
Expand All @@ -214,6 +211,7 @@ async fn router(
}
}

// watch the provided file for changes
fn spawn_watcher(cfg: CfgPtr, updaters: SenderListPtr) -> notify::Result<RecommendedWatcher> {
let parent = cfg
.md_file
Expand All @@ -232,7 +230,7 @@ fn spawn_watcher(cfg: CfgPtr, updaters: SenderListPtr) -> notify::Result<Recomme
// with the yielded events
let md_file_name = cfg.md_file.file_name().expect("path was `..`").to_owned();
let mut file_event_watcher: RecommendedWatcher =
Watcher::new_immediate(move |event: notify::Result<Event>| {
notify::recommended_watcher(move |event: notify::Result<Event>| {
let event = match event {
Ok(ev) => ev,
Err(e) => {
Expand Down Expand Up @@ -269,24 +267,39 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::Builder::from_default_env().init();
let cfg = Arc::new(Cfg::from_args());
let file = &cfg.md_file;
if let Some(parent) = file.parent() {
std::env::set_current_dir(parent)?;
} else {
std::env::set_current_dir(std::path::Component::RootDir.as_os_str())?;
}

debug!("Configuration {:#?}", &cfg);

if !file.exists() {
return Err(
io::Error::new(io::ErrorKind::Other, format!("No such file: {:?}", file)).into(),
);
return Err(io::Error::new(
io::ErrorKind::Other,
format!("No (markdown) file at: {:?}", file),
)
.into());
}

if !file.is_file() {
return Err(
io::Error::new(io::ErrorKind::Other, format!("No such file: {:?}", file)).into(),
io::Error::new(io::ErrorKind::Other, format!("{:?} is not a file.", file)).into(),
);
}

// move the workdir relative to the MD file
// that way resources in directories relative to the MD file can be serveed as static files
// also catch the case where the path has no parent directory
//(i.e. file is in working directory & given filename is relative)
// file.parent() will return an empty string in this case (i would expected it to return None in that case)
if let Some(parent) = file
.parent()
.map(|s| s.to_str())
.flatten()
.filter(|s| !s.is_empty())
{
debug!("File is not in current WD. Changing WD to {}", &parent);
eprintln!("* Switching working directory to {}", parent);
std::env::set_current_dir(parent)?;
}

let updaters = Arc::new(Mutex::new(Vec::new()));
// we just hold on to this, so the file watcher is killed when this function exits
let _watcher = spawn_watcher(cfg.clone(), Arc::clone(&updaters));
Expand All @@ -303,8 +316,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

let addr = std::net::SocketAddr::new(cfg.host, cfg.port);
let server = Server::bind(&addr).serve(service);
println!("Server running at http://{}", addr);
println!("Press Ctrl-C to exit");
eprintln!("* Webserver running at http://{}", addr);
eprintln!("Press Ctrl-C to stop it & exit");
server.await?;
Ok(())
}

0 comments on commit e39bc06

Please sign in to comment.