diff --git a/bun.lock b/bun.lock
index bb6a4c4..a9dd4de 100644
--- a/bun.lock
+++ b/bun.lock
@@ -6,6 +6,7 @@
"dependencies": {
"@biomejs/biome": "^1.9.4",
"@endeavorance/emdy": "^1.0.0",
+ "chokidar": "^4.0.3",
"rehype-callouts": "^2.1.0",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
@@ -72,6 +73,8 @@
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
+ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
+
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
@@ -206,6 +209,8 @@
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
+ "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
+
"rehype-callouts": ["rehype-callouts@2.1.0", "", { "dependencies": { "@types/hast": "^3.0.4", "hast-util-from-html": "^2.0.3", "hast-util-is-element": "^3.0.0", "hastscript": "^9.0.1", "unist-util-visit": "^5.0.0" } }, "sha512-Als/wlYpXg2Rs0p/yofrkSH6IQzn1xh6cvBC5BHy5JVT3lGlzUvVT8wOyVqCEgf8Eun6cQ3f47rLLoaiyLkP0w=="],
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
diff --git a/demo/another.md b/demo/another.md
index 2792cae..2786cbc 100644
--- a/demo/another.md
+++ b/demo/another.md
@@ -17,3 +17,7 @@ How about a callout?
> [!warning]- This is collapsible
> And how!
+
+## A subheading
+
+This is a section!
diff --git a/demo/index.md b/demo/index.md
index 339807f..5830805 100644
--- a/demo/index.md
+++ b/demo/index.md
@@ -1,5 +1,6 @@
---
title: Home | Demo
+and: how
---
# Demo Data
@@ -33,3 +34,8 @@ Tables should be a thing too
- Need numbers
HTML works, still!
+
+
+| hello | world | hello | world | hello | world | hello | world | hello | world | hello |
+| ------------------------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
+| dfjhasfdlkjahslfkjahsdlfjhasldjhasldjhasldk | | | | | | | | | | |
diff --git a/package.json b/package.json
index 05fcb90..a62542f 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@biomejs/biome": "^1.9.4",
"@endeavorance/emdy": "^1.0.0",
+ "chokidar": "^4.0.3",
"rehype-callouts": "^2.1.0",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
diff --git a/src/defaults/default-style.css b/src/defaults/default-style.css
index 4299046..7e111a4 100644
--- a/src/defaults/default-style.css
+++ b/src/defaults/default-style.css
@@ -92,16 +92,19 @@ a {
}
table {
- border-collapse: collapse;
- width: 100%;
- max-width: 100%;
- margin: 1em 0;
- font-size: 1em;
- background-color: var(--color-bg);
+ display: block;
color: var(--color-text);
+ background-color: var(--color-bg);
+ width: 100%;
+ max-width: -moz-fit-content;
+ max-width: fit-content;
+ overflow-x: auto;
+ font-size: 1em;
border: 1px solid var(--color-text);
+ margin: 0 auto;
+ white-space: nowrap;
+ border-collapse: collapse;
}
-
th,
td {
padding: 0.5em 0.5em;
diff --git a/src/index.ts b/src/index.ts
index ab83f80..3a6e9ba 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
import Path from "node:path";
import { parseArgs } from "node:util";
import EMDY from "@endeavorance/emdy";
-import type { BunFile } from "bun";
+import { Glob, type BunFile } from "bun";
import DEFAULT_STYLESHEET from "./defaults/default-style.css" with {
type: "text",
};
@@ -10,10 +10,17 @@ import DEFAULT_TEMPLATE from "./defaults/default-template.html" with {
};
import { CLIError } from "./error";
import { parseMarkdown } from "./markdown";
+import { watch } from "node:fs/promises";
const DEFAULT_TEMPLATE_FILE = "_template.html";
const DEFAULT_STYLESHEET_FILE = "_style.css";
+async function isDirectory(path: string): Promise {
+ const file = Bun.file(path);
+ const stat = await file.stat();
+ return stat.isDirectory();
+}
+
/**
* Given a file path, attempt to read the text of the file
* @param filePath - The path to the file to read
@@ -66,6 +73,9 @@ interface CLIOptions {
/** The path to the stylesheet file */
stylesheetFilePath: string | null;
+ /** If the default stylesheet should be included regardless of the provided stylesheet */
+ includeDefaultStylesheet: boolean;
+
/** If true, show help message */
help: boolean;
@@ -107,6 +117,10 @@ function parseCLIArgs(): { options: CLIOptions; args: string[] } {
type: "string",
short: "s",
},
+ "include-default-stylesheet": {
+ type: "boolean",
+ short: "S",
+ },
cwd: {
type: "string",
default: process.cwd(),
@@ -119,12 +133,14 @@ function parseCLIArgs(): { options: CLIOptions; args: string[] } {
allowPositionals: true,
});
- // Validation
+ // == Argument Validation ==
- if (positionals.length > 1 && flags.outfile) {
+ // Outfile requires only one argument
+ if (flags.outfile && positionals.length > 1) {
throw new CLIError("--outfile cannot be used with multiple inputs.");
}
+ // Outfile and Outdir cannot be used together
if (flags.outdir && flags.outfile) {
throw new CLIError("--outdir and --outfile cannot be used together.");
}
@@ -137,6 +153,7 @@ function parseCLIArgs(): { options: CLIOptions; args: string[] } {
stdout: flags.stdout ?? false,
templateFilePath: flags.template ?? null,
stylesheetFilePath: flags.stylesheet ?? null,
+ includeDefaultStylesheet: flags["include-default-stylesheet"] ?? false,
help: flags.help ?? false,
title: flags.title ?? null,
},
@@ -171,8 +188,11 @@ async function loadTemplate(options: CLIOptions): Promise {
* @returns The stylesheet string
*/
async function loadStylesheet(options: CLIOptions): Promise {
+ const preamble = options.includeDefaultStylesheet ? DEFAULT_STYLESHEET : "";
+
if (options.stylesheetFilePath) {
- return readFile(options.stylesheetFilePath);
+ const loadedSheet = await readFile(options.stylesheetFilePath);
+ return [preamble, loadedSheet].join("\n");
}
const checkStylesheetFile = Bun.file(
@@ -180,7 +200,8 @@ async function loadStylesheet(options: CLIOptions): Promise {
);
if (await checkStylesheetFile.exists()) {
- return checkStylesheetFile.text();
+ const loadedSheet = await checkStylesheetFile.text();
+ return [preamble, loadedSheet].join("\n");
}
return DEFAULT_STYLESHEET;
@@ -263,6 +284,27 @@ async function getOutputFile(
return Bun.file(outputPath);
}
+function processStdin(options: CLIOptions): Promise {
+ const outfile = options.outfile ? Bun.file(options.outfile) : Bun.stdout;
+ return buildFile(Bun.stdin, outfile, options);
+}
+
+async function processFile(
+ options: CLIOptions,
+ filePath: string,
+): Promise {
+ console.log(`Building ${filePath}...`);
+ const file = Bun.file(filePath);
+
+ // Ensure input files exist
+ if (!(await file.exists())) {
+ throw new CLIError(`File ${filePath} does not exist.`);
+ }
+
+ const outfile = await getOutputFile(filePath, options);
+ await buildFile(file, outfile, options);
+}
+
async function main() {
const { options, args } = parseCLIArgs();
@@ -277,13 +319,14 @@ Usage:
echo "some markdown" | buildmd
Options:
- --outfile, -o Output path
- --outdir, -d Output directory
- --stdout Force output to stdout
- --template, -t Template path (default: _template.html)
- --stylesheet, -s Stylesheet path (default: _style.css)
- --title, -T Document title override
- --help, -h Show this help message
+ --outfile, -o Output path
+ --outdir, -d Output directory
+ --stdout Force output to stdout
+ --template, -t Template path (default: _template.html)
+ --stylesheet, -s Stylesheet path (default: _style.css)
+ --include-default-stylesheet, -S Extend default CSS instead of overwriting
+ --title, -T Document title override
+ --help, -h Show this help message
`.trim(),
);
process.exit(0);
@@ -291,22 +334,21 @@ Options:
// stdin mode
if (args.length === 0) {
- const outfile = options.outfile ? Bun.file(options.outfile) : Bun.stdout;
- await buildFile(Bun.stdin, outfile, options);
- process.exit(0);
+ return processStdin(options);
}
// file mode
for (const arg of args) {
- const file = Bun.file(arg);
+ const isDir = await isDirectory(arg);
- // Ensure input files exist
- if (!(await file.exists())) {
- throw new CLIError(`File ${arg} does not exist.`);
+ if (isDir) {
+ const dirGlob = new Glob(`${arg}/**/*.md`);
+ for await (const file of dirGlob.scan()) {
+ await processFile(options, file);
+ }
+ } else {
+ await processFile(options, arg);
}
-
- const outfile = await getOutputFile(arg, options);
- await buildFile(file, outfile, options);
}
}