* Desktop: cleanup headers * Get loader pubsub via record * [FL-2183] Dolphin refactoring 2022.01 * Restruct animations assets structure * Rename assets * Cleanup headers * Update Recording animation * Add BadBattery animation * Provide loader's pubsub via record * Fix load/unload animations * Scripts: add flipper format support, initial dolphin packager rework. Assets: internal and external dolphin. * Sync internal meta.txt and manifest.txt * Reorder, rename dolphin assets * Split essential generated assets * Add ReadMe for dolphin assets * Separate essential blocking animations * Scripts: full dolphin validation before packaging * Assets, Scripts: dolphin external resources packer * Github: update codeowners * Scripts: proper slots handling in dolphin animation meta * Scripts: correct frames enumeration and fix compiled assets. * [FL-2209] Add Dolphin Deeds points and many more * Remove excess frame_rate * Change dolphin assets directory * Scripts: add internal resource support to dolphin compiler * Scripts: add internal assets generation, renaming * Scripts: correct assert, renaming * Code cleanup, documentation, fixes * Update Levelup animations * Rename essential -> blocking * Fix Unlocked hint * Scripts: rewrite Templite compiller, replace regexps with token parser, split block types into code and variable blocks. Update dolphin templates. * Documentation: add key combos description and use information * Scripts: cleanup templit, more debug info and add dev comment Co-authored-by: あく <alleteam@gmail.com>
		
			
				
	
	
		
			197 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#  Templite++
 | 
						|
#  A light-weight, fully functional, general purpose templating engine
 | 
						|
#  Proudly made of shit and sticks. Strictly not for production use.
 | 
						|
#  Extremly unsafe and difficult to debug.
 | 
						|
#
 | 
						|
#  Copyright (c) 2022 Flipper Devices
 | 
						|
#  Author: Aleksandr Kutuzov <alletam@gmail.com>
 | 
						|
#
 | 
						|
#  Copyright (c) 2009 joonis new media
 | 
						|
#  Author: Thimo Kraemer <thimo.kraemer@joonis.de>
 | 
						|
#
 | 
						|
#  Based on Templite by Tomer Filiba
 | 
						|
#  http://code.activestate.com/recipes/496702/
 | 
						|
#
 | 
						|
#  This program is free software; you can redistribute it and/or modify
 | 
						|
#  it under the terms of the GNU General Public License as published by
 | 
						|
#  the Free Software Foundation; either version 2 of the License, or
 | 
						|
#  (at your option) any later version.
 | 
						|
#
 | 
						|
#  This program is distributed in the hope that it will be useful,
 | 
						|
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
#  GNU General Public License for more details.
 | 
						|
#
 | 
						|
#  You should have received a copy of the GNU General Public License
 | 
						|
#  along with this program; if not, write to the Free Software
 | 
						|
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 | 
						|
#  MA 02110-1301, USA.
 | 
						|
 | 
						|
 | 
						|
from enum import Enum
 | 
						|
 | 
						|
import sys
 | 
						|
import os
 | 
						|
 | 
						|
 | 
						|
class TempliteCompiler:
 | 
						|
    class State(Enum):
 | 
						|
        TEXT = 1
 | 
						|
        CONTROL = 2
 | 
						|
        VARIABLE = 3
 | 
						|
 | 
						|
    def __init__(self, source: str, encoding: str):
 | 
						|
        self.blocks = [f"# -*- coding: {encoding} -*-"]
 | 
						|
        self.block = ""
 | 
						|
        self.source = source
 | 
						|
        self.cursor = 0
 | 
						|
        self.offset = 0
 | 
						|
 | 
						|
    def processText(self):
 | 
						|
        self.block = self.block.replace("\\", "\\\\").replace('"', '\\"')
 | 
						|
        self.block = "\t" * self.offset + f'write("""{self.block}""")'
 | 
						|
        self.blocks.append(self.block)
 | 
						|
        self.block = ""
 | 
						|
 | 
						|
    def getLine(self):
 | 
						|
        return self.source[: self.cursor].count("\n") + 1
 | 
						|
 | 
						|
    def controlIsEnding(self):
 | 
						|
        block_stripped = self.block.lstrip()
 | 
						|
        if block_stripped.startswith(":"):
 | 
						|
            if not self.offset:
 | 
						|
                raise SyntaxError(
 | 
						|
                    f"Line: {self.getLine()}, no statement to terminate: `{block_stripped}`"
 | 
						|
                )
 | 
						|
            self.offset -= 1
 | 
						|
            self.block = block_stripped[1:]
 | 
						|
            if not self.block.endswith(":"):
 | 
						|
                return True
 | 
						|
        return False
 | 
						|
 | 
						|
    def processControl(self):
 | 
						|
        self.block = self.block.rstrip()
 | 
						|
 | 
						|
        if self.controlIsEnding():
 | 
						|
            self.block = ""
 | 
						|
            return
 | 
						|
 | 
						|
        lines = self.block.splitlines()
 | 
						|
        margin = min(len(l) - len(l.lstrip()) for l in lines if l.strip())
 | 
						|
        self.block = "\n".join("\t" * self.offset + l[margin:] for l in lines)
 | 
						|
        self.blocks.append(self.block)
 | 
						|
        if self.block.endswith(":"):
 | 
						|
            self.offset += 1
 | 
						|
        self.block = ""
 | 
						|
 | 
						|
    def processVariable(self):
 | 
						|
        self.block = self.block.strip()
 | 
						|
        self.block = "\t" * self.offset + f"write({self.block})"
 | 
						|
        self.blocks.append(self.block)
 | 
						|
        self.block = ""
 | 
						|
 | 
						|
    def compile(self):
 | 
						|
        state = self.State.TEXT
 | 
						|
 | 
						|
        # Process template source
 | 
						|
        while self.cursor < len(self.source):
 | 
						|
            # Process plain text till first token occurance
 | 
						|
            if state == self.State.TEXT:
 | 
						|
                if self.source[self.cursor :].startswith("{%"):
 | 
						|
                    state = self.State.CONTROL
 | 
						|
                    self.cursor += 1
 | 
						|
                elif self.source[self.cursor :].startswith("{{"):
 | 
						|
                    state = self.State.VARIABLE
 | 
						|
                    self.cursor += 1
 | 
						|
                else:
 | 
						|
                    self.block += self.source[self.cursor]
 | 
						|
                # Commit self.block if token was found
 | 
						|
                if state != self.State.TEXT:
 | 
						|
                    self.processText()
 | 
						|
            elif state == self.State.CONTROL:
 | 
						|
                if self.source[self.cursor :].startswith("%}"):
 | 
						|
                    self.cursor += 1
 | 
						|
                    state = self.State.TEXT
 | 
						|
                    self.processControl()
 | 
						|
                else:
 | 
						|
                    self.block += self.source[self.cursor]
 | 
						|
            elif state == self.State.VARIABLE:
 | 
						|
                if self.source[self.cursor :].startswith("}}"):
 | 
						|
                    self.cursor += 1
 | 
						|
                    state = self.State.TEXT
 | 
						|
                    self.processVariable()
 | 
						|
                else:
 | 
						|
                    self.block += self.source[self.cursor]
 | 
						|
            else:
 | 
						|
                raise Exception("Unknown State")
 | 
						|
 | 
						|
            self.cursor += 1
 | 
						|
 | 
						|
        if state != self.State.TEXT:
 | 
						|
            raise Exception("Last self.block was not closed")
 | 
						|
 | 
						|
        if self.block:
 | 
						|
            self.processText()
 | 
						|
 | 
						|
        return "\n".join(self.blocks)
 | 
						|
 | 
						|
 | 
						|
class Templite:
 | 
						|
    cache = {}
 | 
						|
 | 
						|
    def __init__(self, text=None, filename=None, encoding="utf-8", caching=False):
 | 
						|
        """Loads a template from string or file."""
 | 
						|
        if filename:
 | 
						|
            filename = os.path.abspath(filename)
 | 
						|
            mtime = os.path.getmtime(filename)
 | 
						|
            self.file = key = filename
 | 
						|
        elif text is not None:
 | 
						|
            self.file = mtime = None
 | 
						|
            key = hash(text)
 | 
						|
        else:
 | 
						|
            raise ValueError("either text or filename required")
 | 
						|
        # set attributes
 | 
						|
        self.encoding = encoding
 | 
						|
        self.caching = caching
 | 
						|
        # check cache
 | 
						|
        cache = self.cache
 | 
						|
        if caching and key in cache and cache[key][0] == mtime:
 | 
						|
            self._code = cache[key][1]
 | 
						|
            return
 | 
						|
        # read file
 | 
						|
        if filename:
 | 
						|
            with open(filename) as fh:
 | 
						|
                text = fh.read()
 | 
						|
        # Compile template to executable
 | 
						|
        code = TempliteCompiler(text, self.encoding).compile()
 | 
						|
        self._code = compile(code, self.file or "<string>", "exec")
 | 
						|
        # Cache for future use
 | 
						|
        if caching:
 | 
						|
            cache[key] = (mtime, self._code)
 | 
						|
 | 
						|
    def render(self, **namespace):
 | 
						|
        """Renders the template according to the given namespace."""
 | 
						|
        stack = []
 | 
						|
        namespace["__file__"] = self.file
 | 
						|
        # add write method
 | 
						|
        def write(*args):
 | 
						|
            for value in args:
 | 
						|
                stack.append(str(value))
 | 
						|
 | 
						|
        namespace["write"] = write
 | 
						|
        # add include method
 | 
						|
        def include(file):
 | 
						|
            if not os.path.isabs(file):
 | 
						|
                if self.file:
 | 
						|
                    base = os.path.dirname(self.file)
 | 
						|
                else:
 | 
						|
                    base = os.path.dirname(sys.argv[0])
 | 
						|
                file = os.path.join(base, file)
 | 
						|
            t = Templite(None, file, self.encoding, self.delimiters, self.caching)
 | 
						|
            stack.append(t.render(**namespace))
 | 
						|
 | 
						|
        namespace["include"] = include
 | 
						|
        # execute template code
 | 
						|
        exec(self._code, namespace)
 | 
						|
        return "".join(stack)
 |