wip
This commit is contained in:
@@ -5,6 +5,7 @@ package parser
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.max-richter.dev/max/marka/parser/decoders"
|
"git.max-richter.dev/max/marka/parser/decoders"
|
||||||
"git.max-richter.dev/max/marka/parser/matcher"
|
"git.max-richter.dev/max/marka/parser/matcher"
|
||||||
@@ -61,29 +62,73 @@ func MatchBlocks(markdownContent string) ([]matcher.Block, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ParseFile(markdownContent string) (any, error) {
|
func ParseFile(markdownContent string) (any, error) {
|
||||||
|
timings := make(map[string]int64)
|
||||||
|
|
||||||
|
startDetectType := time.Now()
|
||||||
markdownContent = strings.TrimSuffix(markdownContent, "\n")
|
markdownContent = strings.TrimSuffix(markdownContent, "\n")
|
||||||
|
|
||||||
contentType, err := DetectType(markdownContent)
|
contentType, err := DetectType(markdownContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not detect type: %w", err)
|
return nil, fmt.Errorf("could not detect type: %w", err)
|
||||||
}
|
}
|
||||||
|
timings["detect_type"] = time.Since(startDetectType).Milliseconds()
|
||||||
|
|
||||||
|
startGetTemplate := time.Now()
|
||||||
templateContent, err := registry.GetTemplate(contentType)
|
templateContent, err := registry.GetTemplate(contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not get schema: %w", err)
|
return nil, fmt.Errorf("could not get schema: %w", err)
|
||||||
}
|
}
|
||||||
|
timings["get_template"] = time.Since(startGetTemplate).Milliseconds()
|
||||||
|
|
||||||
|
startTemplate := time.Now()
|
||||||
tpl, err := template.CompileTemplate(templateContent)
|
tpl, err := template.CompileTemplate(templateContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to compile template: %w", err)
|
return nil, fmt.Errorf("failed to compile template: %w", err)
|
||||||
}
|
}
|
||||||
|
timings["template_compilation"] = time.Since(startTemplate).Milliseconds()
|
||||||
|
|
||||||
|
startMarkdown := time.Now()
|
||||||
blocks := matcher.MatchBlocksFuzzy(markdownContent, tpl, 0.3)
|
blocks := matcher.MatchBlocksFuzzy(markdownContent, tpl, 0.3)
|
||||||
|
|
||||||
result, err := decoders.Parse(blocks)
|
result, err := decoders.Parse(blocks)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse blocks: %w", err)
|
return nil, fmt.Errorf("failed to parse blocks: %w", err)
|
||||||
}
|
}
|
||||||
|
timings["markdown_parsing"] = time.Since(startMarkdown).Milliseconds()
|
||||||
|
|
||||||
return result, nil
|
response := map[string]any{
|
||||||
|
"data": result,
|
||||||
|
"timings": timings,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseFileWithTemplate(markdownContent string, templateContent string) (any, error) {
|
||||||
|
timings := make(map[string]int64)
|
||||||
|
|
||||||
|
startTemplate := time.Now()
|
||||||
|
markdownContent = strings.TrimSuffix(markdownContent, "\n")
|
||||||
|
|
||||||
|
tpl, err := template.CompileTemplate(templateContent)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compile template: %w", err)
|
||||||
|
}
|
||||||
|
timings["template_compilation"] = time.Since(startTemplate).Milliseconds()
|
||||||
|
|
||||||
|
startMarkdown := time.Now()
|
||||||
|
blocks := matcher.MatchBlocksFuzzy(markdownContent, tpl, 0.3)
|
||||||
|
|
||||||
|
result, err := decoders.Parse(blocks)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse blocks: %w", err)
|
||||||
|
}
|
||||||
|
timings["markdown_parsing"] = time.Since(startMarkdown).Milliseconds()
|
||||||
|
|
||||||
|
response := map[string]any{
|
||||||
|
"data": result,
|
||||||
|
"timings": timings,
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url));
|
|||||||
|
|
||||||
export default defineConfig(
|
export default defineConfig(
|
||||||
includeIgnoreFile(gitignorePath),
|
includeIgnoreFile(gitignorePath),
|
||||||
|
{ ignores: ['static/wasm_exec.js'] },
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...ts.configs.recommended,
|
...ts.configs.recommended,
|
||||||
...svelte.configs.recommended,
|
...svelte.configs.recommended,
|
||||||
|
25
playground/src/app.d.ts
vendored
25
playground/src/app.d.ts
vendored
@@ -1,22 +1,13 @@
|
|||||||
// See https://svelte.dev/docs/kit/types#app.d.ts
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
// for information about these interfaces
|
// for information about these interfaces
|
||||||
declare global {
|
declare global {
|
||||||
namespace App {
|
namespace App {
|
||||||
// interface Error {}
|
// interface Error {}
|
||||||
// interface Locals {}
|
// interface Locals {}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface PageState {}
|
// interface PageState {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Window {
|
|
||||||
Go: {
|
|
||||||
new(): {
|
|
||||||
run: (inst: WebAssembly.Instance) => Promise<void>;
|
|
||||||
importObject: WebAssembly.Imports;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { };
|
export {};
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<script src="/wasm_exec.js"></script>
|
<script src="/wasm_exec.js"></script>
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -7,26 +7,92 @@
|
|||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
children?: Snippet;
|
children?: Snippet;
|
||||||
|
headerActions?: Snippet;
|
||||||
|
subtitle?: string;
|
||||||
|
status?: 'success' | 'error';
|
||||||
|
timing?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { title, value = $bindable(), placeholder = "", readonly = false, children }: Props = $props();
|
let {
|
||||||
|
title,
|
||||||
|
value = $bindable(),
|
||||||
|
placeholder = '',
|
||||||
|
readonly = false,
|
||||||
|
children,
|
||||||
|
headerActions,
|
||||||
|
subtitle,
|
||||||
|
status,
|
||||||
|
timing
|
||||||
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col h-full border-r border-gray-200 last:border-r-0">
|
<div class="flex h-full flex-col border-r border-gray-200 last:border-r-0">
|
||||||
<div class="px-4 py-3 border-b border-gray-200 bg-gray-50/50">
|
<div class="flex items-center border-b border-gray-200 bg-gray-50/50 px-4 py-3">
|
||||||
<h2 class="font-semibold text-gray-900 text-sm uppercase tracking-wide">{title}</h2>
|
{#if status === 'success'}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="mr-2 h-5 w-5 text-green-500"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{:else if status === 'error'}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="mr-2 h-5 w-5 text-red-500"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
<div class="flex-1">
|
||||||
|
<h2 class="text-sm font-semibold tracking-wide text-gray-900 uppercase">{title}</h2>
|
||||||
|
{#if subtitle}
|
||||||
|
<p class="text-xs text-gray-500">{subtitle}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if headerActions}
|
||||||
|
<div class="ml-4">
|
||||||
|
{@render headerActions()}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if timing !== undefined}
|
||||||
|
<div class="ml-4 text-xs text-gray-500">{timing}ms</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 relative group">
|
<div class="group relative flex-1">
|
||||||
{#if readonly}
|
{#if readonly}
|
||||||
<div class="absolute inset-0 p-4 overflow-auto">
|
<div class="absolute inset-0 overflow-auto p-4">
|
||||||
<pre class="text-sm font-mono text-gray-800 whitespace-pre-wrap leading-relaxed">{value}</pre>
|
<pre
|
||||||
|
class="font-mono text-sm leading-relaxed whitespace-pre-wrap text-gray-800">{value}</pre>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<textarea bind:value {placeholder} class="absolute inset-0 w-full h-full p-4 text-sm font-mono resize-none border-0 outline-none bg-transparent text-gray-800 leading-relaxed placeholder:text-gray-400 focus:bg-gray-50/30 transition-colors" spellcheck="false"></textarea>
|
<textarea
|
||||||
|
bind:value
|
||||||
|
{placeholder}
|
||||||
|
class="absolute inset-0 h-full w-full resize-none border-0 bg-transparent p-4 font-mono text-sm leading-relaxed text-gray-800 transition-colors outline-none placeholder:text-gray-400 focus:bg-gray-50/30"
|
||||||
|
spellcheck="false"
|
||||||
|
></textarea>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Subtle hover effect -->
|
<!-- Subtle hover effect -->
|
||||||
<div class="absolute inset-0 pointer-events-none opacity-0 group-hover:opacity-5 bg-black transition-opacity duration-200"></div>
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 bg-black opacity-0 transition-opacity duration-200 group-hover:opacity-5"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if children}
|
{#if children}
|
||||||
|
@@ -1,24 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import EditorPanel from './EditorPanel.svelte';
|
import type { ParseResult } from '../wasm';
|
||||||
|
|
||||||
// Sample data based on the provided example
|
type StringArray = string[];
|
||||||
let templateValue = $state(`---
|
let templateValue = $state(``);
|
||||||
_type: Recipe
|
let templates = $state<StringArray>([]);
|
||||||
author.name: Max Richter
|
|
||||||
---
|
|
||||||
|
|
||||||
# Baguette
|
|
||||||
|
|
||||||
My favourite baguette recipe
|
|
||||||
|
|
||||||
## Ingredients
|
|
||||||
- Flour
|
|
||||||
- Water
|
|
||||||
- Salt
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
1. Mix Flour Water and Salt
|
|
||||||
2. Bake the bread`);
|
|
||||||
|
|
||||||
let markdownValue = $state(`---
|
let markdownValue = $state(`---
|
||||||
_type: Recipe
|
_type: Recipe
|
||||||
@@ -38,25 +23,46 @@ My favourite baguette recipe
|
|||||||
1. Mix Flour Water and Salt
|
1. Mix Flour Water and Salt
|
||||||
2. Bake the bread`);
|
2. Bake the bread`);
|
||||||
|
|
||||||
// Simulated JSON output - in real implementation this would be generated from the parser
|
let jsonOutput = $state('');
|
||||||
let jsonOutput = $derived(() => {
|
let timings = $state<ParseResult['timings'] | null>(null);
|
||||||
|
let status = $state<'success' | 'error' | undefined>(undefined);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($wasmReady) {
|
||||||
|
try {
|
||||||
|
templates = listTemplates();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$wasmReady) {
|
||||||
|
jsonOutput = 'Loading wasm...';
|
||||||
|
timings = null;
|
||||||
|
status = undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const output = {
|
const result = templateValue
|
||||||
_type: 'Recipe',
|
? parseMarkdownWithTemplate(markdownValue, templateValue)
|
||||||
name: 'Baguette',
|
: parseMarkdown(markdownValue);
|
||||||
author: {
|
jsonOutput = JSON.stringify(result.data, null, 2);
|
||||||
_type: 'Person',
|
timings = result.timings;
|
||||||
name: 'Max Richter'
|
status = 'success';
|
||||||
},
|
} catch (e: unknown) {
|
||||||
description: 'My favourite baguette recipe',
|
jsonOutput = (e as Error).message;
|
||||||
recipeIngredient: ['Flour', 'Water', 'Salt'],
|
timings = null;
|
||||||
recipeInstructions: ['Mix Flour Water and Salt', 'Bake the bread']
|
status = 'error';
|
||||||
};
|
|
||||||
return JSON.stringify(output, null, 2);
|
|
||||||
} catch (e) {
|
|
||||||
return 'Invalid input';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function loadTemplate(name: string) {
|
||||||
|
if (!name) return;
|
||||||
|
try {
|
||||||
|
templateValue = getTemplate(name);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="grid min-h-0 flex-1 grid-cols-1 lg:grid-cols-3">
|
<div class="grid min-h-0 flex-1 grid-cols-1 lg:grid-cols-3">
|
||||||
@@ -64,21 +70,37 @@ My favourite baguette recipe
|
|||||||
title="Template"
|
title="Template"
|
||||||
bind:value={templateValue}
|
bind:value={templateValue}
|
||||||
placeholder="Enter your Marka template here..."
|
placeholder="Enter your Marka template here..."
|
||||||
/>
|
{status}
|
||||||
|
timing={timings?.template_compilation}
|
||||||
|
subtitle="Define your mapping schema"
|
||||||
|
>
|
||||||
|
{#snippet headerActions()}
|
||||||
|
<select
|
||||||
|
onchange={(e) => loadTemplate(e.currentTarget.value)}
|
||||||
|
class="rounded border border-gray-300 bg-white px-2 py-1 text-xs text-gray-700 shadow-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 focus:outline-none"
|
||||||
|
>
|
||||||
|
<option value="">Load a template</option>
|
||||||
|
{#each templates as template (template)}
|
||||||
|
<option value={template}>{template}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{/snippet}
|
||||||
|
</EditorPanel>
|
||||||
|
|
||||||
<EditorPanel
|
<EditorPanel
|
||||||
title="Markdown"
|
title="Markdown"
|
||||||
bind:value={markdownValue}
|
bind:value={markdownValue}
|
||||||
placeholder="Enter your markdown content here..."
|
placeholder="Enter your markdown content here..."
|
||||||
|
{status}
|
||||||
|
timing={timings?.markdown_parsing}
|
||||||
|
subtitle="Your source content"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditorPanel title="Data" value={jsonOutput()} readonly={true}>
|
<EditorPanel
|
||||||
{#snippet children()}
|
title="Data"
|
||||||
<div class="absolute bottom-4 right-4 opacity-60">
|
value={jsonOutput}
|
||||||
<div class="rounded border bg-white px-2 py-1 text-xs text-gray-500">
|
readonly={true}
|
||||||
Auto-generated JSON
|
{status}
|
||||||
</div>
|
subtitle="Parsed JSON output"
|
||||||
</div>
|
/>
|
||||||
{/snippet}
|
|
||||||
</EditorPanel>
|
|
||||||
</div>
|
</div>
|
||||||
|
74
playground/src/lib/wasm.ts
Normal file
74
playground/src/lib/wasm.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { readable } from 'svelte/store';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
Go: {
|
||||||
|
new (): {
|
||||||
|
run: (inst: WebAssembly.Instance) => Promise<void>;
|
||||||
|
importObject: WebAssembly.Imports;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
markaParseFile: (input: string) => string;
|
||||||
|
markaParseFileWithTemplate: (markdown: string, template: string) => string;
|
||||||
|
markaListTemplates: () => string;
|
||||||
|
markaGetTemplate: (name: string) => string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wasmReady = readable(false, (set) => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadWasm = async () => {
|
||||||
|
const go = new window.Go();
|
||||||
|
try {
|
||||||
|
const result = await WebAssembly.instantiateStreaming(fetch('/main.wasm'), go.importObject);
|
||||||
|
go.run(result.instance);
|
||||||
|
set(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading wasm module:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === 'complete') {
|
||||||
|
loadWasm();
|
||||||
|
} else {
|
||||||
|
window.addEventListener('load', loadWasm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export interface ParseResult {
|
||||||
|
data: unknown;
|
||||||
|
timings: { [key: string]: number };
|
||||||
|
}
|
||||||
|
export function parseMarkdown(markdown: string): ParseResult {
|
||||||
|
if (typeof window.markaParseFile !== 'function') {
|
||||||
|
throw new Error('Wasm module not ready');
|
||||||
|
}
|
||||||
|
const result = window.markaParseFile(markdown);
|
||||||
|
return JSON.parse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseMarkdownWithTemplate(markdown: string, template: string): ParseResult {
|
||||||
|
if (typeof window.markaParseFileWithTemplate !== 'function') {
|
||||||
|
throw new Error('Wasm module not ready');
|
||||||
|
}
|
||||||
|
const result = window.markaParseFileWithTemplate(markdown, template);
|
||||||
|
return JSON.parse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listTemplates(): string[] {
|
||||||
|
if (typeof window.markaListTemplates !== 'function') {
|
||||||
|
throw new Error('Wasm module not ready');
|
||||||
|
}
|
||||||
|
const result = window.markaListTemplates();
|
||||||
|
return JSON.parse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTemplate(name: string): string {
|
||||||
|
if (typeof window.markaGetTemplate !== 'function') {
|
||||||
|
throw new Error('Wasm module not ready');
|
||||||
|
}
|
||||||
|
return window.markaGetTemplate(name);
|
||||||
|
}
|
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="icon" href={favicon} />
|
<link rel="icon" href={favicon} />
|
||||||
|
<script src="/wasm_exec.js"></script>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
import Playground from '$lib/components/Playground.svelte';
|
import Playground from '$lib/components/Playground.svelte';
|
||||||
import StatusBar from '$lib/components/StatusBar.svelte';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-background text-foreground flex min-h-screen flex-col">
|
<div class="bg-background text-foreground flex min-h-screen flex-col">
|
||||||
<Header />
|
<Header />
|
||||||
<Playground />
|
<Playground />
|
||||||
<StatusBar />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@@ -1,47 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {onMount} from 'svelte';
|
|
||||||
|
|
||||||
const test = `---
|
|
||||||
_type: Recipe
|
|
||||||
author.name: Max Richter
|
|
||||||
---
|
|
||||||
|
|
||||||
# Baguette
|
|
||||||
|
|
||||||
My favourite baguette recipe
|
|
||||||
|
|
||||||
## Ingredients
|
|
||||||
- Flour
|
|
||||||
- Water
|
|
||||||
- Salt
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
1. Mix Flour Water and Salt
|
|
||||||
2. Bake the bread
|
|
||||||
`
|
|
||||||
|
|
||||||
function exec(){
|
|
||||||
let start = performance.now();
|
|
||||||
const res = markaParseFile(test);
|
|
||||||
let end = performance.now();
|
|
||||||
|
|
||||||
console.log(JSON.parse(res))
|
|
||||||
|
|
||||||
start = performance.now();
|
|
||||||
const res2 = markaMatchBlocks(test);
|
|
||||||
end = performance.now();
|
|
||||||
|
|
||||||
console.log(JSON.parse(res2))
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const go = new Go();
|
|
||||||
WebAssembly.instantiateStreaming(fetch("/main.wasm"), go.importObject).then((result) => {
|
|
||||||
go.run(result.instance);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<button onclick={exec}>test</button>
|
|
Binary file not shown.
@@ -7,6 +7,7 @@ import (
|
|||||||
"syscall/js"
|
"syscall/js"
|
||||||
|
|
||||||
p "git.max-richter.dev/max/marka/parser"
|
p "git.max-richter.dev/max/marka/parser"
|
||||||
|
"git.max-richter.dev/max/marka/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
func matchBlocks(_ js.Value, args []js.Value) any {
|
func matchBlocks(_ js.Value, args []js.Value) any {
|
||||||
@@ -50,10 +51,51 @@ func parseFile(_ js.Value, args []js.Value) any {
|
|||||||
return js.ValueOf(string(b))
|
return js.ValueOf(string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseFileWithTemplate(_ js.Value, args []js.Value) any {
|
||||||
|
if len(args) < 2 {
|
||||||
|
return js.ValueOf(map[string]any{"error": "missing markdown or template"})
|
||||||
|
}
|
||||||
|
res, err := p.ParseFileWithTemplate(args[0].String(), args[1].String())
|
||||||
|
if err != nil {
|
||||||
|
return js.ValueOf(map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(res) // return JSON string to avoid reflect-heavy bridging
|
||||||
|
if err != nil {
|
||||||
|
return js.ValueOf(map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return js.ValueOf(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func listTemplates(_ js.Value, args []js.Value) any {
|
||||||
|
templates, err := registry.ListTemplates()
|
||||||
|
if err != nil {
|
||||||
|
return js.ValueOf(map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(templates)
|
||||||
|
if err != nil {
|
||||||
|
return js.ValueOf(map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return js.ValueOf(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTemplate(_ js.Value, args []js.Value) any {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return js.ValueOf(map[string]any{"error": "missing template name"})
|
||||||
|
}
|
||||||
|
template, err := registry.GetTemplate(args[0].String())
|
||||||
|
if err != nil {
|
||||||
|
return js.ValueOf(map[string]any{"error": err.Error()})
|
||||||
|
}
|
||||||
|
return js.ValueOf(template)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
js.Global().Set("markaDetectType", js.FuncOf(detectType))
|
js.Global().Set("markaDetectType", js.FuncOf(detectType))
|
||||||
js.Global().Set("markaParseFile", js.FuncOf(parseFile))
|
js.Global().Set("markaParseFile", js.FuncOf(parseFile))
|
||||||
|
js.Global().Set("markaParseFileWithTemplate", js.FuncOf(parseFileWithTemplate))
|
||||||
js.Global().Set("markaMatchBlocks", js.FuncOf(matchBlocks))
|
js.Global().Set("markaMatchBlocks", js.FuncOf(matchBlocks))
|
||||||
|
js.Global().Set("markaListTemplates", js.FuncOf(listTemplates))
|
||||||
|
js.Global().Set("markaGetTemplate", js.FuncOf(getTemplate))
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,9 @@ package registry
|
|||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
"io"
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed templates/*.marka
|
//go:embed templates/*.marka
|
||||||
@@ -23,3 +26,20 @@ func GetTemplate(name string) (string, error) {
|
|||||||
|
|
||||||
return string(templateBytes), nil
|
return string(templateBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ListTemplates() ([]string, error) {
|
||||||
|
var templateNames []string
|
||||||
|
err := fs.WalkDir(templates, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !d.IsDir() && strings.HasSuffix(path, ".marka") {
|
||||||
|
templateNames = append(templateNames, strings.TrimSuffix(filepath.Base(path), ".marka"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return templateNames, nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user