reflex/pynecone/compiler/utils.py
2023-01-02 16:08:00 -08:00

306 lines
7.4 KiB
Python

"""Common utility functions used in the compiler."""
from __future__ import annotations
import inspect
import json
import os
from typing import TYPE_CHECKING, Any, Dict, List, Set, Type
from pynecone import constants, utils
from pynecone.compiler import templates
from pynecone.components.base import (
Body,
Description,
DocumentHead,
Head,
Html,
Image,
Link,
Main,
Script,
Title,
)
from pynecone.components.component import ImportDict
from pynecone.state import State
from pynecone.style import Style
from pynecone.var import BaseVar, Var
if TYPE_CHECKING:
from pynecone.components.component import Component, CustomComponent
# To re-export this function.
merge_imports = utils.merge_imports
def compile_import_statement(lib: str, fields: Set[str]) -> str:
"""Compile an import statement.
Args:
lib: The library to import from.
fields: The set of fields to import from the library.
Returns:
The compiled import statement.
"""
# Check for default imports.
defaults = {
field
for field in fields
if field.lower() == lib.lower().replace("-", "").replace("/", "")
}
assert len(defaults) < 2
# Get the default import, and the specific imports.
default = next(iter(defaults), "")
rest = fields - defaults
return templates.format_import(lib=lib, default=default, rest=rest)
def compile_imports(imports: ImportDict) -> str:
"""Compile an import dict.
Args:
imports: The import dict to compile.
Returns:
The compiled import dict.
"""
return templates.join(
[compile_import_statement(lib, fields) for lib, fields in imports.items()]
)
def compile_constant_declaration(name: str, value: str) -> str:
"""Compile a constant declaration.
Args:
name: The name of the constant.
value: The value of the constant.
Returns:
The compiled constant declaration.
"""
return templates.CONST(name=name, value=json.dumps(value))
def compile_constants() -> str:
"""Compile all the necessary constants.
Returns:
A string of all the compiled constants.
"""
endpoint = constants.Endpoint.EVENT
return templates.join(
[compile_constant_declaration(name=endpoint.name, value=endpoint.get_url())]
)
def compile_state(state: Type[State]) -> str:
"""Compile the state of the app.
Args:
state: The app state object.
Returns:
A string of the compiled state.
"""
initial_state = state().dict()
initial_state.update(
{
"events": [{"name": utils.get_hydrate_event(state)}],
}
)
initial_state = utils.format_state(initial_state)
synced_state = templates.format_state(
state=state.get_name(), initial_state=json.dumps(initial_state)
)
initial_result = {
constants.STATE: None,
constants.EVENTS: [],
constants.PROCESSING: False,
}
result = templates.format_state(
state="result",
initial_state=json.dumps(initial_result),
)
router = templates.ROUTER
socket = templates.SOCKET
return templates.join([synced_state, result, router, socket])
def compile_events(state: Type[State]) -> str:
"""Compile all the events for a given component.
Args:
state: The state class for the component.
Returns:
A string of the compiled events for the component.
"""
state_name = state.get_name()
state_setter = templates.format_state_setter(state_name)
return templates.EVENT_FN(state=state_name, set_state=state_setter)
def compile_effects(state: Type[State]) -> str:
"""Compile all the effects for a given component.
Args:
state: The state class for the component.
Returns:
A string of the compiled effects for the component.
"""
state_name = state.get_name()
set_state = templates.format_state_setter(state_name)
return templates.USE_EFFECT(state=state_name, set_state=set_state)
def compile_render(component: Component) -> str:
"""Compile the component's render method.
Args:
component: The component to compile the render method for.
Returns:
A string of the compiled render method.
"""
return component.render()
def compile_custom_component(component: CustomComponent) -> tuple[str, ImportDict]:
"""Compile a custom component.
Args:
component: The custom component to compile.
Returns:
A tuple of the compiled component and the imports required by the component.
"""
props = [
BaseVar(
name=name,
type_=prop.type_ if utils._isinstance(prop, Var) else type(prop),
is_local=True,
)
for name, prop in component.props.items()
]
# Compile the component.
render = component.component_fn(*props)
# Concatenate the props.
props = ", ".join([prop.name for prop in props])
# Compile the component.
return (
templates.COMPONENT(
name=component.tag,
props=props,
render=render,
),
render.get_imports(),
)
def create_document_root(stylesheets: List[str]) -> Component:
"""Create the document root.
Args:
stylesheets: The list of stylesheets to include in the document root.
Returns:
The document root.
"""
sheets = [Link.create(rel="stylesheet", href=href) for href in stylesheets]
return Html.create(
DocumentHead.create(*sheets),
Body.create(
Main.create(),
Script.create(),
),
)
def create_theme(style: Style) -> Dict:
"""Create the base style for the app.
Args:
style: The style dict for the app.
Returns:
The base style for the app.
"""
return {
"styles": {
"global": Style({k: v for k, v in style.items() if not isinstance(k, type)})
},
}
def get_page_path(path: str) -> str:
"""Get the path of the compiled JS file for the given page.
Args:
path: The path of the page.
Returns:
The path of the compiled JS file.
"""
return os.path.join(constants.WEB_PAGES_DIR, path + constants.JS_EXT)
def get_theme_path() -> str:
"""Get the path of the base theme style.
Returns:
The path of the theme style.
"""
return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
def get_components_path() -> str:
"""Get the path of the compiled components.
Returns:
The path of the compiled components.
"""
return os.path.join(constants.WEB_UTILS_DIR, "components" + constants.JS_EXT)
def add_meta(page: Component, title: str, image: str, description: str) -> Component:
"""Add metadata to a page.
Args:
page: The component for the page.
title: The title of the page.
image: The image for the page.
description: The description of the page.
Returns:
The component with the metadata added.
"""
page.children.append(
Head.create(
Title.create(title),
Description.create(content=description),
Image.create(content=image),
)
)
return page
def write_page(path: str, code: str):
"""Write the given code to the given path.
Args:
path: The path to write the code to.
code: The code to write.
"""
utils.mkdir(os.path.dirname(path))
with open(path, "w") as f:
f.write(code)