Skip to content

Commit

Permalink
Implement plugin as executable
Browse files Browse the repository at this point in the history
  • Loading branch information
mscolnick committed Sep 18, 2024
1 parent 3da9d7e commit c4766f9
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 27 deletions.
10 changes: 2 additions & 8 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
---
title: Example
title: homepage
---

```{marimo}
:name: marimo-island
:caption: Marimo Island Example
:app-id: main
:version: 0.6.2
import marimo as mo
import sys
Expand All @@ -16,6 +11,5 @@ if "pyodide" in sys.modules:
await micropip.install('cowsay')
import cowsay
cow_string = cowsay.get_output_string('cow', 'Hello, Marimo!!!')
cow_string
cowsay.get_output_string('cow', 'Hello, Marimo!!!')
```
5 changes: 4 additions & 1 deletion docs/myst.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
version: 1
project:
id: 0595192a-6571-427c-a258-4c0bbb80e2fb
title: Marimo Extension Example
title: marimo extension example
# description:
# keywords: []
# authors: []
github: https://github.com/curvenote/myst-ext-marimo
# To autogenerate a Table of Contents, run "myst init --write-toc"
plugins:
- type: executable
path: ../py/executable.py
site:
template: book-theme
# options:
Expand Down
137 changes: 137 additions & 0 deletions py/executable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
#!/usr/bin/env python3
import argparse
import asyncio
import json
import sys
from typing import Any, Generator

plugin: dict[Any, Any] = {
"name": "Run marimo contents",
"directives": [
{
"name": "marimo",
}
],
"transforms": [
{
"name": "marimo",
"doc": "This transform picks up any directives with ```{marimo} and generates the marimo islands",
"stage": "document",
}
],
}

# - type: mystDirective
# name: marimo
# value: |-
# import marimo as mo
# mo.md("Hello, marimo!")

DIRECTIVE_TYPE = "mystDirective"
DIRECTIVE_NAME = "marimo"


def find_all_by_type(
node: dict[Any, Any], type_: str, name: str
) -> Generator[dict[Any, Any], None, None]:
"""Simple node visitor that matches a particular node type
:param parent: starting node
:param type_: type of the node to search for
"""
if node["type"] == type_:
yield node

if "children" not in node:
return
for next_node in node["children"]:
yield from find_all_by_type(next_node, type_, name)


def declare_result(content: dict[Any, Any]):
"""Declare result as JSON to stdout
:param content: content to declare as the result
"""

# Format result and write to stdout
json.dump(content, sys.stdout, indent=2)
# Successfully exit
raise SystemExit(0)


def run_transform(name: str, data: dict[Any, Any]) -> dict[Any, Any]:
"""Execute a transform with the given name and data
:param name: name of the transform to run
:param data: AST of the document upon which the transform is being run
"""
assert name == "marimo"
try:
import marimo
except ImportError:
sys.stderr.write(
"Error: marimo is not installed. Install with 'pip install marimo'\n"
)
sys.exit(1)

try:
import htmlmin
except ImportError:
sys.stderr.write(
"Error: htmlmin is not installed. Install with 'pip install htmlmin'\n"
)
sys.exit(1)

generator = marimo.MarimoIslandGenerator()
outputs: list[Any] = []

# First pass, collect code and add placeholder
for idx, marimo_node in enumerate(
find_all_by_type(data, DIRECTIVE_TYPE, DIRECTIVE_NAME)
):
code = marimo_node["value"]
outputs.append(generator.add_code(code))
marimo_node["name"] = "marimo-placeholder"
marimo_node["value"] = idx

# Run generator
asyncio.run(generator.build())

# Second pass, replace placeholder
for idx, marimo_node in enumerate(
find_all_by_type(data, DIRECTIVE_TYPE, "marimo-placeholder")
):
marimo_node["type"] = "html"
del marimo_node["name"]
html = outputs[idx].render()
# TODO: maybe don't need to minify
minified_html: str = htmlmin.minify(str(html), remove_empty_space=True)
marimo_node["value"] = minified_html

# Add header as first child
if data["type"] == "root":
header_node = {"type": "html", "value": generator.render_header()}
data["children"].insert(0, header_node)

return data


if __name__ == "__main__":
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("--role")
group.add_argument("--directive")
group.add_argument("--transform")
args = parser.parse_args()

if args.directive:
# Noop, will be handled in run_transform
pass
elif args.transform:
data = json.load(sys.stdin)
declare_result(run_transform(args.transform, data))
elif args.role:
raise NotImplementedError
else:
declare_result(plugin)
19 changes: 19 additions & 0 deletions src/marimoDirective.ts → src/directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,22 @@ export const marimoDirective: DirectiveSpec = {
return [marimo];
},
};

export const marimoHeadDirective: DirectiveSpec = {
name: 'marimo head',
doc: 'Required link/assets for marimo',
options: {
version: { type: String, doc: 'the version string of marimo renderer' },
},
body: {
type: String,
},
run(data, vfile, ctx) {
const marimo = {
type: 'marimo-head',
data: { ...data.options },
value: data.body as string,
};
return [marimo];
},
};
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { MystPlugin } from 'myst-common';
import { marimoDirective } from './marimoDirective.js';
import { marimoHeadDirective, marimoDirective } from './directives.js';

const plugin: MystPlugin = {
name: 'Marimo example',
directives: [marimoDirective],
name: 'marimo plugin',
directives: [marimoHeadDirective, marimoDirective],
roles: [],
transforms: [],
};
Expand Down
38 changes: 23 additions & 15 deletions src/react/index.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,44 @@
import { type GenericNode } from 'myst-common';
import { type NodeRenderers } from '@myst-theme/providers';
import type { GenericNode } from 'myst-common';
import type { NodeRenderers } from '@myst-theme/providers';
import { useEffect } from 'react';

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace React.JSX {
interface IntrinsicElements {
['marimo-island']: {
'marimo-island': {
'data-app-id': string;
'data-cell-id': string;
'data-reactive': string;
children: any;
children: React.ReactNode;
};
['marimo-cell-output']: Record<string, never>;
['marimo-cell-code']: { hidden: boolean; children: any };
'marimo-cell-output': Record<string, never>;
'marimo-cell-code': { hidden: boolean; children: React.ReactNode };
}
}
}

export function MarimoRenderer({ node }: { node: GenericNode }) {
return (
<marimo-island data-app-id="main" data-cell-id="Hbol" data-reactive="true">
<marimo-cell-output />
<marimo-cell-code hidden>{encodeURIComponent(node.value || '')}</marimo-cell-code>
</marimo-island>
);
}

export function MarimoHeadRenderer({ node }: { node: GenericNode<{ version?: string }> }) {
const version = node.version || 'latest';

useEffect(() => {
// Create the script element
const script = document.createElement('script');
script.type = 'module';
script.src = 'https://cdn.jsdelivr.net/npm/@marimo-team/islands@0.6.2/dist/main.js';
script.src = `https://cdn.jsdelivr.net/npm/@marimo-team/islands@${version}/dist/main.js`;

// Create the link element
const link = document.createElement('link');
link.href = 'https://cdn.jsdelivr.net/npm/@marimo-team/islands@0.6.2/dist/style.css';
link.href = `https://cdn.jsdelivr.net/npm/@marimo-team/islands@${version}/dist/style.css`;
link.rel = 'stylesheet';
link.crossOrigin = 'anonymous';

Expand All @@ -40,17 +51,14 @@ export function MarimoRenderer({ node }: { node: GenericNode }) {
document.head.removeChild(script);
document.head.removeChild(link);
};
}, []);
return (
<marimo-island data-app-id="main" data-cell-id="Hbol" data-reactive="true">
<marimo-cell-output />
<marimo-cell-code hidden>{encodeURIComponent(node.value || '')}</marimo-cell-code>
</marimo-island>
);
}, [version]);

return null;
}

const RENDERERS: NodeRenderers = {
marimo: MarimoRenderer,
marimoHead: MarimoHeadRenderer,
};

export default RENDERERS;

0 comments on commit c4766f9

Please sign in to comment.