Skip to content

Commit

Permalink
add components\doc\expression.ts
Browse files Browse the repository at this point in the history
  • Loading branch information
EthanThatOneKid committed Mar 23, 2024
1 parent cb27a2b commit dd09995
Show file tree
Hide file tree
Showing 40 changed files with 276 additions and 83 deletions.
46 changes: 46 additions & 0 deletions components/doc/doc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type { Meta } from "#/lib/meta/mod.ts";
import Replace from "#/components/replace.tsx";
import Playground from "#/components/playground/playground.tsx";
import {
getDataByRegExpMatchArray,
PLAYGROUND_EXPRESSION_REGEX,
} from "./expression.ts";

/**
* DocProps are the properties for the Doc component.
*/
export interface DocProps {
/**
* html is the html content of the documentation page.
*/
html: string;

/**
* meta is the meta data for the documentation page's playgrounds.
*/
meta: Meta;
}

/**
* Doc is a jsonx documentation page component.
*/
export default function Doc(props: DocProps) {
// TODO: Replace with actual MDX parsing because the async isn't working
// with Preact hydration.
return (
<Replace
html={props.html}
pattern={PLAYGROUND_EXPRESSION_REGEX}
component={async ({ match }) => {
const data = await getDataByRegExpMatchArray(match);
return (
<Playground
meta={props.meta}
code={data.code}
version={data.version}
/>
);
}}
/>
);
}
89 changes: 89 additions & 0 deletions components/doc/expression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* @fileoverview
*
* This file contains the helper functions for parsing and evaluating playground
* expressions. Playground expressions are a simple syntax for embedding
* playgrounds directly into markdown files.
*/

import { getPlayground } from "#/lib/playgrounds/deno_kv/mod.ts";
import { kv } from "#/lib/resources/kv.ts";
import { readExample } from "#/lib/examples/mod.ts";

/**
* PLAYGROUND_EXPRESSION_REGEX is the regular expression for parsing playground
* expressions.
*/
export const PLAYGROUND_EXPRESSION_REGEX =
/<!--\s*playground\s+(id|example):[a-zA-Z0-9-_.]+\s*-->/g;

/**
* fromRegExpMatchArray converts a regular expression match array to a
* PlaygroundExpression.
*/
export function fromRegExpMatchArray(
match: RegExpMatchArray,
): PlaygroundExpression {
return parsePlaygroundExpression(match[0]);
}

/**
* PlaygroundExpression is a playground expression.
*/
export type PlaygroundExpression =
| { id: string }
| { example: string };

/**
* parsePlaygroundExpression parses a playground expression.
*
* Example playground expressions:
*
* <!-- playground id:abc123 -->
* <!-- playground example:hello_world -->
*/
export function parsePlaygroundExpression(
expression: string,
): PlaygroundExpression {
const idMatch = expression.match(/id:([a-zA-Z0-9-_.]+)/);
if (idMatch) {
return { id: idMatch[1] };
}

const exampleMatch = expression.match(/example:([a-zA-Z0-9-_.]+)/);
if (exampleMatch) {
return { example: exampleMatch[1] };
}

throw new Error(`Invalid playground expression: ${expression}`);
}

/**
* getDataByRegExpMatchArray converts a regular expression match array to
* playground data.
*/
export async function getDataByRegExpMatchArray(
match: RegExpMatchArray,
): Promise<{ code: string; version?: string }> {
const expr = fromRegExpMatchArray(match);
console.log({ expr });
if ("id" in expr) {
const playground = await getPlayground(kv, expr.id);
if (!playground) {
throw new Error(`Playground not found: ${expr.id}`);
}

return { code: playground.code, version: playground.version };
}

if ("example" in expr) {
const example = await readExample(`./examples/${expr.example}`);
if (!example) {
throw new Error(`Example not found: ${expr.example}`);
}

return { code: example };
}

throw new Error(`Invalid playground expression: ${expr}`);
}
2 changes: 1 addition & 1 deletion components/nav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ToC from "#/components/toc.tsx";
import { nodes } from "#/server/docs/docs.ts";
import { nodes } from "#/lib/resources/docs.ts";

/**
* NavProps are the properties for the Nav component.
Expand Down
2 changes: 1 addition & 1 deletion components/playground/playground.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Head } from "$fresh/runtime.ts";
import type { Meta } from "#/client/meta.ts";
import type { Meta } from "#/lib/meta/mod.ts";
import PlaygroundScripts from "./scripts.tsx";

export interface PlaygroundProps {
Expand Down
50 changes: 50 additions & 0 deletions components/replace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { JSX } from "preact/jsx-runtime";

/**
* ReplaceProps
*/
export interface ReplaceProps {
/**
* html is the html content to be replaced.
*/
html: string;

/**
* pattern is the pattern to inject the component by.
*/
pattern: RegExp;

/**
* component is the component to be injected in place of the content.
*/
component: (
props: { match: RegExpMatchArray },
) => JSX.Element | Promise<JSX.Element>;
}

/**
* Replace is a component that replaces the content of the element with the content of the component.
*/
export default function Replace(props: ReplaceProps) {
const segments = props.html.split(props.pattern);
if (segments.length === 1) {
return <Raw>{props.html}</Raw>;
}

const matches = [...props.html.matchAll(props.pattern)];
return (
<>
{segments.map(async (segment, index) => (
<>
<Raw>{segment}</Raw>
{index < matches.length &&
await props.component({ match: matches[index] })}
</>
))}
</>
);
}

function Raw(props: { children: string }) {
return <div dangerouslySetInnerHTML={{ __html: props.children }}></div>;
}
3 changes: 1 addition & 2 deletions components/toc.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Node } from "#/server/docs/nodes.ts";
import type { FSItem } from "#/server/docs/items.ts";
import type { FSItem, Node } from "#/lib/docs/mod_client.ts";

/**
* ToCProps are the properties for the ToC component.
Expand Down
2 changes: 2 additions & 0 deletions server/docs/00_index.md → docs/00_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ title: Overview
# Overview

The jsonx library exposes a JSX runtime for composing JSON data.

<!-- playground example:01_animals.tsx -->
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion server/docs/98_api_docs.md → docs/98_api_docs.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
title: API Docs
href: https://jsr.io/@fartlabs/jsonx
---
---
File renamed without changes.
File renamed without changes.
8 changes: 4 additions & 4 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

import * as $_404 from "./routes/_404.tsx";
import * as $_app from "./routes/_app.tsx";
import * as $api_examples_id_ from "./routes/api/examples/[id].ts";
import * as $api_examples_name_ from "./routes/api/examples/[name].ts";
import * as $api_meta from "./routes/api/meta.ts";
import * as $api_playgrounds_id_ from "./routes/api/playgrounds/[id].ts";
import * as $api_playgrounds_index from "./routes/api/playgrounds/index.ts";
import * as $index from "./routes/index.tsx";
import * as $meta from "./routes/meta.ts";
import * as $playgrounds_id_ from "./routes/playgrounds/[id].tsx";

import { type Manifest } from "$fresh/server.ts";
Expand All @@ -17,11 +17,11 @@ const manifest = {
routes: {
"./routes/_404.tsx": $_404,
"./routes/_app.tsx": $_app,
"./routes/api/examples/[id].ts": $api_examples_id_,
"./routes/api/examples/[name].ts": $api_examples_name_,
"./routes/api/meta.ts": $api_meta,
"./routes/api/playgrounds/[id].ts": $api_playgrounds_id_,
"./routes/api/playgrounds/index.ts": $api_playgrounds_index,
"./routes/index.tsx": $index,
"./routes/meta.ts": $meta,
"./routes/playgrounds/[id].tsx": $playgrounds_id_,
},
islands: {},
Expand Down
6 changes: 3 additions & 3 deletions server/docs/fs.ts → lib/docs/fs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RenderOptions } from "@deno/gfm";
import { render } from "@deno/gfm";
import { test } from "@std/front-matter";
import { extract } from "@std/front-matter/any";
import { test } from "@std/front-matter/test";
import { walk } from "@std/fs";
import { fromFileUrl, normalize, parse, SEPARATOR_PATTERN } from "@std/path";
import type { FSItem } from "./items.ts";
Expand Down Expand Up @@ -59,7 +59,6 @@ export async function readFSItems(
);
for await (const file of walkIt) {
let md = await Deno.readTextFile(file.path);
const html = render(md, options.renderOptions);
const path = parse(file.path);

// Remove index suffix from the name.
Expand Down Expand Up @@ -94,7 +93,8 @@ export async function readFSItems(
items.push(item);

// Store the item contents.
contents.set(file.path, { md, html });
const html = render(md, options.renderOptions);
contents.set(name.join(NAME_SEPARATOR), { md, html });
}

// Return items relative to the root.
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions lib/docs/mod_client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./items.ts";
export * from "./nodes.ts";
2 changes: 2 additions & 0 deletions lib/docs/mod_server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./mod_client.ts";
export * from "./fs.ts";
File renamed without changes.
24 changes: 24 additions & 0 deletions lib/examples/examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* readExample reads the example file and returns the content.
*/
export async function readExample(path: string | URL): Promise<string | null> {
try {
const text = await Deno.readTextFile(path);
return trimJSXImportSource(text);
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
return null;
}

throw error;
}
}

function trimJSXImportSource(code: string): string {
const jsxImportSource = "/** @jsxImportSource @fartlabs/jsonx */\n\n";
if (code.startsWith(jsxImportSource)) {
return code.substring(jsxImportSource.length);
}

return code;
}
File renamed without changes.
8 changes: 5 additions & 3 deletions client/meta.ts → lib/meta/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ function playgroundMeta({ latest, versions }: {
// https://github.com/FartLabs/jsonx/issues/13
const minCompatible = parse("0.0.8");
return {
latest: latest,
latest,
versions: Object.keys(versions)
.filter((versionTag) => greaterThan(parse(versionTag), minCompatible))
.sort((a, b) => compare(parse(b), parse(a))),
.map((versionTag) => parse(versionTag))
.filter((v) => greaterThan(v, minCompatible))
.sort((a, b) => compare(b, a))
.map((v) => v.toString()),
};
}
1 change: 1 addition & 0 deletions lib/meta/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./meta.ts";
5 changes: 4 additions & 1 deletion server/kv/playgrounds.ts → lib/playgrounds/deno_kv/kv.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/// <reference lib="deno.unstable" />

import { ulid } from "@std/ulid";
import type { AddPlaygroundRequest, Playground } from "#/client/playgrounds.ts";
import type {
AddPlaygroundRequest,
Playground,
} from "#/lib/playgrounds/mod.ts";

/**
* getPlayground gets a playground by ID.
Expand Down
1 change: 1 addition & 0 deletions lib/playgrounds/deno_kv/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./kv.ts";
1 change: 1 addition & 0 deletions lib/playgrounds/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./playgrounds.ts";
5 changes: 5 additions & 0 deletions client/playgrounds.ts → lib/playgrounds/playgrounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export interface Playground {
* AddPlaygroundRequest is the request to add a playground.
*/
export type AddPlaygroundRequest = Omit<Playground, "id">;

// TODO: Write HTML comment parser for rendering comments in playground.

// function parsePlaygroundExpression(expression: string): string {
// }
13 changes: 13 additions & 0 deletions lib/resources/docs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { readFSItems } from "#/lib/docs/mod_server.ts";

export const { items, contents, nodes } = await readFSItems({
root: "./docs",
isIndex: (suffix) => suffix.startsWith("00_"),
renderOptions: {
// Preserve HTML comments as-is.
disableHtmlSanitization: true,
//
// TODO: Define the base URL for the site with environment variables.
//
},
});
3 changes: 3 additions & 0 deletions lib/resources/examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { readExample } from "#/lib/examples/examples.ts";

export const defaultExample = (await readExample("./examples/01_animals.tsx"))!;
File renamed without changes.
4 changes: 2 additions & 2 deletions routes/api/examples/[id].ts → routes/api/examples/[name].ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { Handlers } from "$fresh/server.ts";
import { getExampleByName } from "#/server/examples/mod.ts";
import { readExample } from "#/lib/examples/mod.ts";

export const handler: Handlers = {
async GET(_request, ctx) {
const example = await getExampleByName(ctx.params.id);
const example = await readExample(`./examples/${ctx.params.name}`);
return new Response(example);
},
};
2 changes: 1 addition & 1 deletion routes/meta.ts → routes/api/meta.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Handlers } from "$fresh/server.ts";
import { getMeta } from "#/client/meta.ts";
import { getMeta } from "#/lib/meta/mod.ts";

export const handler: Handlers = {
async GET(_request, _ctx) {
Expand Down
7 changes: 2 additions & 5 deletions routes/api/playgrounds/[id].ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type { Handlers } from "$fresh/server.ts";
import {
getPlayground,
setPlayground,
} from "../../../server/kv/playgrounds.ts";
import { kv } from "#/server/kv/kv.ts";
import { kv } from "#/lib/resources/kv.ts";
import { getPlayground, setPlayground } from "#/lib/playgrounds/deno_kv/mod.ts";

export const handler: Handlers = {
async GET(_request, ctx) {
Expand Down
Loading

1 comment on commit dd09995

@deno-deploy
Copy link

@deno-deploy deno-deploy bot commented on dd09995 Mar 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Failed to deploy:

Relative import path "@std/front-matter/any" not prefixed with / or ./ or ../ and not in import map from "file:///src/lib/docs/fs.ts"

Please sign in to comment.