diff libervia/server/pages.py @ 1253:6d49fae517ba

pages: browser metadata + root `_browser`: - the `_browser` directory can now be put in root of a site `pages` directory, it will then include modules for the whole website - in `_browser` directories (notably the root one), a `browser_meta.json` file can be put to specify settings for a browser engine - pathlib.Path is now used LiberviaRootResource.site_path - introduced some type hints - task_brython copy modules in root `_browser` to build_path root. - minimal python version is now 3.7 due to type hints
author Goffi <goffi@goffi.org>
date Wed, 29 Apr 2020 17:34:53 +0200
parents aaf28d45ae67
children b1fb57e9176d
line wrap: on
line diff
--- a/libervia/server/pages.py	Wed Apr 29 15:00:54 2020 +0200
+++ b/libervia/server/pages.py	Wed Apr 29 17:34:53 2020 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-
 # Libervia: a Salut à Toi frontend
 # Copyright (C) 2011-2020 Jérôme Poisson <goffi@goffi.org>
 
@@ -17,14 +16,18 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from __future__ import annotations
+
 import uuid
 import os.path
 import urllib.request, urllib.parse, urllib.error
 import time
 import hashlib
 import copy
+import json
+from pathlib import Path
 from functools import reduce
-from pathlib import Path
+from typing import Optional, List
 
 from twisted.web import server
 from twisted.web import resource as web_resource
@@ -36,6 +39,7 @@
 from sat.core.i18n import _
 from sat.core import exceptions
 from sat.tools.common import date_utils
+from sat.tools.common import utils
 from sat.core.log import getLogger
 from sat_frontends.bridge.bridge_frontend import BridgeException
 
@@ -123,7 +127,7 @@
         @param host(Libervia): the running instance of Libervia
         @param vhost_root(web_resource.Resource): root resource of the virtual host which
             handle this page.
-        @param root_dir(unicode): aboslute file path of the page
+        @param root_dir(Path): aboslute file path of the page
         @param url(unicode): relative URL to the page
             this URL may not be valid, as pages may require path arguments
         @param name(unicode, None): if not None, a unique name to identify the page
@@ -260,7 +264,7 @@
     def createPage(host, meta_path, vhost_root, url_elts, replace_on_conflict=False):
         """Create a LiberviaPage instance
 
-        @param meta_path(unicode): path to the page_meta.py file
+        @param meta_path(Path): path to the page_meta.py file
         @param vhost_root(resource.Resource): root resource of the virtual host
         @param url_elts(list[unicode]): list of path element from root site to this page
         @param replace_on_conflict(bool): same as for [LiberviaPage]
@@ -268,7 +272,7 @@
             - page_data: dict containing data of the page
             - libervia_page: created resource
         """
-        dir_path = os.path.dirname(meta_path)
+        dir_path = meta_path.parent
         page_data = {"__name__": ".".join(["page"] + url_elts)}
         # we don't want to force the presence of __init__.py
         # so we use execfile instead of import.
@@ -295,6 +299,47 @@
             replace_on_conflict=replace_on_conflict
         )
 
+    @staticmethod
+    def createBrowserData(
+        vhost_root,
+        resource: Optional(LiberviaPage),
+        browser_path: Path,
+        path_elts: Optional(List[str]),
+        engine: str = "brython"
+    ) -> None:
+        """create and store data for browser dynamic code"""
+        dyn_data = {
+            "path": browser_path,
+            "url_hash": (
+                hashlib.sha256('/'.join(path_elts).encode()).hexdigest()
+                if path_elts is not None else None
+            ),
+        }
+        browser_meta_path = browser_path / C.PAGES_BROWSER_META_FILE
+        if browser_meta_path.is_file():
+            with browser_meta_path.open() as f:
+                browser_meta = json.load(f)
+            utils.recursive_update(vhost_root.browser_modules, browser_meta)
+            if resource is not None:
+                utils.recursive_update(resource.dyn_data, browser_meta)
+
+        init_path = browser_path / '__init__.py'
+        if init_path.is_file():
+            vhost_root.browser_modules.setdefault(
+                engine, []).append(dyn_data)
+            if resource is not None:
+                resource.dyn_data[engine] = dyn_data
+        elif path_elts is None:
+            try:
+                next(browser_path.glob('*.py'))
+            except StopIteration:
+                # no python file, nothing for Brython
+                pass
+            else:
+                vhost_root.browser_modules.setdefault(
+                    engine, []).append(dyn_data)
+
+
     @classmethod
     def importPages(cls, host, vhost_root, root_path=None, _parent=None, _path=None,
         _extra_pages=False):
@@ -302,7 +347,7 @@
 
         @param host(Libervia): Libervia instance
         @param vhost_root(LiberviaRootResource): root of this VirtualHost
-        @param root_path(unicode, None): use this root path instead of vhost_root's one
+        @param root_path(Path, None): use this root path instead of vhost_root's one
             Used to add default site pages to external sites
         @param _parent(Resource, None): _parent page. Do not set yourself, this is for
             internal use only
@@ -315,23 +360,27 @@
             _path = []
         if _parent is None:
             if root_path is None:
-                root_dir = os.path.join(vhost_root.site_path, C.PAGES_DIR)
+                root_dir = vhost_root.site_path / C.PAGES_DIR
             else:
-                root_dir = os.path.join(root_path, C.PAGES_DIR)
+                root_dir = root_path / C.PAGES_DIR
                 _extra_pages = True
             _parent = vhost_root
+            root_browser_path = root_dir / C.PAGES_BROWSER_DIR
+            if root_browser_path.is_dir():
+                cls.createBrowserData(vhost_root, None, root_browser_path, None)
         else:
             root_dir = _parent.root_dir
+
         for d in os.listdir(root_dir):
-            dir_path = os.path.join(root_dir, d)
-            if not os.path.isdir(dir_path):
+            dir_path = root_dir / d
+            if not dir_path.is_dir():
                 continue
             if _extra_pages and d in _parent.children:
                 log.debug(_("[{host_name}] {path} is already present, ignoring it")
                     .format(host_name=vhost_root.host_name, path='/'.join(_path+[d])))
                 continue
-            meta_path = os.path.join(dir_path, C.PAGES_META_FILE)
-            if os.path.isfile(meta_path):
+            meta_path = dir_path / C.PAGES_META_FILE
+            if meta_path.is_file():
                 new_path = _path + [d]
                 try:
                     page_data, resource = cls.createPage(
@@ -342,7 +391,7 @@
                         continue
                     else:
                         raise e
-                _parent.putChild(d.encode('utf-8'), resource)
+                _parent.putChild(str(d).encode(), resource)
                 log_msg = ("[{host_name}] Added /{path} page".format(
                     host_name=vhost_root.host_name,
                     path="[…]/".join(new_path)))
@@ -374,17 +423,9 @@
                     host, vhost_root, _parent=resource, _path=new_path,
                     _extra_pages=_extra_pages)
                 # now we check if there is some code for browser
-                browser_path = Path(dir_path) / C.PAGES_BROWSER_DIR
+                browser_path = dir_path / C.PAGES_BROWSER_DIR
                 if browser_path.is_dir():
-                    # for now we only handle Brython
-                    dyn_data = {
-                        "path": browser_path,
-                        "url_hash": hashlib.sha256(
-                            '/'.join(new_path).encode()).hexdigest(),
-                    }
-                    vhost_root.browser_modules.setdefault(
-                        "brython", []).append(dyn_data)
-                    resource.dyn_data['brython'] = dyn_data
+                    cls.createBrowserData(vhost_root, resource, browser_path, new_path)
 
     @classmethod
     def onFileChange(cls, host, file_path, flags, site_root, site_path):