Jelajahi Sumber

Create build scripts for the project.

Cixo Develop 8 bulan lalu
induk
melakukan
8a4d1e0ae7

+ 2 - 0
.gitignore

@@ -1,3 +1,5 @@
+build/
+
 # ---> Node
 # Logs
 logs

+ 5 - 0
config.json

@@ -0,0 +1,5 @@
+{
+    "view-params": {
+        "app_name": "La Watch"
+    }
+}

+ 28 - 0
loader.js

@@ -0,0 +1,28 @@
+System.register("assets/cx-ui", [], function (exports_1, context_1) {
+    "use strict";
+    var cx_ui;
+    var __moduleName = context_1 && context_1.id;
+    return {
+        setters: [],
+        execute: function () {
+            alert("UwU");
+            cx_ui = 10;
+            exports_1("cx_ui", cx_ui);
+        }
+    };
+});
+System.register("loader", ["assets/cx-ui"], function (exports_2, context_2) {
+    "use strict";
+    var cx_ui_1;
+    var __moduleName = context_2 && context_2.id;
+    return {
+        setters: [
+            function (cx_ui_1_1) {
+                cx_ui_1 = cx_ui_1_1;
+            }
+        ],
+        execute: function () {
+            alert(cx_ui_1.cx_ui);
+        }
+    };
+});

+ 228 - 0
make.py

@@ -0,0 +1,228 @@
+#!/usr/bin/python
+###
+### Build script for the la-warch app. This script is responsible
+### for building app from source, and config. Config file is 
+### 
+### ./config.json
+###
+### This file has documentation, with options that could be change to
+### make this app more flexible for the end users.
+
+if not __name__ == "__main__":
+    print("This is build system. Can be only executed as app.")
+    exit(-2)
+
+import json
+import pathlib
+import shutil
+import subprocess
+import datetime
+
+# Current release
+release = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
+
+# Root project directory
+root = pathlib.Path("./")
+
+# This is root source dir
+source = root / pathlib.Path("./source/")
+
+# Sources with types of content
+script_source = source / pathlib.Path("./scripts/")
+sass_source = source / pathlib.Path("./theme/")
+static_files = source / pathlib.Path("./static/")
+templates = source / pathlib.Path("./templates/")
+
+# Specific files, like loaders, main view, etc.
+app_view_file = templates / pathlib.Path("view.html")
+script_loader_file = script_source / pathlib.Path("loader.js")
+sass_loader_file = sass_source / pathlib.Path("loader.sass")
+config = root / pathlib.Path("config.json")
+
+# Build dir
+build = root / pathlib.Path("./build/")
+
+# Result specific files
+app_view_result_file = build / pathlib.Path("index.html")
+script_loader_result_file = build / pathlib.Path("bundle.js")
+sass_loader_result_file = build / pathlib.Path("bundle.css")
+
+# Checking that directories exists
+if not source.is_dir():
+    print("Source directory not exists.")
+    exit(-1)
+
+if not script_source.is_dir():
+    print("Script source directory not exists.")
+    exit(-1)
+
+if not sass_source.is_dir():
+    print("Sass source director not exists.")
+    exit(-1)
+
+if not static_files.is_dir():
+    print("Static files directory not exists.")
+    exit(-1)
+
+if not templates.is_dir():
+    print("Templates directory not exists.")
+    exit(-1)
+
+# Checking files
+if not app_view_file.is_file():
+    print("App view file not exists.")
+    exit(-1)
+
+# Check config file
+if not config.is_file():
+    print("App config file not exists.")
+    exit(-1)
+
+# Check script loader file
+if not script_loader_file.is_file():
+    print("Script loader file not exists.")
+    exit(-1)
+
+# Check SASS loader file
+if not sass_loader_file.is_file():
+    print("SASS loader file not exists.")
+    exit(-1)
+
+# Prepare result directory
+if build.is_dir():
+    shutil.rmtree(build)
+
+build.mkdir()
+
+# Parse config json
+try:
+    with config.open() as handle:
+        config = json.loads(handle.read())
+except event:
+    print(str(event))
+    print("Can not parse config file.")
+    exit(-1)
+
+def get_config_section(
+    name: str, 
+    alt: str | int | float | list | None = None
+) -> str | int | float | list:
+    '''
+    This get section from root config file, or raise exception when section
+    not exists, and alt is set to None. When alt is provided, and section
+    not exists then return alt. If section exists, then return section.
+
+    Parameters:
+        name (str): Name of the section
+        alt (str | int | float | list | None) = None: Alternative value
+
+    Returns:
+        (str | int | flaot | list): Value of the section
+    '''
+
+    global config
+
+    if name in config:
+        return config[name]
+
+    if alt is not None:
+        return alt
+
+    print("Not found section \"" + name + "\" in config.")
+    exit(-2)
+
+def compile(*command: list) -> None:
+    '''
+    This function execute command, and when command not exit without
+    error code, that mean not return 0, then it print STDOUT and STDERR
+    from the command.
+
+    Parameters:
+        command (list): Command as list of str
+    '''
+
+    result = subprocess.run(command, capture_output = True)
+
+    if result.returncode == 0:
+        return 
+
+    print(result.stdout.decode("UTF-8"))
+    print(result.stderr.decode("UTF-8"))
+    print("Compilation end with error.")
+    exit(-5)
+
+# Rendering app view
+from source.make.render import render
+from source.make.dom import link, script
+
+app_view_render = render(app_view_file)
+
+# App view use HTML description as replace tags
+app_view_render.start_tag = "<!--"
+app_view_render.stop_tag = "-->"
+
+view_params = get_config_section("view-params")
+
+# Replace all elements from view-params config section
+for param in view_params.keys():
+    if param == "bundle_items":
+        print("Can not use param \"bundle_items\", it is reserved.")
+        exit(-3)
+
+    content = view_params[param]
+
+    if type(content) is str:
+        app_view_render.add(param, content)
+        continue
+
+    if type(content) is float or type(content) is int:
+        app_view_render.add(param, str(content))
+        continue
+
+    print("Param " + param + " not contain string or number.")
+    exit(-3)
+
+# Generating tags
+sass_link = link()
+sass_link.rel = "stylesheet"
+sass_link.type = "text/css"
+sass_link.href = "./" + sass_loader_result_file.name + "?version=" + release
+
+js_script = script()
+js_script.src = "./" + script_loader_result_file.name + "?version=" + release
+
+bundle_items = sass_link.render() + js_script.render()
+
+# Add bundle items tags
+app_view_render.add("bundle_items", bundle_items)
+
+# Render and save app view
+app_view = app_view_render.finalize()
+
+try:
+    with app_view_result_file.open("w") as handle:
+        handle.write(app_view)
+except event:
+    print("Can not save app view.")
+    print(str(event))
+    exit(-4)
+
+# Bundle JS script source
+compile(
+    "esbuild",
+    str(script_loader_file),
+    "--bundle",
+    "--minify",
+    "--outfile=" + str(script_loader_result_file)
+)
+
+# Bundle SASS themes source
+compile(
+    "sass",
+    "--sourcemap=none",
+    "-t compressed",
+    str(sass_loader_file),
+    str(sass_loader_result_file)
+)
+
+print("Build success!")

+ 263 - 0
source/make/dom.py

@@ -0,0 +1,263 @@
+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 + ">"
+
+        if not self.twice:
+            return tag
+
+        closing_tag = "</" + self.name + ">"
+
+        return tag + self.content + closing_tag
+
+    def __str__(self) -> str:
+        ''' This function return tag rendered to string. '''
+        
+        return self.render()
+
+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")

+ 174 - 0
source/make/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()

+ 1 - 0
source/scripts/assets/cx-ui.js

@@ -0,0 +1 @@
+export const cx_ui = "UwU";

+ 3 - 0
source/scripts/loader.js

@@ -0,0 +1,3 @@
+import { cx_ui } from "./assets/cx-ui.js";
+
+console.log(cx_ui);

+ 12 - 0
source/templates/view.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+
+<html>
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        
+        <title><!-- app_name --></title>
+        
+        <!-- bundle_items -->
+    </head>
+</html>

+ 5 - 0
source/theme/document.sass

@@ -0,0 +1,5 @@
+body 
+    margin: 0px
+    padding: 0px
+    width: 100vw
+    min-height: 100vh

+ 1 - 0
source/theme/loader.sass

@@ -0,0 +1 @@
+@import "document"