feature: support more than html for SSI

This commit is contained in:
Andy Burke 2025-07-14 19:22:59 -07:00
parent 02c10aaa72
commit b3e5a4e1b4
4 changed files with 190 additions and 4 deletions

View file

@ -33,7 +33,23 @@ different types of content with a great deal of control.
The default handlers are: The default handlers are:
- HTML with SSI support - HTML with SSI support
eg: <html><body><!-- #include file="./body.html" --></body></html> eg:
```
<html>
<body>
<!-- #include file="./header.html" -->
<!-- can include markdown, which will be converted to html -->
<!-- #include file="./essay.md" -->
<div id="footer">
<!-- you can include text files as well -->
<!-- #include file="./somedir/footer.txt" >
</div>
</body>
</html>
```
- markdown - markdown
will serve markdown as HTML (or raw with an Accept header of text/markdown) will serve markdown as HTML (or raw with an Accept header of text/markdown)
- Typescript - Typescript
@ -92,7 +108,6 @@ object indicating permission is denied or similar.
- [ ] reload typescript if it is modified on disk - [ ] reload typescript if it is modified on disk
#### NOTE ON WINDOWS #### NOTE ON WINDOWS
Because Windows has more restrictions on filenames, you can use `___` in place of `:` in Because Windows has more restrictions on filenames, you can use `___` in place of `:` in

View file

@ -1,7 +1,7 @@
{ {
"name": "@andyburke/serverus", "name": "@andyburke/serverus",
"description": "A flexible HTTP server for mixed content. Throw static files, markdown, Typescript and (hopefully, eventually) more into a directory and serverus can serve it up a bit more like old-school CGI.", "description": "A flexible HTTP server for mixed content. Throw static files, markdown, Typescript and (hopefully, eventually) more into a directory and serverus can serve it up a bit more like old-school CGI.",
"version": "0.7.1", "version": "0.8.0",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": "./serverus.ts", ".": "./serverus.ts",

View file

@ -4,6 +4,7 @@
*/ */
import * as path from '@std/path'; import * as path from '@std/path';
import { md_to_html } from '@andyburke/serverus/handlers/markdown';
// https://stackoverflow.com/a/75205316 // https://stackoverflow.com/a/75205316
const replaceAsync = async (str: string, regex: RegExp, asyncFn: (match: any, ...args: any) => Promise<any>) => { const replaceAsync = async (str: string, regex: RegExp, asyncFn: (match: any, ...args: any) => Promise<any>) => {
@ -34,7 +35,27 @@ async function load_html_with_ssi(html_file: string): Promise<string> {
break; break;
} }
return await load_html_with_ssi(resolved); const HANDLERS: Record<string, (include_path: string) => Promise<string> | string> = {
default: async (include_path: string) => {
try {
return await Deno.readTextFile(include_path);
} catch (error) {
console.dir({ error });
throw error;
}
},
'.md': async (include_path: string) => {
const markdown_content = await Deno.readTextFile(include_path);
return md_to_html(markdown_content);
},
'.html': load_html_with_ssi
};
const extension = path.extname(resolved);
const handler = extension ? (HANDLERS[extension.toLowerCase()] ?? HANDLERS.default) : HANDLERS.default;
return await handler(resolved);
} }
default: { default: {

View file

@ -71,3 +71,153 @@ Deno.test({
} }
} }
}); });
Deno.test({
name: 'get html file with markdown included',
permissions: {
env: true,
read: true,
write: true,
net: true
},
fn: async () => {
let test_server_info: EPHEMERAL_SERVER | null = null;
const cwd = Deno.cwd();
try {
Deno.chdir('./tests/www');
test_server_info = await get_ephemeral_listen_server();
const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/includes/with_markdown.html`);
const body = await response.text();
asserts.assert(response.ok);
asserts.assert(body);
asserts.assertMatch(body, /\<html\>.*?\<h1\>This is Markdown\<\/h1\>.*?\<\/html\>/is);
} finally {
Deno.chdir(cwd);
if (test_server_info) {
await test_server_info?.server?.stop();
}
}
}
});
Deno.test({
name: 'get html file with text included',
permissions: {
env: true,
read: true,
write: true,
net: true
},
fn: async () => {
let test_server_info: EPHEMERAL_SERVER | null = null;
const cwd = Deno.cwd();
try {
Deno.chdir('./tests/www');
test_server_info = await get_ephemeral_listen_server();
const response = await fetch(`http://${test_server_info.hostname}:${test_server_info.port}/includes/with_text.html`);
const body = await response.text();
asserts.assert(response.ok);
asserts.assert(body);
asserts.assertMatch(body, /\<html\>.*?Just some random text in a random text file\..*?\<\/html\>/is);
} finally {
Deno.chdir(cwd);
if (test_server_info) {
await test_server_info?.server?.stop();
}
}
}
});
Deno.test({
name: 'get html file with lots of includes',
permissions: {
env: true,
read: true,
write: true,
net: true
},
fn: async () => {
let test_server_info: EPHEMERAL_SERVER | null = null;
const cwd = Deno.cwd();
try {
Deno.chdir('./tests/www');
test_server_info = await get_ephemeral_listen_server();
const response = await fetch(
`http://${test_server_info.hostname}:${test_server_info.port}/includes/with_lots_of_includes.html`
);
const body = await response.text();
asserts.assert(response.ok);
asserts.assert(body);
asserts.assertEquals(
body,
`<html>
<body>
<p>Hello. Markdown should follow:</p>
<h1>This is Markdown</h1>
<br>
Welcome to it.
<br>
<h2>This is a level 2 heading</h2>
<br>
Can you read this?
<br>
<h3>Let&#39;s go deeper, level 3 heading</h3>
<br>
<ul>
<li> this</li>
<br>
<li> is</li>
<br>
<li> a</li>
<br>
<li> list?</li>
<br>
</ul>
<br>
<h3>How about a table?</h3>
<br>
| column 1 | column 2 |
| -------- | -------- |
| hi | hey |
| hoy | hah |
<br>
<h2>Ok, that seems sufficient?</h2>
<br>
Hardly, but we&#39;ll get back to this.
Just some random text in a random text file.
<div>Include #1</div>
<div>Include #2!</div>
<div>Include #3</div>
<p>Goodbye. The include should be above.</p>
</body>
</html>
`
);
} finally {
Deno.chdir(cwd);
if (test_server_info) {
await test_server_info?.server?.stop();
}
}
}
});