Skip to content

Commit

Permalink
feat: adding reactive html export (marimo-team#1360)
Browse files Browse the repository at this point in the history
* adding reactive_html export

* doctype to avoid quirks mode

* respect config width

* clarified help message

* islands from_file static method

* switching cli export to static method

* adding ; between styles

* removing exposing cli option

---------

Co-authored-by: Myles Scolnick <myles@marimo.io>
  • Loading branch information
gvarnavi and mscolnick committed May 19, 2024
1 parent d1de084 commit ba2529a
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 4 deletions.
144 changes: 140 additions & 4 deletions marimo/_islands/island_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@
from typing import TYPE_CHECKING, List, Optional, Union, cast

from marimo import __version__, _loggers
from marimo._ast.app import App, InternalApp
from marimo._ast.app import App, InternalApp, _AppConfig
from marimo._ast.cell import Cell, CellConfig
from marimo._ast.compiler import compile_cell
from marimo._messaging.cell_output import CellOutput
from marimo._output.formatting import as_html
from marimo._output.utils import uri_encode_component
from marimo._plugins.stateless.json_output import json_output
from marimo._plugins.ui import code_editor
from marimo._server.export import run_app_until_completion
from marimo._server.file_manager import AppFileManager
from marimo._server.file_router import AppFileRouter
from marimo._utils.marimo_path import MarimoPath

if TYPE_CHECKING:
from marimo._server.session.session_view import SessionView
Expand Down Expand Up @@ -178,6 +182,42 @@ def __init__(self, app_id: str = "main"):
self._app_id = app_id
self._app = InternalApp(App())
self._stubs: List[MarimoIslandStub] = []
self._config = _AppConfig()

@staticmethod
def from_file(
filename: str,
display_code: bool = False,
) -> MarimoIslandGenerator:
"""
Create a MarimoIslandGenerator and populate MarimoIslandStubs
using code cells from a marimo *.py file.
*Args:*
- filename (str): Marimo .py filename to convert to reactive HTML.
- display_code (bool): Whether to display the code in HTML snippets.
"""
path = MarimoPath(filename)
file_router = AppFileRouter.from_filename(path)
file_key = file_router.get_unique_file_key()
assert file_key is not None
file_manager = file_router.get_file_manager(file_key)

generator = MarimoIslandGenerator()
stubs = []
for cell_data in file_manager.app.cell_manager.cell_data():
stubs.append(
generator.add_code(
cell_data.code,
display_code=display_code,
)
)

generator._stubs = stubs
generator._config = file_manager.app.config

return generator

def add_code(
self,
Expand Down Expand Up @@ -230,9 +270,6 @@ async def build(self) -> App:
- App: The built app.
"""
from marimo._server.export import run_app_until_completion
from marimo._server.file_manager import AppFileManager

if self.has_run:
raise ValueError("You can only call build() once")

Expand All @@ -257,6 +294,11 @@ def render_head(
"""
Render the header for the app.
This should be included in the <head> tag of the page.
*Args:*
- version_override (str): Marimo version to use for loaded js/css.
- _development_url (str): If True, uses local marimo islands js.
"""

# This loads:
Expand Down Expand Up @@ -316,6 +358,100 @@ def render_head(
"""
).strip()

def render_body(
self,
*,
max_width: Optional[str] = None,
margin: Optional[str] = None,
style: Optional[str] = None,
) -> str:
"""
Render the body for the app.
This should be included in the <body> tag of the page.
*Args:*
- max_width (str): CSS style max_width property.
- margin (str): CSS style margin property.
- style (str): CSS style. Overrides max_width and margin.
"""

rendered_stubs = []
for stub in self._stubs:
rendered_stubs.append(stub.render())

body = "\n".join(rendered_stubs)

if margin is None:
margin = "auto"
if max_width is None:
width = self._config.width
if width == "normal":
max_width = "740px"
elif width == "medium":
max_width = "1110px"
else:
max_width = "none"

if style is None:
style = f"margin: {margin}; max-width: {max_width};"

return dedent(
f"""
<div style="{style}">
{body}
</div>
"""
).strip()

def render_html(
self,
*,
version_override: str = __version__,
_development_url: Union[str | bool] = False,
max_width: Optional[str] = None,
margin: Optional[str] = None,
style: Optional[str] = None,
) -> str:
"""
Render reactive html for the app.
*Args:*
- version_override (str): Marimo version to use for loaded js/css.
- _development_url (str): If True, uses local marimo islands js.
- max_width (str): CSS style max_width property.
- margin (str): CSS style margin property.
- style (str): CSS style. Overrides max_width and margin.
"""
head = self.render_head(
version_override=version_override,
_development_url=_development_url,
)
body = self.render_body(
max_width=max_width, margin=margin, style=style
)
title = (
self._app_id
if self._config.app_title is None
else self._config.app_title
)

return dedent(
f"""<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title> {title} </title>
{head}
</head>
<body>
{body}
</body>
</html>
"""
).strip()


def remove_empty_lines(text: str) -> str:
return "\n".join([line for line in text.split("\n") if line.strip() != ""])
Expand Down
18 changes: 18 additions & 0 deletions marimo/_server/export/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,24 @@ async def run_app_then_export_as_html(
return html, filename


async def run_app_then_export_as_reactive_html(
path: MarimoPath,
include_code: bool,
) -> tuple[str, str]:
import os

from marimo._islands.island_generator import MarimoIslandGenerator

generator = MarimoIslandGenerator.from_file(
path.absolute_name, display_code=include_code
)
await generator.build()
html = generator.render_html()
basename = os.path.basename(path.absolute_name)
filename = f"{os.path.splitext(basename)[0]}.html"
return html, filename


async def run_app_until_completion(
file_manager: AppFileManager,
cli_args: SerializedCLIArgs,
Expand Down

0 comments on commit ba2529a

Please sign in to comment.