def test_parse_call(): parser = PegParser() f = parser.parse_call("function(alpha, bravo, charlie)") assert f.__class__.__name__ == 'Call' assert f.name == 'function' assert f.arguments == ['alpha', 'bravo', 'charlie'] f = parser.parse_call("function('Alpha', 1)") assert f.__class__.__name__ == 'Call' assert f.name == 'function' assert f.arguments == ["'Alpha'", '1'] f = parser.parse_call("function(1, bravo=2, charlie=3)") assert f.__class__.__name__ == 'Call' assert f.name == 'function' assert f.arguments == ['1', ('bravo', '2'), ('charlie', '3')]
class HtmlGenerator(object): def __init__(self, include_paths): self.HANDLERS = { LineToken.LINE_TYPE_ROOT: self.handle_root, LineToken.LINE_TYPE_COMMENT: self.handle_comment, LineToken.LINE_TYPE_CODE: self.handle_code, LineToken.LINE_TYPE_DJANGO: self.handle_django, LineToken.LINE_TYPE_TAG: self.handle_tag, LineToken.LINE_TYPE_CALL: self.handle_call, } # Code variables (for now, later it will be more complex... with IFs # and FORs and expand_vars... self.functions = {} self.variables = {} self.include_paths = include_paths from pypugly.peg_parser import PegParser self.__parser = PegParser() def generate(self, line_token): """ Generates HTML from a line_token (tree). :param LineToken line_token: :return str: """ lines = self._handle_line_token(line_token, self.variables) return '\n'.join(lines) def _handle_line_token(self, t, context): handler = self.HANDLERS.get(t.line_type) assert \ handler is not None, \ 'No handler for token of type "{}"'.format(t.line_type) result = [] try: result += handler(t, after=False, context=context) result += self._handle_children(t.children, context=context) result += handler(t, after=True, context=context) except Exception as e: reraise(e, 'While handling line-token {}'.format(six.text_type(t))) return result def _handle_children(self, children, context): result = [] for i_child in children: result += self._handle_line_token(i_child, context=context) return result def handle_root(self, token, after, context): return [] def handle_code(self, token, after, context): if after: return [] code = self.__parser.parse_code(token.line) code_class = code.__class__.__name__ if code_class == 'Assignment': self.variables[six.text_type(code.left)] = literal_eval(code.right) elif code_class == 'Def': self.functions[six.text_type(code.name)] = \ Function(code.name, code.parameters, token.children) token.children = [] return [] elif code_class == 'Include': filename = self._eval(code.filename, context) parser = PugParser() input_contents = GetFileContents(self._find_file(filename)) token_tree = parser.tokenize(input_contents) return self._handle_line_token(token_tree, self.variables) elif code_class == 'ForLoop': return [token.indentation + repr(code)] return [] def handle_call(self, token, after, context): if after: return [] # Parse function call... code = self.__parser.parse_call(token.line) # Obtain the associated function function = self.functions.get(six.text_type(code.name)) assert function is not None # Prepare arguments arguments = function.format_arguments(code.arguments) # Prepare context for the function call, adding the global variables # and argument values context = self.variables context.update(arguments) # Call the function 'code' tokens = function.code[:] # Replaces the code indent with the function call indent. for i in tokens: i.indent -= 1 return self._handle_children(tokens, context=context) def handle_django(self, token, after, context): if after: if not token.children: return [] code = self.__parser.parse_django(token.line) end_tag = code.name end_tag = '{% end' + end_tag.strip() + ' %}' return [token.indentation + end_tag] else: code = self.__parser.parse_django(token.line) start_tag = code.name + ' ' + code.restline start_tag = '{% ' + start_tag.strip() + ' %}' return [token.indentation + start_tag] def handle_comment(self, token, after, context): if after: return [] return [] def handle_tag(self, token, after, context): # Parses the TAG line, extracting the id, classes, arguments and the # contents. try: tag = self.__parser.parse_tag(token.line) except Exception as e: reraise(e, 'While parsing tag in line %d' % token.line_no) raise result = [] have_content = hasattr(tag, 'content') and tag.content have_children = len(token.children) > 0 if have_content and have_children: raise RuntimeError('A tag should have contents OR children nodes.') if after: if not have_content and have_children: result.append( token.indentation + '</{name}>'.format( name=tag.name ) ) else: args = getattr(tag, 'args', []) args = { i.key: self._eval(getattr(i, 'value', 'True'), context) for i in args } tag_text = create_tag( tag.name, args, klass=tag.classes, id_=tag.id ) if have_content or have_children: tag_format = '<{}>' else: tag_format = '<{} />' line = token.indentation + tag_format.format(tag_text) if have_content: # With content, close the tag in the same line. end_tag = '</{}>'.format(tag.name) content = self._eval(tag.content, context) line += content + end_tag result.append(line) return result def _eval(self, literal, context): """ Evaluates a literal. For now, we evaluate only text (unicode) or boolean values (True/False). :param str literal: :return object: """ try: result = literal_eval(literal) if isinstance(result, bytes): result = result.decode('UTF-8') if isinstance(result, six.text_type): result = result.format(**context) return result except Exception as e: reraise( e, 'While evaluation literal: "{}" with context: {}'.format( literal, ', '.join(context.keys()) ) ) def _find_file(self, filename): from zerotk.easyfs._exceptions import FileNotFoundError filenames = [] for i_include_path in self.include_paths: filenames.append(i_include_path + '/' + filename) for i_filename in filenames: if IsFile(i_filename): return i_filename raise FileNotFoundError(filename)