|  | @@ -0,0 +1,109 @@
 | 
	
		
			
				|  |  | +import _io
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class template_exception(Exception):
 | 
	
		
			
				|  |  | +    pass
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +class template:
 | 
	
		
			
				|  |  | +    def __init__(self, content: str | _io.TextIOWrapper, check: bool = True):
 | 
	
		
			
				|  |  | +        self.__template = None
 | 
	
		
			
				|  |  | +        self.__rendered = None
 | 
	
		
			
				|  |  | +        self.__properties = dict()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if type(content) is str:
 | 
	
		
			
				|  |  | +            self.__template = content
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if type(content) is _io.TextIOWrapper:
 | 
	
		
			
				|  |  | +            self.__template = content.read()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if self.__template is None:
 | 
	
		
			
				|  |  | +            raise Exception("Template content must be string or file.")
 | 
	
		
			
				|  |  | +       
 | 
	
		
			
				|  |  | +        if check is True:
 | 
	
		
			
				|  |  | +            self.__prepare_template()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @property
 | 
	
		
			
				|  |  | +    def separators(self) -> [str, str]:
 | 
	
		
			
				|  |  | +        return "<<!", "!>>"
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __prepare_template(self) -> None:
 | 
	
		
			
				|  |  | +        template = self.template
 | 
	
		
			
				|  |  | +        start, end = self.separators
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        count = 0
 | 
	
		
			
				|  |  | +        current = 0
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        while True:
 | 
	
		
			
				|  |  | +            current_start = template.find(start, count)
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            if current_start == -1:
 | 
	
		
			
				|  |  | +                break
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            current = current_start + len(start)
 | 
	
		
			
				|  |  | +            current_end = template.find(end, count)
 | 
	
		
			
				|  |  | +            current_after = current_end + len(end)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if current > current_end:
 | 
	
		
			
				|  |  | +                found = "Close char before start char on " + str(current)
 | 
	
		
			
				|  |  | +                raise template_exception(found)
 | 
	
		
			
				|  |  | +            
 | 
	
		
			
				|  |  | +            key = template[current:current_end]
 | 
	
		
			
				|  |  | +            key = key.strip()
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if len(key) == 0:
 | 
	
		
			
				|  |  | +                raise template_exception("Empty key on " + str(current))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            before = template[:current_start]
 | 
	
		
			
				|  |  | +            after = template[current_after:]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            template = before + start + key + end + after
 | 
	
		
			
				|  |  | +            count = current + len(key) + len(end) 
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.__template = template
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @property
 | 
	
		
			
				|  |  | +    def template(self) -> str:
 | 
	
		
			
				|  |  | +        return self.__template
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def get(self, name: str) -> str:
 | 
	
		
			
				|  |  | +        if not type(name) is str:
 | 
	
		
			
				|  |  | +            raise Exception("Name of the key must be str.")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if not name in self.__properties:
 | 
	
		
			
				|  |  | +            raise Exception("Key " + name + " not exists in properties.")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return self.__properties[name]
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def set(self, name: str, value: str | int | float) -> template:
 | 
	
		
			
				|  |  | +        if not type(name) is str:
 | 
	
		
			
				|  |  | +            raise Exception("Name of the attribute must be str.")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if type(value) is str:
 | 
	
		
			
				|  |  | +            self.__properties[name] = value
 | 
	
		
			
				|  |  | +            return self
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if type(value) is int or type(value) is float:
 | 
	
		
			
				|  |  | +            self.__properties[name] = str(value)
 | 
	
		
			
				|  |  | +            return self
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        raise Exception("Value to set must be str, int or float.")
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def clone(self) -> template:
 | 
	
		
			
				|  |  | +        return template(self.template, False)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def __key(self, target: str) -> str:
 | 
	
		
			
				|  |  | +        start, end = self.separators
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return start + target + end
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def render(self) -> str:
 | 
	
		
			
				|  |  | +        if not self.__rendered is None:
 | 
	
		
			
				|  |  | +            return self.__rendered
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        template = self.template
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for name, value in self.__properties.items():
 | 
	
		
			
				|  |  | +            template = template.replace(self.__key(name), value)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        self.__rendered = template
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  | +        return self.render()
 |