Compare commits

...

8 Commits

Author SHA1 Message Date
Tom Alexander
27a2bea705
Split the output so I can have a tree. 2023-08-18 17:40:19 -04:00
Tom Alexander
4fb203c1db
Putting in new-line characters in the empty lines has fixed copy+paste and made the min-height css unnecessary. 2023-08-18 17:20:45 -04:00
Tom Alexander
51b4eed034
Beginning the render the parsed content. 2023-08-18 17:10:55 -04:00
Tom Alexander
c3be0f249d
Minor style improvements. 2023-08-18 16:26:05 -04:00
Tom Alexander
13fab742e5
Add a sample output code block. 2023-08-18 16:19:37 -04:00
Tom Alexander
893de9a65e
Set cache control headers for the static files. 2023-08-18 15:50:22 -04:00
Tom Alexander
bff0a62291
Change response to impl IntoResponse. 2023-08-18 15:41:23 -04:00
Tom Alexander
c24c5ee54e
POSTing the body to the server. 2023-08-18 15:41:06 -04:00
7 changed files with 218 additions and 11 deletions

1
Cargo.lock generated
View File

@ -394,6 +394,7 @@ dependencies = [
"nom",
"serde",
"tokio",
"tower",
"tower-http",
]

View File

@ -8,4 +8,5 @@ axum = { git = "https://github.com/tokio-rs/axum.git", rev = "52a90390195e884bcc
nom = "7.1.1"
serde = { version = "1.0.183", features = ["derive"] }
tokio = { version = "1.30.0", default-features = false, features = ["macros", "process", "rt", "rt-multi-thread"] }
tower-http = { version = "0.4.3", features = ["fs"] }
tower = "0.4.13"
tower-http = { version = "0.4.3", features = ["fs", "set-header"] }

View File

@ -1,8 +1,13 @@
#![feature(exit_status_error)]
use axum::http::header::CACHE_CONTROL;
use axum::http::HeaderValue;
use axum::response::IntoResponse;
use axum::{http::StatusCode, routing::post, Json, Router};
use owner_tree::{build_owner_tree, OwnerTree};
use owner_tree::build_owner_tree;
use parse::emacs_parse_org_document;
use tower::ServiceBuilder;
use tower_http::services::{ServeDir, ServeFile};
use tower_http::set_header::SetResponseHeaderLayer;
mod error;
mod owner_tree;
@ -11,26 +16,32 @@ mod sexp;
#[tokio::main]
async fn main() {
let serve_dir = ServeDir::new("static").not_found_service(ServeFile::new("static/index.html"));
let static_files_service = {
let serve_dir =
ServeDir::new("static").not_found_service(ServeFile::new("static/index.html"));
ServiceBuilder::new()
.layer(SetResponseHeaderLayer::if_not_present(
CACHE_CONTROL,
HeaderValue::from_static("public, max-age=120"),
))
.service(serve_dir)
};
let app = Router::new()
.route("/parse", post(parse_org_mode))
.fallback_service(serve_dir);
.fallback_service(static_files_service);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
async fn parse_org_mode(
body: String,
) -> Result<(StatusCode, Json<OwnerTree>), (StatusCode, String)> {
async fn parse_org_mode(body: String) -> Result<impl IntoResponse, (StatusCode, String)> {
_parse_org_mode(body)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))
}
async fn _parse_org_mode(
body: String,
) -> Result<(StatusCode, Json<OwnerTree>), Box<dyn std::error::Error>> {
async fn _parse_org_mode(body: String) -> Result<impl IntoResponse, Box<dyn std::error::Error>> {
let ast = emacs_parse_org_document(&body).await?;
let owner_tree = build_owner_tree(body.as_str(), ast.as_str()).map_err(|e| e.to_string())?;
Ok((StatusCode::OK, Json(owner_tree)))

View File

@ -1,5 +1,21 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="reset.css">
<link rel="stylesheet" href="style.css">
<script type="text/javascript" src="script.js" defer></script>
</head>
<body>
Test html file.
<h2>Input org-mode source:</h2>
<textarea id="org-input" rows="24" cols="80"></textarea>
<hr/>
<div class="output_container">
<div>
<div id="parse-output" class="code_block" style="counter-set: code_line_number 0;"></div>
</div>
<div>
tree goes here
</div>
</div>
</body>
</html>

48
static/reset.css Normal file
View File

@ -0,0 +1,48 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

66
static/script.js Normal file
View File

@ -0,0 +1,66 @@
let inFlightRequest = null;
const inputElement = document.querySelector("#org-input");
const outputElement = document.querySelector("#parse-output");
function abortableFetch(request, options) {
const controller = new AbortController();
const signal = controller.signal;
return {
abort: () => controller.abort(),
ready: fetch(request, { ...options, signal })
};
}
async function renderParseResponse(response) {
console.log(response);
outputElement.innerHTML = "";
const lines = response.input.split(/\r?\n/);
const numLines = lines.length;
const numDigits = Math.log10(numLines) + 1;
outputElement.style.paddingLeft = `calc(${numDigits + 1}ch + 10px)`;
for (let line of lines) {
let wrappedLine = document.createElement("code");
wrappedLine.textContent = line ? line : "\n";
outputElement.appendChild(wrappedLine);
}
}
inputElement.addEventListener("input", async () => {
let orgSource = inputElement.value;
if (inFlightRequest != null) {
inFlightRequest.abort();
inFlightRequest = null;
}
outputElement.innerHTML = "";
let newRequest = abortableFetch("/parse", {
method: "POST",
cache: "no-cache",
body: orgSource,
});
inFlightRequest = newRequest;
let response = null;
try {
response = await inFlightRequest.ready;
}
catch (err) {
if (err.name === "AbortError") return;
}
renderParseResponse(await response.json());
});
function highlightLine(htmlName, lineOffset) {
const childOffset = lineOffset + 1;
const codeLineElement = document.querySelector(`.${htmlName} > code:nth-child(${childOffset})`);
codeLineElement?.classList.add("highlighted")
}
function unhighlightLine(htmlName, lineOffset) {
const childOffset = lineOffset + 1;
const codeLineElement = document.querySelector(`.${htmlName} > code:nth-child(${childOffset})`);
codeLineElement?.classList.remove("highlighted")
}

64
static/style.css Normal file
View File

@ -0,0 +1,64 @@
h1, h2, h3, h4, h5, h6, h7 {
font-weight: 700;
}
h1 {
font-size: 28px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 22px;
}
h4 {
font-size: 20px;
}
h5 {
font-size: 18px;
}
h6 {
font-size: 18px;
}
h7 {
font-size: 18px;
}
.code_block {
font: 14px/1.4 "Cascadia Mono", monospace;
background: #272822ff;
color: #f8f8f2ff;
display: table;
white-space: break-spaces;
padding: 5px;
}
.code_block > code {
display: table;
counter-increment: code_line_number;
}
.code_block > code::before {
content: counter(code_line_number) " ";
display: inline-block;
position: absolute;
transform: TranslateX(-100%);
padding-right: 5px;
color: #eeeeee;
}
.code_block > code.highlighted {
background: #307351ff;
}
.code_block > code.highlighted::before {
background: #307351ff;
}
.output_container {
display: flex;
flex-direction: row;
}
.output_container > * {
flex: 1 0;
}