Przeglądaj źródła

First Work-In-Progress build.

Cixo Develop 7 miesięcy temu
rodzic
commit
6c8e4b1010
6 zmienionych plików z 674 dodań i 0 usunięć
  1. 9 0
      __init__.py
  2. 136 0
      compiler.py
  3. 281 0
      dom.py
  4. 41 0
      make.example.py
  5. 174 0
      render.py
  6. 33 0
      view.py

+ 9 - 0
__init__.py

@@ -0,0 +1,9 @@
+from dom import tag
+from dom import link
+from dom import style
+from dom import script 
+from render import render
+from compiler import compiler 
+from compiler import compiler 
+from compiler import sass_compiler 
+from compiler import esbuild_compiler 

+ 136 - 0
compiler.py

@@ -0,0 +1,136 @@
+import pathlib
+import subprocess
+
+class compiler:
+    """
+    This class is responsible for building and bundling source code into
+    result file. This could return to command line after error, or raise
+    exception to handle it in build script.
+    """
+
+    def __init__(self, source: pathlib.Path, throw: bool = False) -> None:
+        """
+        This function create new compiler instance.
+
+        Parameters:
+            source (pathlib.Path) - Source file to build
+            throw (bool) - When True failed compilation raise Exception
+        """
+
+        self.__source = source
+        self.__throw = throw
+
+    @property
+    def throw(self) -> bool:
+        """ When throw is True, failed command would raise Exception. """
+
+        return self.__throw
+
+    @property
+    def source(self) -> pathlib.Path:
+        """ This return source file to build. """
+
+        return self.__source
+
+    def build(self, result: pathlib.Path) -> None:
+        """
+        This function is responsible for building process.
+
+        Parameters:
+            result (pathlib.Path): Result file to store compiled code
+        """
+
+        if result.is_dir():
+            self._error("Build file \"" + str(result) + "\" is directory.")
+
+        if result.exists():
+            result.unlink()
+
+        command = self._command(self.source, result)
+        returned = subprocess.run(command, capture_output = True)
+
+        if returned.returncode == 0:
+            return
+
+        failed = result.stdout.decode("UTF-8") 
+        failed = failed + returned.stderr.decode("UTF-8")
+
+        self._error(failed)
+
+    def _error(self, content: str) -> None:
+        """
+        This function raise Exception if throw flag is set, or print
+        error to command line, and return there.
+
+        Parameters:
+            content (str): Content of the error
+        """
+
+        if self.throw:
+            raise Exception(content)
+
+        print("Error while compilation: ")
+        print("\"" + content + "\"")
+
+        exit(-1)
+
+    def _command(self, source: pathlib.Path, result: pathlib.Path) -> list:
+        """
+        This function is responsible for creating command for compilation 
+        process.
+
+        Parameters:
+            source (pathlib.Path) - Source file to compile
+            result (pathlib.Path) - Result compiled file
+
+        Returns:
+            (list) - List of strings, for subprocess creation
+        """
+
+        raise TypeError("This is abstract class. Overwrite this function.")    
+
+
+class sass_compiler(compiler):
+    def _command(self, source: pathlib.Path, result: pathlib.Path) -> list:
+        """
+        This function is responsible for creating command for compilation 
+        process.
+
+        Parameters:
+            source (pathlib.Path) - Source file to compile
+            result (pathlib.Path) - Result compiled file
+
+        Returns:
+            (list) - List of strings, for subprocess creation
+        """
+
+        return [
+            "sass",
+            "--sourcemap=none",
+            "-t compressed",
+            str(source),
+            str(result)
+        ]
+
+
+class esbuild_compiler(compiler):
+    def _command(self, source: pathlib.Path, result: pathlib.Path) -> list:
+        """
+        This function is responsible for creating command for compilation 
+        process.
+
+        Parameters:
+            source (pathlib.Path) - Source file to compile
+            result (pathlib.Path) - Result compiled file
+
+        Returns:
+            (list) - List of strings, for subprocess creation
+        """
+
+        return [
+            "esbuild", 
+            str(source),
+            "--bundle",
+            "--outfile=" + str(result)
+        ]
+

+ 281 - 0
dom.py

@@ -0,0 +1,281 @@
+class tag:
+    '''
+    This class is responsible for create HTML tag, such as script, link and
+    other. It is requied for creating tags in the app view.
+    '''
+
+    def __init__(self, name: str, twice: bool) -> None:
+        '''
+        This function create new tag from its name. It also require parameter
+        named 'twice'. Twice told that tag has second closing tag. For example
+        when twice is True, then tag look like <a></a>, but when twice is 
+        False, tag looks like <br/>.
+
+        Parameters:
+            name (str): Name of the tag
+            twice (bool): True when tag has closing tag, false when not
+        '''
+
+        name = name.strip()
+
+        if len(name) == 0:
+            raise Exception("Can not create tag without name.")
+
+        for letter in name:
+            if letter.isspace():
+                raise Exception("HTML Tag can not contain white space.")
+        
+        self.__name = name
+        self.__twice = twice
+        self.__content = str()
+        self.__attributes = dict()
+
+    @property
+    def name(self) -> str:
+        ''' This function return name of the tag. '''
+
+        return self.__name
+
+    @property
+    def twice(self) -> bool:
+        ''' This function return that tag has closing tag. '''
+        return self.__twice
+
+    @property
+    def content(self) -> str:
+        ''' This function return content of the tag, like innerHTML. '''
+        return self.__content
+
+    @content.setter
+    def content(self, target: str) -> None:
+        ''' This function set new content, like innerHTML. '''
+
+        self.__content = target
+
+    def set_attribute(
+        self,
+        name: str, 
+        content: str | int | float | bool | None
+    ) -> None:
+        ''' 
+        This function set value for tag attribute. 
+
+        Parameters:
+            name (str): Name of the attribute
+            content (str | int | float | bool | None): Content of the attribute
+        '''
+
+        name = name.strip()
+
+        for letter in name:
+            if letter.isspace():
+                raise Exception("Tag attribute can not contain white char.")
+
+        self.__attributes[name] = content
+
+    def drop_attribute(self, name: str) -> str | int | float | bool | None:
+        '''
+        This function remove attribute by name, when parameter exists.
+
+        Parameters:
+            name (str): Name of the attribute
+
+        Returns:
+            (str | int | float | bool | None): Content of the droped attribute
+        '''
+
+        if not name in self.__attributes:
+            return None
+
+        copy = self.__attributes[name]
+        del self.__attributes[name]
+
+        return copy
+
+    def get_attribute(self, name: str) -> str | int | float | bool | None:
+        '''
+        This function return content of the attribute, when exists.
+
+        Parameters:
+            name (str): Name of the attribute
+
+        Returns:
+            (str | int | float | bool | None): Content of the attribute 
+        '''
+
+        if not name in self.__attributes:
+            raise Exception("Attribute " + name + " not exists.")
+
+        return self.__attributes[name]
+
+    def has_attribute(self, name: str) -> bool:
+        ''' This function return that attribute exists or not. '''
+
+        return name in self.__attributes
+
+    @property
+    def attributes(self) -> list:
+        ''' This function return copy of the attributes dict. '''
+
+        return self.__attributes.copy() 
+
+    def render(self) -> str:
+        '''
+        This function return tag rendered to string.
+        '''
+
+        attributes = str()
+
+        for attribute in self.attributes.keys():
+            content = self.get_attribute(attribute)
+
+            if content is None:
+                attributes = attributes + " " + attribute
+                continue
+
+            content = str(content)
+            content = content.replace("\"", "\\\"")
+
+            attributes = attributes + " " + attribute + "=\""
+            attributes = attributes + content + "\""
+
+        tag = "<" + self.name + attributes + ">\n"
+
+        if not self.twice:
+            return tag
+
+        closing_tag = "</" + self.name + ">"
+
+        return tag + self.content + "\n" + closing_tag + "\n"
+
+    def __str__(self) -> str:
+        ''' This function return tag rendered to string. '''
+        
+        return self.render()
+
+class style(tag):
+    '''
+    This class is responsible for style.
+    '''
+
+    def __init__(self, content: str | None = None):
+        '''
+        This create new style tag, with optional setuped content.
+
+        Parameters:
+            content (str | None): Stylesheet of the style tag
+        '''
+
+        super().__init__("style", True)
+
+        if content is not None:
+            self.content = content
+
+class script(tag):
+    ''' 
+    This class is responsible for script loading.
+    '''
+
+    def __init__(self, src: str | None = None):
+        '''
+        This create new script tag.
+
+        Parameters:
+            src (str | None) = None: Source of the script
+        '''
+
+        super().__init__("script", True)
+
+        if src is not None:
+            self.set_attribute("src", src)
+
+    @property
+    def src(self) -> str | None:
+        ''' Return source of the script, or None when not set. '''
+
+        if self.has_attribute("src"):
+            return self.get_attribute("src")
+
+        return None
+
+    @src.setter
+    def src(self, target: str | None) -> None:
+        ''' This function set soure of the script, or drop it when None. '''
+
+        if target is not None:
+            self.set_attribute("src", target)
+            return
+
+        if self.has_attribute("src"):
+            self.drop_attribute("src")
+            return
+
+class link(tag):
+    '''
+    This class is responsible for creating link tags.
+    '''
+
+    def __init__(self):
+        ''' This function initialize link tag. '''
+
+        super().__init__("link", False)
+
+    @property
+    def rel(self) -> str | None:
+        ''' This function return rel of the link, or None when not set. '''
+
+        if not self.has_attribute("rel"):
+            return None
+
+        return self.get_attribute("rel")
+
+    @property
+    def href(self) -> str | None:
+        ''' This function return href of the link, or None, when not set. '''
+
+        if not self.has_attribute("href"):
+            return None
+
+        return self.get_attribute("href")
+
+    @property
+    def type(self) -> str | None:
+        ''' This function return type of the link, or None when not set. '''
+
+        if not self.has_attribute("type"):
+            return None
+
+        return self.get_attribute("type")
+
+    @rel.setter
+    def rel(self, target: str | None) -> None:
+        ''' This function set rel of the link, or drop it when None. '''
+
+        if target is not None:
+            self.set_attribute("rel", target)
+            return
+
+        if self.has_attribute("rel"):
+            self.drop_attribute("rel")
+
+    @href.setter
+    def href(self, target: str | None) -> None:
+        ''' This function set href of the link, or drop it when None. '''
+        
+        if target is not None:
+            self.set_attribute("href", target)
+            return
+
+        if self.has_attribute("href"):
+            self.drop_attribute("href")
+
+    @type.setter
+    def type(self, target: str | None) -> None:
+        ''' This function set type of the link, or drop it when None. '''
+        
+        if target is not None:
+            self.set_attribute("type", target)
+            return
+
+        if self.has_attribute("type"):
+            self.drop_attribute("type")

+ 41 - 0
make.example.py

@@ -0,0 +1,41 @@
+from pathlib import Path
+from json import loads
+from shutil import rmtree
+from shutil import copytree
+
+from source.cx_maketool import sass_compiler
+from source.cx_maketool import esbuild_compiler
+from source.cx_maketool import render
+from source.cx_maketool import script
+from source.cx_maketool import link
+
+root = Path(__file__).absolute()
+source = root / Path("source/")
+build = root / Path("build/")
+
+views = source / Path("views/")
+styles = source / Path("theme/")
+scripts = source / Path("scripts/")
+assets = source / Path("assets/")
+
+bundles = build / Path("bundle/")
+assets_output = build / Path("assets/")
+
+index = views / Path("core.html")
+scripts_loader = scripts / Path("code.js")
+theme_loader = styles / Path("core.sass")
+
+index_output = build / Path("index.html")
+script_bundle = bundle / Path("app.js")
+theme_bundle = bundle / Path("theme.css")
+
+if build.exists():
+    rmtree(build)
+
+build.mkdir()
+bundles.mkdir()
+
+sass_compiler(theme_loader).build(theme_bundle)
+esbuild_compiler(scripts_loader).build(script_bundle)
+copytree(assets, assets_output)
+copytree(config, config_output)

+ 174 - 0
render.py

@@ -0,0 +1,174 @@
+import pathlib
+
+class render:
+    '''
+    This class is responsible for rendering files, such as HTML files to
+    result. It could replace given formats with given values.
+    '''
+
+    def __init__(self, file: pathlib.Path):
+        '''
+        This is default render builder. It require handle to file which 
+        would be rendered.
+
+        Parameters:
+            file (Path): This is handle to file
+
+        Returns:
+            (render): New render object
+        '''
+
+        self.__list = dict()
+        self.__cache = None
+
+        self.__start_tag = ">>"
+        self.__stop_tag = "<<"
+
+        try:
+            with file.open() as handle:
+                self.__content = handle.read()
+        
+        except:
+            Exception("Can not open file to render.")
+
+    def add(self, name: str, content: str) -> None:
+        '''
+        This add new replace to the render. That mean all places, where 
+        special tags contains name, would be replaced with content.
+
+        Parameters:
+            name (str): Name of the parameter
+            content (str): Content to replace when render
+
+        Returns:
+            (None)
+        '''
+
+        if name in self.__list:
+            raise Exception("Parameter " + name + " had been already set.")
+
+        self.__list[name] = content
+        self.__clean_cache()
+
+    def __clean_cache(self):
+        ''' This clean render cache. '''
+
+        self.__cache = None
+
+    def validate(self, name: str) -> bool:
+        '''
+        This check that given name is valid, that mean not contain any 
+        blocked chars, and white chars.
+
+        Parameters:
+            name (str): Name to validate
+
+        Returns:
+            (bool): True if valid, or False when not
+        '''
+
+        blocked = ["'", "\\", "\""]
+
+        for char in blocked:
+            if char in name:
+                return False
+
+        for letter in name:
+            if letter.isspace():
+                return False
+
+        return True
+
+    @property
+    def start_tag(self):
+        ''' This return tag which start replace pattern. '''
+
+        return self.__start_tag
+
+    @property
+    def stop_tag(self):
+        ''' This return stop tag which end pattern. '''
+
+        return self.__stop_tag
+
+    @start_tag.setter
+    def start_tag(self, tag: str) -> None:
+        ''' This set new start tag. '''
+
+        if not self.validate(tag):
+            raise Exception("Start tag contain bad chars.")
+
+        self.__start_tag = tag
+
+    @stop_tag.setter
+    def stop_tag(self, tag: str) -> None:
+        ''' This set new stop tag. '''
+
+        if not self.validate(tag):
+            raise Exception("Stop tag can not contain blocked chars.")
+
+        self.__stop_tag = tag
+
+    def get(self, name: str) -> str:
+        ''' 
+        This retunr content set to given parameter.
+
+        Parameters:
+            name (str): Name of the parameter
+
+        Returns:
+            (str): Content of the parameter or blank string
+        '''
+
+        if name in self.__list:
+            return self.__list[name]
+
+        return str()
+
+    def finalize(self) -> str:
+        '''
+        This function render given documents, using parameters set before, 
+        and return finalized.       
+
+        Returns:
+            (str): Finalized rendered document 
+        '''
+
+        if self.__cache is not None:
+            return self.__cache
+
+        start_length = len(self.start_tag)
+        stop_length = len(self.stop_tag)
+        
+        result = self.__content
+        last = 0
+
+        while True:
+            start = result.find(self.start_tag, last)
+            stop = result.find(self.stop_tag, last)
+
+            if start == -1:
+                break
+
+            if stop == -1:
+                break
+
+            name = result[start + start_length + 1 : stop]
+            name = name.strip()
+
+            last = start
+            content = self.get(name)
+
+            result \
+            = result[: start] \
+            + content \
+            + result[stop + stop_length :]
+
+        self.__cache = result
+
+        return self.__cache
+
+    def __str__(self) -> str:
+        ''' This is string converter, and alias of finalize. '''
+
+        return self.finalize()

+ 33 - 0
view.py

@@ -0,0 +1,33 @@
+import pathlib
+
+from .render import render
+
+class view:
+    def __init__(self, source: pathlib.Path):
+        if not source.is_file():
+            raise Exception("Source \"" + str(source) + "\" not exists.")
+
+        self.__renderer = render(source)
+        self.__result = None
+
+    def add_params(self, params: dict) -> object:
+        for param in params.keys():
+            self.__renderer.add(param, params[param])
+
+        return self
+
+    def set_result(self, result: pathlib.Path) -> object:
+        self.__result = result
+        return self
+
+    def save(self) -> object:
+        if self.__result.is_file():
+            self.__result.unlink()
+
+        if self.__result.is_dir():
+            raise Exception("View \"" + str(self.__result) + "\" is dir.")
+
+        with self.__result.open("w") as result:
+            result.write(self.__renderer.finalize())
+
+        return self