Compare commits
8 Commits
91e9645c37
...
27a2bea705
Author | SHA1 | Date | |
---|---|---|---|
![]() |
27a2bea705 | ||
![]() |
4fb203c1db | ||
![]() |
51b4eed034 | ||
![]() |
c3be0f249d | ||
![]() |
13fab742e5 | ||
![]() |
893de9a65e | ||
![]() |
bff0a62291 | ||
![]() |
c24c5ee54e |
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -394,6 +394,7 @@ dependencies = [
|
||||
"nom",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
]
|
||||
|
||||
|
@ -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"] }
|
||||
|
29
src/main.rs
29
src/main.rs
@ -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)))
|
||||
|
@ -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
48
static/reset.css
Normal 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
66
static/script.js
Normal 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
64
static/style.css
Normal 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user