 c3ececcf96
			
		
	
	
		c3ececcf96
		
			
		
	
	
	
	
		
			
			* ufbt: added "dolphin_ext" target (expects "external" subfolder in cwd with dolphin assets); cleaned up unused code * ufbt: codestyle fixes * scripts: fixed style according to ruff linter * scripts: additional cleanup & codestyle fixes * github: pass target hw code when installing local SDK with ufbt * ufbt: added error message for missing folder in dolphin builder * scripts: more linter fixes * sdk: added flipper_format_stream; ufbt: support for --extra-define * fbt: reduced amount of global defines * scripts, fbt: rearranged imports Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
		
			
				
	
	
		
			199 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			199 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(line) - len(line.lstrip()) for line in lines if line.strip())
 | |
|         self.block = "\n".join("\t" * self.offset + line[margin:] for line 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)
 |