utils.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. """Common utility functions used in the compiler."""
  2. import json
  3. import os
  4. from typing import Dict, List, Optional, Set, Tuple, Type
  5. from pynecone import constants, utils
  6. from pynecone.compiler import templates
  7. from pynecone.components.base import (
  8. Body,
  9. ColorModeScript,
  10. Description,
  11. DocumentHead,
  12. Head,
  13. Html,
  14. Image,
  15. Link,
  16. Main,
  17. Script,
  18. Title,
  19. )
  20. from pynecone.components.component import Component, CustomComponent, ImportDict
  21. from pynecone.state import State
  22. from pynecone.style import Style
  23. # To re-export this function.
  24. merge_imports = utils.merge_imports
  25. def compile_import_statement(lib: str, fields: Set[str]) -> str:
  26. """Compile an import statement.
  27. Args:
  28. lib: The library to import from.
  29. fields: The set of fields to import from the library.
  30. Returns:
  31. The compiled import statement.
  32. """
  33. # Check for default imports.
  34. defaults = {
  35. field
  36. for field in fields
  37. if field.lower() == lib.lower().replace("-", "").replace("/", "")
  38. }
  39. assert len(defaults) < 2
  40. # Get the default import, and the specific imports.
  41. default = next(iter(defaults), "")
  42. rest = fields - defaults
  43. return templates.format_import(lib=lib, default=default, rest=rest)
  44. def compile_imports(imports: ImportDict) -> str:
  45. """Compile an import dict.
  46. Args:
  47. imports: The import dict to compile.
  48. Returns:
  49. The compiled import dict.
  50. """
  51. return templates.join(
  52. [compile_import_statement(lib, fields) for lib, fields in imports.items()]
  53. )
  54. def compile_constant_declaration(name: str, value: str) -> str:
  55. """Compile a constant declaration.
  56. Args:
  57. name: The name of the constant.
  58. value: The value of the constant.
  59. Returns:
  60. The compiled constant declaration.
  61. """
  62. return templates.CONST(name=name, value=json.dumps(value))
  63. def compile_constants() -> str:
  64. """Compile all the necessary constants.
  65. Returns:
  66. A string of all the compiled constants.
  67. """
  68. endpoint = constants.Endpoint.EVENT
  69. return templates.join(
  70. [compile_constant_declaration(name=endpoint.name, value=endpoint.get_url())]
  71. )
  72. def compile_state(state: Type[State]) -> str:
  73. """Compile the state of the app.
  74. Args:
  75. state: The app state object.
  76. Returns:
  77. A string of the compiled state.
  78. """
  79. initial_state = state().dict()
  80. initial_state.update(
  81. {
  82. "events": [{"name": utils.get_hydrate_event(state)}],
  83. }
  84. )
  85. initial_state = utils.format_state(initial_state)
  86. synced_state = templates.format_state(
  87. state=state.get_name(), initial_state=json.dumps(initial_state)
  88. )
  89. initial_result = {
  90. constants.STATE: None,
  91. constants.EVENTS: [],
  92. constants.PROCESSING: False,
  93. }
  94. result = templates.format_state(
  95. state="result",
  96. initial_state=json.dumps(initial_result),
  97. )
  98. router = templates.ROUTER
  99. socket = templates.SOCKET
  100. ready = templates.READY
  101. color_toggle = templates.COLORTOGGLE
  102. return templates.join([synced_state, result, router, socket, ready, color_toggle])
  103. def compile_events(state: Type[State]) -> str:
  104. """Compile all the events for a given component.
  105. Args:
  106. state: The state class for the component.
  107. Returns:
  108. A string of the compiled events for the component.
  109. """
  110. state_name = state.get_name()
  111. state_setter = templates.format_state_setter(state_name)
  112. return templates.EVENT_FN(state=state_name, set_state=state_setter)
  113. def compile_effects(state: Type[State]) -> str:
  114. """Compile all the effects for a given component.
  115. Args:
  116. state: The state class for the component.
  117. Returns:
  118. A string of the compiled effects for the component.
  119. """
  120. state_name = state.get_name()
  121. set_state = templates.format_state_setter(state_name)
  122. return templates.USE_EFFECT(state=state_name, set_state=set_state)
  123. def compile_render(component: Component) -> str:
  124. """Compile the component's render method.
  125. Args:
  126. component: The component to compile the render method for.
  127. Returns:
  128. A string of the compiled render method.
  129. """
  130. return component.render()
  131. def compile_custom_component(component: CustomComponent) -> Tuple[str, ImportDict]:
  132. """Compile a custom component.
  133. Args:
  134. component: The custom component to compile.
  135. Returns:
  136. A tuple of the compiled component and the imports required by the component.
  137. """
  138. # Render the component.
  139. render = component.get_component()
  140. # Get the imports.
  141. imports = {
  142. lib: fields
  143. for lib, fields in render.get_imports().items()
  144. if lib != component.library
  145. }
  146. # Concatenate the props.
  147. props = ", ".join([prop.name for prop in component.get_prop_vars()])
  148. # Compile the component.
  149. return (
  150. templates.COMPONENT(
  151. name=component.tag,
  152. props=props,
  153. render=render,
  154. ),
  155. imports,
  156. )
  157. def create_document_root(stylesheets: List[str]) -> Component:
  158. """Create the document root.
  159. Args:
  160. stylesheets: The list of stylesheets to include in the document root.
  161. Returns:
  162. The document root.
  163. """
  164. sheets = [Link.create(rel="stylesheet", href=href) for href in stylesheets]
  165. return Html.create(
  166. DocumentHead.create(*sheets),
  167. Body.create(
  168. ColorModeScript.create(),
  169. Main.create(),
  170. Script.create(),
  171. ),
  172. )
  173. def create_theme(style: Style) -> Dict:
  174. """Create the base style for the app.
  175. Args:
  176. style: The style dict for the app.
  177. Returns:
  178. The base style for the app.
  179. """
  180. # Get the global style from the style dict.
  181. global_style = Style({k: v for k, v in style.items() if not isinstance(k, type)})
  182. # Root styles.
  183. root_style = Style({k: v for k, v in global_style.items() if k.startswith("::")})
  184. # Body styles.
  185. root_style["body"] = Style(
  186. {k: v for k, v in global_style.items() if k not in root_style}
  187. )
  188. # Return the theme.
  189. return {
  190. "styles": {"global": root_style},
  191. }
  192. def get_page_path(path: str) -> str:
  193. """Get the path of the compiled JS file for the given page.
  194. Args:
  195. path: The path of the page.
  196. Returns:
  197. The path of the compiled JS file.
  198. """
  199. return os.path.join(constants.WEB_PAGES_DIR, path + constants.JS_EXT)
  200. def get_theme_path() -> str:
  201. """Get the path of the base theme style.
  202. Returns:
  203. The path of the theme style.
  204. """
  205. return os.path.join(constants.WEB_UTILS_DIR, constants.THEME + constants.JS_EXT)
  206. def get_components_path() -> str:
  207. """Get the path of the compiled components.
  208. Returns:
  209. The path of the compiled components.
  210. """
  211. return os.path.join(constants.WEB_UTILS_DIR, "components" + constants.JS_EXT)
  212. def add_meta(page: Component, title: str, image: str, description: str) -> Component:
  213. """Add metadata to a page.
  214. Args:
  215. page: The component for the page.
  216. title: The title of the page.
  217. image: The image for the page.
  218. description: The description of the page.
  219. Returns:
  220. The component with the metadata added.
  221. """
  222. page.children.append(
  223. Head.create(
  224. Title.create(title),
  225. Description.create(content=description),
  226. Image.create(content=image),
  227. )
  228. )
  229. return page
  230. def write_page(path: str, code: str):
  231. """Write the given code to the given path.
  232. Args:
  233. path: The path to write the code to.
  234. code: The code to write.
  235. """
  236. utils.mkdir(os.path.dirname(path))
  237. with open(path, "w", encoding="utf-8") as f:
  238. f.write(code)
  239. def empty_dir(path: str, keep_files: Optional[List[str]] = None):
  240. """Remove all files and folders in a directory except for the keep_files.
  241. Args:
  242. path: The path to the directory that will be emptied
  243. keep_files: List of filenames or foldernames that will not be deleted.
  244. """
  245. # If the directory does not exist, return.
  246. if not os.path.exists(path):
  247. return
  248. # Remove all files and folders in the directory.
  249. keep_files = keep_files or []
  250. directory_contents = os.listdir(path)
  251. for element in directory_contents:
  252. if element not in keep_files:
  253. utils.rm(os.path.join(path, element))