Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
CirnoV committed Nov 5, 2024
1 parent 1b5a0ce commit be658d2
Showing 1 changed file with 213 additions and 134 deletions.
347 changes: 213 additions & 134 deletions src/layouts/sidebar/CodePreview.tsx
Original file line number Diff line number Diff line change
@@ -1,134 +1,31 @@
import { type JSXElement } from "solid-js";
import { Portal } from "solid-js/web";
import { match, P } from "ts-pattern";

interface Props {
children?: JSXElement;
}

// export type Interpolation<Sections extends string> =
// | [Sections, string]
// | (() => [Sections, string]);

// const file =
// <
// File extends FileDefinition,
// Sections extends string = File["sections"][number],
// Parameters extends object = File["parameters"],
// >(
// fileDef: File,
// ) =>
// (
// codes: TemplateStringsArray,
// ...interpolations: Interpolation<Sections>[]
// ): {
// fileName: string;
// sections: {
// [key in Sections]: "";
// };
// code: string;
// } => {
// const sections = fileDef.sections as Sections[];
// return {
// fileName: fileDef.fileName,
// sections: sections.reduce(
// (acc, section) => {
// acc[section] = "";
// return acc;
// },
// {} as { [key in Sections]: "" },
// ),
// code: "",
// };
// };
// const section =
// <Section extends string>(section: Section) =>
// (codes: TemplateStringsArray): (() => [section: Section, code: string]) => {
// return () => [section, codes.join("")];
// };

// type FileDefinition = {
// fileName: string;
// sections: string[];
// parameters: object;
// };
// const a = {
// fileName: "test.ts",
// sections: ["a", "b"],
// parameters: { smartRouting: true },
// } as const satisfies FileDefinition;

// const _f = file(a)`
// ${section("a")``}
// ${() => section("b")`
// test
// `}

// `;

type FileDefinition = {
export type CodeForPreview<FileName extends string, Sections extends string> = {
fileName: FileName;
code: string;
sections: Map<Sections, Section>;
};

export type FileDefinition = {
fileName: string;
sections: string[];
};
type options = {

export type options = {
smartRouting: boolean;
};
const a = {
fileName: "test.ts",
sections: ["a", "b"],
} as const satisfies FileDefinition;

const generateFile = <
Params extends object,
FileDef extends FileDefinition,
Sections extends string = FileDef["sections"][number],
>(
fileDef: FileDef,
): CodeTemplateFunction<Params, Sections> => {
return code<Params, Sections>;
export type Section = {
startLine: number;
endLine: number;
};

function sectionCode<Params extends object, Sections extends string>(
section: Sections,
): CodeTemplateFunction<Params, Sections> {
return code<Params, Sections>;
}

const whenCode =
<Params extends object, Sections extends string>(params: Params) =>
(key: keyof Params, condition: Params[typeof key]) => {
return (...args: Parameters<CodeTemplateFunction<Params, Sections>>) => {
return (value: Params[typeof key]) => {
if (value === condition) {
return code<Params, Sections>(...args);
}
return () => "";
};
};
};

function code<Params extends object, Sections extends string>(
codes: TemplateStringsArray,
...interpolations: Interpolation<Params, Sections>[]
): string {
const result: Interpolation<Params, Sections>[] = [codes[0]];
for (let i = 0, len = interpolations.length; i < len; i++) {
const interpolation = interpolations[i]!;
if (typeof interpolation === "function") {
result.push(
interpolation({
section: sectionCode<Params, Sections>,
when: whenCode<Params, Sections>,
}),
);
} else {
result.push(interpolation);
}
result.push(codes[i + 1]);
}

return result.join("");
}

type Interpolation<Params extends object, Sections extends string> =
| GenerateCode<Params, Sections>
| string
Expand All @@ -141,37 +38,219 @@ type Interpolation<Params extends object, Sections extends string> =
type CodeTemplateFunction<Params extends object, Sections extends string> = (
codes: TemplateStringsArray,
...interpolations: Interpolation<Params, Sections>[]
) => string;
) => (params: Params) => CodeResult<Sections>;

type GenerateCode<Params extends object, Sections extends string> = (func: {
section: (section: Sections) => CodeTemplateFunction<Params, Sections>;
when: <K extends keyof Params>(
key: K,
value: Params[K],
) => (
...args: Parameters<CodeTemplateFunction<Params, Sections>>
) => (value: Params[K]) => CodeTemplateFunction<Params, Sections>;
}) => string;

const _f = generateFile<options, typeof a>(a)`
${({ section }) => section("a")`
test4
${({ when }) => when("smartRouting", true)`
test3
`}
`}
${({ when }) => when("smartRouting", true)`
test2
`}
tayw`;
) => CodeTemplateFunction<Params, Sections>;
}) => (params: Params) => CodeResult<Sections>;

type CodeResult<Sections extends string> = {
code: string;
sections: Map<Sections, Section>;
};

function countLines(text: string): number {
return text.split("\n").length;
}

class CodeGenerator<Params extends object, Sections extends string> {
codeString: string;
sections: Map<Sections, Section>;
currentLine: number;
params: Params;

constructor(params: Params) {
this.codeString = "";
this.sections = new Map<Sections, Section>();
this.currentLine = 1;
this.params = params;
}

generate(
_codes: TemplateStringsArray,
interpolations: Interpolation<Params, Sections>[],
) {
const codes = trimCodes(_codes);
for (let i = 0; i < codes.length; i++) {
const literal = codes[i]!;
this.append(literal);

if (i < interpolations.length) {
const interpolation = interpolations[i]!;
this.processInterpolation(interpolation);
}
}
}

append(text: string) {
this.codeString += text;
this.currentLine += countLines(text) - 1;
}

processInterpolation(interpolation: Interpolation<Params, Sections>) {
if (typeof interpolation === "function") {
const result = interpolation({
section: this.sectionResolver(),
when: this.whenResolver(),
})(this.params);

this.append(result.code);

// Merge sections
for (const [key, value] of result.sections) {
this.sections.set(key, value);
}
} else if (Array.isArray(interpolation)) {
for (const item of interpolation) {
this.processInterpolation(item);
}
} else {
const interpolationStr = String(interpolation);
this.append(interpolationStr);
}
}

sectionResolver() {
return (sectionName: Sections) =>
(
codes: TemplateStringsArray,
...interpolations: Interpolation<Params, Sections>[]
) =>
(params: Params): CodeResult<Sections> => {
const startLine = this.currentLine;

const generator = new CodeGenerator<Params, Sections>(params);
generator.currentLine = startLine;
generator.generate(codes, interpolations);

this.append(generator.codeString);

const endLine = this.currentLine;

this.sections.set(sectionName, { startLine, endLine });

// Merge nested sections
for (const [key, value] of generator.sections) {
this.sections.set(key, value);
}

// Return an empty result since we've already appended the code
return { code: "", sections: new Map<Sections, Section>() };
};
}

whenResolver() {
return <K extends keyof Params>(key: K, condition: Params[K]) =>
(
codes: TemplateStringsArray,
...interpolations: Interpolation<Params, Sections>[]
) =>
(params: Params): CodeResult<Sections> => {
if (params[key] === condition) {
const generator = new CodeGenerator<Params, Sections>(params);
generator.currentLine = this.currentLine; // Start from the current line
generator.generate(codes, interpolations);

this.append(generator.codeString);

// Merge nested sections
for (const [key, value] of generator.sections) {
this.sections.set(key, value);
}

// Update current line
this.currentLine = generator.currentLine;
}

// Return an empty result
return { code: "", sections: new Map<Sections, Section>() };
};
}
}

const code =
<Params extends object, Sections extends string>(
codes: TemplateStringsArray,
...interpolations: Interpolation<Params, Sections>[]
) =>
(params: Params): CodeResult<Sections> => {
const generator = new CodeGenerator<Params, Sections>(params);
generator.generate(codes, interpolations);
return { code: generator.codeString, sections: generator.sections };
};

function trimCodes(codes: TemplateStringsArray): readonly string[] {
const [first, ...rest] = codes;
const last = rest.pop();
return match([first, last])
.with([P.string, P.string], ([first, last]) => [
first.trimStart(),
...rest,
last.trimEnd(),
])
.with([P.string, P.nullish], ([first]) => [first.trim(), ...rest])
.with([P.nullish, P._], () => [])
.exhaustive();
}

export const createPreviewFile =
<
Params extends object,
FileDef extends FileDefinition,
Sections extends string = FileDef["sections"][number],
>(
fileDef: FileDef,
) =>
(
codes: TemplateStringsArray,
...interpolations: Interpolation<Params, Sections>[]
) =>
(params: Params): CodeForPreview<FileDef["fileName"], Sections> => {
const result = code<Params, Sections>(codes, ...interpolations)(params);
return {
code: result.code,
fileName: fileDef.fileName,
sections: result.sections,
};
};

// Example usage
const _a = {
fileName: "a",
sections: ["a", "b", "c"],
} as const satisfies FileDefinition;

const _test = createPreviewFile<options, typeof _a>(_a)`
export default function CodePreview(props: Props) {
const ref: HTMLDivElement | undefined = undefined;
return (
<>
${({ section }) => section("a")`<div>{props.children}</div>`}
${({ when }) => when("smartRouting", false)`
${({ section }) => section("b")`
<Portal mount={document.getElementById("docs-right-sidebar")!} ref={ref}>
${({ section }) => section("c")`<div class="w-133">{_test({ smartRouting: false }).code}</div>`}
</Portal>
`}
`}
</>
);
}`;

console.log(_test({ smartRouting: false }));
console.log(_test({ smartRouting: false }).code);

export default function CodePreview(props: Props) {
const ref: HTMLDivElement | undefined = undefined;
return (
<>
<div>{props.children}</div>
<Portal mount={document.getElementById("docs-right-sidebar")!} ref={ref}>
<div class="w-133">{_f}</div>
<div class="w-133">{_test({ smartRouting: false }).code}</div>
</Portal>
</>
);
Expand Down

0 comments on commit be658d2

Please sign in to comment.