diff --git a/PythonLatex/generate_latex.py b/PythonLatex/generate_latex.py index f06c2c1..69b1898 100644 --- a/PythonLatex/generate_latex.py +++ b/PythonLatex/generate_latex.py @@ -1,373 +1,375 @@ #! /usr/bin/python ################################################################ from __future__ import print_function ################################################################ import re as myre import PythonLatex.latex_structure as tex import types import sympy as sym import hashlib import dill ################################################################ from sympy.printing.latex import LatexPrinter from sympy import latex from sympy.core.function import Derivative ################################################################ class CustomStrPrinter(LatexPrinter): def nbIdenticalDx(self, expr): first_dx = expr.args[1:][0] count = 0 for dx in expr.args[1:]: if dx == first_dx: count += 1 if count == len(expr.args[1:]): return count, first_dx return 0, None def _print_Derivative(self, expr): # print expr # print LatexPrinter._print_Derivative(self,expr) count, dx = self.nbIdenticalDx(expr) f = expr.args[0] if len(f.args) == 0: return LatexPrinter._print_Derivative(self, expr) dxf = f.args[0] f_nargs = len(f.args) # case where ' applies if f_nargs == 1 and dx == dxf: return (latex(expr.args[0].func) + "'" * len(expr.args[1:]) + "(" + latex(dxf) + ")") return LatexPrinter._print_Derivative(self, expr) def _print_Subs(self, expr): if not isinstance(expr.args[0], Derivative): return LatexPrinter._print_Subs(self, expr) # print expr deriv = expr.args[0] _dxs = deriv.args[1:] # print _dxs p1 = expr.args[1] p2 = expr.args[2] # print len(_dxs) _subst = {} _subst2 = {} for i in range(0, len(p1)): _subst[p1[i]] = p2[i] if hasattr(p2[i], 'args'): _p2 = type(p2[i]) _subst2[p1[i]] = _p2 _dxs = [e.subs(_subst2) for e in _dxs] new_f = deriv.args[0].subs(_subst) # print 'CCCCCCCCC' # print new_f # print _dxs # print _subst tmp = Derivative(new_f, *_dxs) # print tmp return latex(tmp) # return LatexPrinter._print_Subs(self,expr) ################################################################ class PyCode(object): """ """ def checkLine(self, line): m = myre.match(r"\\end{python}", line) if m: return Block() return Block.checkLine(self, line) def checkValidity(self): for c in self.content: if isinstance(c, types.InstanceType): raise Exception("python block cannot have subblocks") def __str__(self): return "" def generateHashSource(self): txt_content = self.content.encode() self.hash_source = hashlib.sha1(txt_content).hexdigest() gl = globals() self.global_keys_before = gl.keys() self.local_keys_before = self.__dict__.keys() def needReexecute(self): if self.alias == 'header': return True if 'loaded_hash_source' in self.__dict__: return not self.loaded_hash_source == self.hash_source return True def saveBlockOutputInfo(self): if self.alias == 'header': return global_keys = globals().keys() global_keys_to_save = set(global_keys) - set(self.global_keys_before) global_keys_to_save = list(global_keys_to_save) global_entries_to_save = dict( [(e, globals()[e]) for e in global_keys_to_save]) local_keys = self.__dict__.keys() local_keys_to_save = set(local_keys) - set(self.local_keys_before) local_keys_to_save = list(local_keys_to_save) local_entries_to_save = dict( [(e, self.__dict__[e]) for e in local_keys_to_save]) try: for k in global_entries_to_save: try: print('try to save ', k, ' ', latex(global_entries_to_save[k])) global_entries_to_save[k] = dill.dumps( global_entries_to_save[k]) print('saved ', k) except Exception as e: print(e) self.global_block_result = dill.dumps( global_entries_to_save) self.local_block_result = dill.dumps(local_entries_to_save) self.global_hash_result = hashlib.sha1( self.global_block_result).hexdigest() self.local_hash_result = hashlib.sha1( self.local_block_result).hexdigest() except Exception as e: print("cannot save block: {0}\n{1}".format(self.alias, e)) self.global_block_result = None self.global_hash_result = None self.local_block_result = None self.local_hash_result = None self.hash_source = None filename = self.alias + '.pytex.out' f = open(filename, 'w') # print "save symbols {0}: hash_source {1}".format( # self.alias,self.hash_source) raw_save = [self.hash_source, self.global_hash_result, self.global_block_result, self.local_hash_result, self.local_block_result] dill.dump(raw_save, f) f.close() def loadBlockOutputInfo(self): return if self.alias == 'header': return filename = self.alias + '.pytex.out' try: f = open(filename, 'r') self.loaded_hash_source, self.global_hash_result, self.global_block_result, self.local_hash_result, self.local_block_result = dill.load(f) # print "loaded symbols" # print "AAAAAAAAAAA " + str(self.loaded_hash_source) globals().update(dill.loads(self.global_block_result)) self.__dict__.update(dill.loads(self.local_block_result)) # print pickle.loads(self.global_block_result).keys() # print self.__dict__.keys() f.close() print('loaded {0}'.format(filename)) except Exception: # print "Could not load block " + self.alias # print e pass def evalBlock(self): txt_content = self.content self.generateHashSource() self.loadBlockOutputInfo() need_reexecute = self.needReexecute() if need_reexecute or reexec: print("execute block: {0}".format(self.alias)) gl = globals() gl.update({'self': self}) try: exec(txt_content, gl) except Exception as e: mesg = """{0}:0: For block '{1}' there was an execution problem {2} Block was: ************ {3} """.format(parsed_latex_file, self.alias, e, txt_content) print(mesg) if not continue_flag: raise RuntimeError('have to stop') del gl['self'] # self.saveBlockOutputInfo() def getContent(self, varname): try: c = self.__dict__[varname] except Exception as ex: print(ex) raise Exception("cannot retreive variable " + varname + " from block " + self.alias) if (myre.search('sympy', str(type(c)))): c = sym.latex(c) return c def __init__(self, name, alias, content): self.name = name self.alias = alias self.content = content if not self.name == 'python': raise Exception( 'cannot create a PyCode block from block type ' + self.name) if self.alias is None: raise Exception("not valid python block") self.block_eval = None ################################################################ def evaluatePythonBlock(b): + print('AAAAA', type(b), b.name, b.content) + if isinstance(b, tex.LatexEnvironment) and b.name == 'python': b.hide = True b = PyCode(b.name, b.option, str(b.toks[1])) globals()[b.alias] = b b.evalBlock() def replaceAliases(b, text): if isinstance(b, tex.LatexEnvironment) and b.name == 'python': return text if type(text) == str: - # print text regex = r'\\py{(.*?)}' m = myre.findall(regex, text) if not m: return text # print text for expression in m: # print('replaceAliasses: ', expression) try: d = dict() exec("ans = " + expression, globals(), d) except Exception as e: print("Block " + str(b.name) + " Cannot parse expression '" + str(expression) + "'\n" + str(e)) if continue_flag: continue raise(e) ans = d['ans'] rep = '\\py{' + expression + '}' try: ans_str = str(latex(ans)) except Exception as e: print("AAAAAAAA " + str(e)) return text # print "ans is now " + str(ans) globals().update({'ans': ans}) # print "replace " + rep + " with " + cont text = text.replace(rep, ans_str, 1) return text ################################################################ def collectScript(b, text): if isinstance(b, tex.LatexEnvironment) and b.name == 'python': # print('AAAA ', b) globals()['total_python_script'] += "{0} = CodeEval()\n".format( b.option) globals()['total_python_script'] += text.replace( 'self', b.option) return text if type(text) == str: # print text regex = r'\\py{(.*?)}' m = myre.findall(regex, text) if not m: return text # print m for expression in m: globals()['total_python_script'] += ( "print('printing expression: {0}')\n".format( str(expression).replace("'", "\\'"))) globals()['total_python_script'] += ( "ans = " + expression + "\n") globals()['total_python_script'] += ( "print(str(latex(ans)) + '\\n\\n')\n") return text ################################################################ continue_flag = False reexec = True def interpretPython(filename, c_flag, reexec_flag): continue_flag = c_flag reexec = reexec_flag tex_struct = tex.LatexStructure() # tex_struct.buildLatexBlocks(filename) tex_struct.parseLatexFile(filename) total_python_script = """ from PythonLatex.generate_latex import * class CodeEval: pass """ globals().update({'total_python_script': total_python_script}) globals().update({'reexec': reexec}) globals().update({'continue_flag': continue_flag}) globals().update({'parsed_latex_file': filename}) tex_struct.pathInBlock(text_functor=collectScript) + + total_python_script = globals()['total_python_script'] + tmp_file = open('python_script.py', 'w') + tmp_file.write(total_python_script) + tmp_file.close() + tex_struct.pathInBlock(begin_functor=evaluatePythonBlock, text_functor=replaceAliases) - total_python_script = globals()['total_python_script'] # pyblocks = tex_struct.getBlocksFromType("python") # # for b in pyblocks: # total_python_script += b.content[0] - tmp_file = open('python_script.py', 'w') - tmp_file.write(total_python_script) - tmp_file.close() - return str(tex_struct) diff --git a/PythonLatex/latex_structure.py b/PythonLatex/latex_structure.py index 9a08ddf..d3631fd 100644 --- a/PythonLatex/latex_structure.py +++ b/PythonLatex/latex_structure.py @@ -1,361 +1,357 @@ #!/ usr / bin / python ################################################################ from __future__ import print_function ################################################################ import re import types import pyparsing as pp ################################################################ class LatexEnvironment(object): def __init__(self, toks): self.toks = toks self.content = self.toks[1:-1] self.head = self.toks[0] self.tail = self.toks[-1] self.name = self.head.toks[3] self.hide = False try: self.option = self.head.toks[6] except Exception as e: self.option = None # print('env: {0}:{1}:{2}'.format(self.name, self.option, self.content)) def __str__(self): if self.hide: return '' return ''.join([str(self.head)] + [str(i) for i in self.content] + [str(self.tail)]) def __getitem__(self, index): return self.toks[index] class LatexCommand(object): def __init__(self, toks): self.name = toks[1] self.toks = toks # print('create_command:', self.name, toks) def __str__(self): return ''.join(self.toks) def __getitem__(self, index): return self.toks[index] class LatexBlock(object): def __init__(self, toks, name=None): self.toks = toks self.name = name def __str__(self): return ''.join([str(t) for t in self.toks]) class LatexMain(object): def __init__(self, toks): self.toks = toks self.content = self.toks self.name = "main" def __str__(self): # for i in self.toks: # print('LatexMain:\n', type(i), str(i)) res = ''.join([str(t) for t in self.toks]) # print(res) return res def __getitem__(self, index): return self.toks[index] ################################################################ class LatexStructure: def getBlocksFromType(self, typ): mylist = [] def foo(b): if b.name == typ: mylist.append(b) self.pathInBlock(fbegin=foo) return mylist @staticmethod def ppValidCharacters(): valid_characters = pp.printables valid_characters = valid_characters.replace('%', '') valid_characters = valid_characters.replace('{', '') valid_characters = valid_characters.replace('}', '') valid_characters = valid_characters.replace('\\', '') valid_characters += ' \t\r\n' return valid_characters @property def text(self): if '_text' not in self.__dict__: self._text = pp.Word(self.ppValidCharacters()) self._text = (self._text | pp.Literal('\\\\') | pp.Literal(r'\&') | pp.Literal(r'\%') | pp.Literal(r'\_')) self._text.leaveWhitespace() # self._text.addParseAction(lambda toks: print('text:', toks)) return self._text @property def comment(self): if '_comment' not in self.__dict__: self._comment = pp.Literal('%') self._comment += pp.SkipTo(pp.LineEnd()) # self._comment.leaveWhitespace() # self._comment.addParseAction(lambda toks: print('comment: ', toks)) return self._comment @property def block(self): if '_block' not in self.__dict__: _start = pp.Literal('{') _end = pp.Literal('}') _content = pp.Forward().leaveWhitespace() _block = _start + _content + _end self._block = _block - _content << pp.ZeroOrMore(self.environment | + _content << pp.ZeroOrMore(self.environment() | self.ppCommand() | self.block | self.text | self.comment) self._block.leaveWhitespace() def createBlock(toks): b = LatexBlock(toks) return b # _block.addParseAction(lambda toks: print('block:', toks)) _block.addParseAction(createBlock) return self._block.leaveWhitespace() @staticmethod def ppCommand(name=None): _command = pp.Literal('\\').leaveWhitespace() if name is None: _command += pp.Word(pp.alphanums + '@').leaveWhitespace() else: _command += pp.Literal(name).leaveWhitespace() option = ( pp.Literal('[') + pp.delimitedList(pp.Word(pp.alphanums), combine=True) + pp.Literal(']')).leaveWhitespace() valid_param_character = pp.printables + ' \t\r\n\\' valid_param_character = valid_param_character.replace('{', '') valid_param_character = valid_param_character.replace('}', '') param_name = pp.delimitedList(pp.Word(valid_param_character), combine=True) parameters = ( pp.Literal('{') + param_name + pp.Literal('}').leaveWhitespace()).leaveWhitespace() _command += pp.ZeroOrMore(option | parameters).leaveWhitespace() def createCommand(toks): c = LatexCommand(toks) return c _command.addParseAction(createCommand) # _command.addParseAction(lambda cmd: print('create_command:', - # cmd[0].name, cmd[0].toks)) + # cmd[0].name, cmd[0].toks)) return _command.leaveWhitespace() - @property def environment(self): - if '_environment' in self.__dict__: - return self._environment - _env_start = self.ppCommand('begin') _env_end = self.ppCommand('end') _env_content = pp.Forward().leaveWhitespace() - self._environment = _env_start + _env_content - self._environment += _env_end + _environment = _env_start + _env_content + _environment += _env_end def set_excluding_command(toks): - # print('set_excluding_command:', toks) env_name = toks[0][3] + # print('startenv:', env_name) _command_excluding = self.ppCommand() # print('command_excluding:', env_name) if env_name == 'python': python_block = pp.SkipTo(pp.Literal(r'\end{python}')) _env_content << python_block else: _env_content << pp.ZeroOrMore( - self._environment.leaveWhitespace() | + self.environment().leaveWhitespace() | _command_excluding | self.block | self.text | self.comment) def check(toks, env_name): # print('check:', env_name) if toks[0][1] != 'end': return toks # print('check:', toks, env_name) # print('check2:', toks[0], env_name) # print('check2:', type(toks[0]), env_name) # print('check3:', toks[0][3], env_name) # print('check2:', toks[0][3]) if toks[0][3] == env_name: # print ('biiiiip') return toks[652336456] return toks # _command_excluding.addParseAction( # lambda toks: print('command_excluding:', toks)) _command_excluding.addParseAction( lambda toks: check(toks, env_name)) # print('set_excluding_command: done') _env_start.addParseAction(set_excluding_command) # _env_content.addParseAction(lambda toks: print('found_content:', toks)) - # _env_end.addParseAction(lambda toks: print('found_end:', toks)) + # _env_end.addParseAction(lambda toks: print('found_end:', toks[0][3])) def createEnvironment(toks): e = LatexEnvironment(toks) return e - self._environment.addParseAction(createEnvironment) - return self._environment + _environment.addParseAction(createEnvironment) + return _environment.leaveWhitespace() def parseLatexFile(self, filename): fin = open(filename, 'r') inp = fin.read() fin.close() self.parseLatex(inp) def parseLatex(self, latex_code): - _content = pp.ZeroOrMore(self.environment | + _content = pp.ZeroOrMore(self.environment() | self.block | self.ppCommand() | self.text | self.comment) _content.leaveWhitespace() self._content = LatexMain(_content.parseString(latex_code)) def buildLatexBlocks(self, filename, herited_types=dict()): fin = open(filename, 'r') inp = fin.readlines() inp = "".join(inp) latex_cmd_expr = r'(\\\w+(?:\[\w*\])*(?:{[\w|,|\.|(|)]*?})+)' splitted = re.split(latex_cmd_expr, inp) self.main_block = LatexBlock() self.current_block = self.main_block for i in splitted: m = re.match(r'\\begin{(.*?)}(.*)', i) if m: name = m.group(1) options = m.group(2) self.current_block = self.current_block.createSubBlock( name, options, herited_types) continue m = re.match(r'\\end{(.*?)}', i) if m: name = m.group(1) try: self.current_block = self.current_block.endSubBlock(name) except Exception as e: print("AAAAAAAAAAAAAAAAAA") print(e) continue self.current_block.appendContent(i) if not self.current_block == self.main_block: raise Exception( "one latex block was not closed: {0}".format( self.current_block.name)) def pathInBlockOld(self, block=None, begin_functor=None, end_functor=None, text_functor=None): if block is None: block = self.main_block if begin_functor is not None: begin_functor(block) for i in range(len(block.content)): c = block.content[i] if isinstance(c, types.InstanceType): try: self.pathInBlock(c, begin_functor, end_functor, text_functor) except Exception as e: print(e) else: if text_functor is not None: block.content[i] = text_functor(block, c) if end_functor is not None: end_functor(block) def pathInBlock(self, block=None, begin_functor=None, end_functor=None, text_functor=None): if text_functor is None: def text_functor(b, c): return c if block is None: block = self._content if begin_functor is not None: begin_functor(block) for i, c in enumerate(block.content): # print(type(c), c) if isinstance(c, LatexEnvironment): self.pathInBlock(c, begin_functor, end_functor, text_functor) else: block.content[i] = text_functor(block, str(c)) if end_functor is not None: end_functor(block) def __str__(self): return str(self._content) @property def content(self): return self._content def __init__(self): self._content = None ################################################################ diff --git a/tests/test_platex.py b/tests/test_platex.py index 159887a..00b6ca6 100644 --- a/tests/test_platex.py +++ b/tests/test_platex.py @@ -1,163 +1,289 @@ #!/bin/env python3 # -*- coding: utf-8 -*- from __future__ import print_function import unittest try: from PythonLatex.latex_structure import LatexStructure except: pass class pLatexTest(unittest.TestCase): "Unit tests for pLatex" def setUp(self, ): pass def test_latex_parser_beamer(self): latex_code = r""" \documentclass[9pt,xcolor=dvipsnames]{beamer} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \usepackage{fancyvrb} \begin{python}{header} from Slides.snippet_helper import Snippet, SnippetCollection Snippet.default_output='latex' # Snippet.default_line_numbering=True \end{python} \py{Snippet.getLatexStyleDefs()} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \input{class.tex} \title{Chapter 4. Pointers} \begin{document} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \maketitle %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{frame}[fragile]{Pointers and the Computer's Memory} \only<1>{ \py{yop} } \only<2>{ } $p_x$ ROW\_MAJOR \end{frame} \end{document} """ tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) f = open('tmp.tex', 'w') f.write(latex_code) f.close() f = open('tmp_output.tex', 'w') f.write(output) f.close() if latex_code != output: print(output) self.assertEqual(latex_code, output) def test_latex_parser_underscore(self): latex_code = r'ROW\_MAJOR' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_parser_nested_begin_end(self): latex_code = r''' \begin{toto}tatie \begin{re}tututoto \end{re} aa\end{toto} ''' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_parser_begin_end(self): latex_code = r'\begin{toto}tatie\end{toto}' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_parser_text(self): latex_code = r'tatie' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_parser_comment(self): latex_code = r''' % tatie toto ''' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_parser_command(self): latex_code = r' \documentclass[10pt]{article}' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_parser_nested_begin_command(self): latex_code = r'''{ yala {toto } \begin{align} tutu \begin{equation} toto \end{equation} \end{align} {\titi{ tutu} } }''' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) def test_latex_env(self): latex_code = r''' \begin{toto} tata \end{toto} \begin{tata} toto \end{tata} \begin{tutu} titi \begin{titi} \end{titi} \end{tutu} ''' tex_struct = LatexStructure() tex_struct.parseLatex(latex_code) output = str(tex_struct) self.assertEqual(latex_code, output) blk_list = [] - def foo(*args): - for i in args: - blk_list.append(i.name) + def foo(i): + blk_list.append(i.name) blk_list_correct = ['main', 'toto', 'toto', 'tata', 'tata', 'tutu', 'titi', 'titi', 'tutu', 'main'] tex_struct.pathInBlock(begin_functor=foo, end_functor=foo) self.assertEqual(blk_list, blk_list_correct) + + def test_latex_python_code(self): + latex_code = r''' +\begin{python}{cpp} +snippet = Snippet('code_snippets/hello.cpp') +\end{python} +''' + tex_struct = LatexStructure() + tex_struct.parseLatex(latex_code) + output = str(tex_struct) + self.assertEqual(latex_code, output) + + def test_latex_frame_includegraphics_only(self): + latex_code = r''' +\begin{frame}{What is a computer ?} +\includegraphics<2>[width=\textwidth]{figures/computer-components.png} +\end{frame} + +\begin{frame}{What is a program ?} + +toto +\end{frame} +''' + tex_struct = LatexStructure() + tex_struct.parseLatex(latex_code) + output = str(tex_struct) + self.assertEqual(latex_code, output) + + blk_list = [] + + def foo(i): + blk_list.append(i.name) + + tex_struct.pathInBlock(begin_functor=foo, end_functor=foo) + + blk_list_correct = ['main', + 'frame', 'frame', + 'frame', 'frame', + 'main'] + + self.assertEqual(blk_list, blk_list_correct) + + def test_latex_item_beamer(self): + latex_code = r''' +\begin{document} +\begin{frame} + \section{Class organization} + \begin{itemize} + \item<1-> Teaching staff: G. Anciaux, A. Nielsen, L. Pegolotti. + \item<2-> Lectures: on Mondays, exercises on Fridays + \item<3-> Follow chapters of the book: \href{http://link.springer.com/book/10.1007/978-1-4471-2736-9}{Guide To Scientific Computing in C++} + \item<4-> Permanent homework: reading next chapter of the book + \item<5-> Moodle (password: PCSC2017): material, forum (at the beginning + \item<6-> Git: material, pdfs, solutions + \item<7-> Evaluation: project realization and oral presentation + \end{itemize} +\end{frame} + +\begin{frame} + +\end{frame} +\end{document} +''' + tex_struct = LatexStructure() + tex_struct.parseLatex(latex_code) + output = str(tex_struct) + self.assertEqual(latex_code, output) + + blk_list = [] + + def foo(i): + blk_list.append(i.name) + + tex_struct.pathInBlock(begin_functor=foo, end_functor=foo) + + blk_list_correct = ['main', + 'document', + 'frame', + 'itemize', 'itemize', + 'frame', + 'frame', + 'frame', + 'document', + 'main'] + + self.assertEqual(blk_list, blk_list_correct) + + def test_latex_python_frame(self): + latex_code = r''' +\begin{python}{header} +b = 100 +\end{python} +\begin{document} +\begin{frame} +\end{frame} + +\begin{python}{cpp} +a = 10 +\end{python} +\begin{frame}[fragile] +\py{str(a)} +\end{frame} +\end{document} +''' + tex_struct = LatexStructure() + tex_struct.parseLatex(latex_code) + output = str(tex_struct) + self.assertEqual(latex_code, output) + + blk_list = [] + + def foo(i): + blk_list.append(i.name) + + tex_struct.pathInBlock(begin_functor=foo, end_functor=foo) + + blk_list_correct = ['main', + 'python', 'python', + 'document', + 'frame', 'frame', + 'python', 'python', + 'frame', 'frame', + 'document', + 'main'] + + print(blk_list) + self.assertEqual(blk_list, blk_list_correct)