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); } }