"""Common utility functions used in the compiler.""" import json import os from typing import Dict, List, Set, Tuple, 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 Component, CustomComponent, ImportDict from pynecone.state import State from pynecone.style import Style # 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 ready = templates.READY return templates.join([synced_state, result, router, socket, ready]) 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. """ # Render the component. render = component.get_component() # Get the imports. imports = { lib: fields for lib, fields in render.get_imports().items() if lib != component.library } # Concatenate the props. props = ", ".join([prop.name for prop in component.get_prop_vars()]) # Compile the component. return ( templates.COMPONENT( name=component.tag, props=props, render=render, ), 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. """ # Get the global style from the style dict. global_style = Style({k: v for k, v in style.items() if not isinstance(k, type)}) # Root styles. root_style = Style({k: v for k, v in global_style.items() if k.startswith("::")}) # Body styles. root_style["body"] = Style( {k: v for k, v in global_style.items() if k not in root_style} ) # Return the theme. return { "styles": {"global": root_style}, } 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)