def testRecursivelyEvaluateJinjaExpressions(self): file_map = { 'docA.md': self.join_lines( '~ title: test', # Creates a variable called 'title' '{{ title|capitalize }}'), } template_loader = jinja2.DictLoader({ 'some/path': '{{ blocks["all"] }}', # Use the block defined in the content. }) jinja_env = jinja2.Environment(loader=template_loader) # By default, should not recursively evaluate Jinja expressions. with self.mock_open(file_map): result = publish(ConfigBuilder().build(), source='docA.md', template='some/path', jinja_env=jinja_env, no_write=True) self.assertEquals('{{ title|capitalize }}', result) # Set recursively_evaluate_jinja_expressions to True. config = ConfigBuilder().set_recursively_evaluate_jinja_expressions( True).build() with self.mock_open(file_map): result = publish(config, source='docA.md', template='some/path', jinja_env=jinja_env, no_write=True) self.assertEquals('Test', result)
def testTemplateDirs_preventNonExistentPath(self): with mock.patch('os.path.isdir', lambda s: False): with self.assertRaises(ConfigBuilderError) as cm: ConfigBuilder().add_template_dirs('no/such/path') self.assertEqual( 'template_dir path is not a directory: no/such/path', str(cm.exception))
def testOnlyTemplate_success(self): template_loader = jinja2.DictLoader({ 'some/path': self.join_lines( '<title>{{ title }}</title>', '<ul>', '{% for user in users -%}', '<li><a href="{{ user.url }}">{{ user.username }}</a></li>', '{% endfor -%}', '</ul>') }) jinja_env = jinja2.Environment(loader=template_loader) class User(object): def __init__(self, url, username): self.url = url self.username = username # Build Templar config. config_builder = ConfigBuilder() config_builder.add_variable('title', 'Test') config_builder.add_variable( 'users', [User('url1', 'user1'), User('url2', 'user2')]) config = config_builder.build() result = publish(config, template='some/path', jinja_env=jinja_env, no_write=True) self.assertEqual( self.join_lines('<title>Test</title>', '<ul>', '<li><a href="url1">user1</a></li>', '<li><a href="url2">user2</a></li>', '</ul>'), result)
def testVariables_preventNonStrings_addVariables(self): with self.assertRaises(ConfigBuilderError) as cm: ConfigBuilder().add_variables({ 4: 'val1', 'var2': 'val2', }) self.assertEqual('variable must be a string, but instead was: 4', str(cm.exception))
def testRecursiveEvaluateJinjaExpressions(self): builder = ConfigBuilder() # Default should be False. self.assertFalse(builder.build().recursively_evaluate_jinja_expressions) builder.set_recursively_evaluate_jinja_expressions(True) self.assertTrue(builder.build().recursively_evaluate_jinja_expressions)
def testRecursivelyEvaluateJinjaExpressions_preventInfiniteLoop(self): file_map = { 'docA.md': '{{ blocks["all"] }}', # Refers to itself. } template_loader = jinja2.DictLoader({ 'some/path': '{{ blocks["all"] }}', # Use the block defined in the content. }) jinja_env = jinja2.Environment(loader=template_loader) # Set recursively_evaluate_jinja_expressions to True. config = ConfigBuilder().set_recursively_evaluate_jinja_expressions( True).build() with self.assertRaises(PublishError) as cm: with self.mock_open(file_map): result = publish(config, source='docA.md', template='some/path', jinja_env=jinja_env, no_write=True) self.assertEquals( self.join_lines( 'Recursive Jinja expression evaluation exceeded the allowed number of ' 'iterations. Last state of template:', '{{ blocks["all"] }}'), str(cm.exception))
def testTemplateDirs_preventNonBooleans(self): with self.assertRaises(ConfigBuilderError) as cm: ConfigBuilder().set_recursively_evaluate_jinja_expressions(4) self.assertEqual( 'recursively_evaluate_jinja_expressions must be a boolean, ' 'but instead was: 4', str(cm.exception))
def testOnlySource_preserveNewlinesOutsideOfBlocks(self): file_map = { 'docA.md': """<block zero> zero </block zero> <block one> one </block one> <block two> two </block two> <block three> three </block three>""" } with self.mock_open(file_map): result = publish(ConfigBuilder().build(), source='docA.md', no_write=True) self.assertEquals("""zero one two three""", result)
def testOnlySource_withRulesAndBlocks(self): file_map = { 'docA.md': self.join_lines('outer content', '<block blockA>', 'inner content', '</block blockA>', 'outer content'), } class AppliedRule(Rule): counter = 0 def apply(self, content): self.counter += 1 return 'segment {}: '.format(self.counter) + content config_builder = ConfigBuilder() config_builder.append_postprocess_rules(AppliedRule()) with self.mock_open(file_map): result = publish(config_builder.build(), source='docA.md', no_write=True) self.assertEquals( self.join_lines('segment 1: outer content', 'segment 2: inner content', 'segment 3: outer content'), result)
def testTemplateDirs(self): with mock.patch('os.path.isdir', lambda s: True): builder = ConfigBuilder().add_template_dirs('template/path1', 'template/path2') self.assertSequenceEqual( ['template/path1', 'template/path2'], builder.build().template_dirs) builder.clear_template_dirs() self.assertSequenceEqual([], builder.build().template_dirs)
def testPostprocessRules(self): rule1, rule2 = Rule(), Rule() builder = ConfigBuilder().append_postprocess_rules(rule1, rule2) self.assertSequenceEqual([rule1, rule2], builder.build().postprocess_rules) rule3, rule4 = Rule(), Rule() builder.prepend_postprocess_rules(rule3, rule4) self.assertSequenceEqual([rule3, rule4, rule1, rule2], builder.build().postprocess_rules) builder.clear_postprocess_rules() self.assertSequenceEqual([], builder.build().postprocess_rules)
def testCompilerRules(self): rule1, rule2 = Rule(), Rule() builder = ConfigBuilder().append_compiler_rules(rule1, rule2) self.assertSequenceEqual([rule1, rule2], builder.build().compiler_rules) rule3, rule4 = Rule(), Rule() builder.prepend_compiler_rules(rule3, rule4) self.assertSequenceEqual([rule3, rule4, rule1, rule2], builder.build().compiler_rules) builder.clear_compiler_rules() self.assertSequenceEqual([], builder.build().compiler_rules)
def testOnlySource_withLinks(self): file_map = { 'docA.md': self.join_lines('To be or not to be', '<include docB.md:hamlet>'), 'docB.md': self.join_lines('Catchphrases', '<block hamlet>', 'That is the question', '</block hamlet>'), } with self.mock_open(file_map): result = publish(ConfigBuilder().build(), source='docA.md', no_write=True) self.assertEquals( self.join_lines('To be or not to be', 'That is the question'), result)
def testConfigIsImmutable(self): with mock.patch('os.path.isdir', lambda s: True): builder = ConfigBuilder().add_template_dirs('template/path1', 'template/path2') builder.add_variable('var1', 'val1') config = builder.build() # Verify config was constructed correctly. self.assertSequenceEqual(['template/path1', 'template/path2'], config.template_dirs) self.assertDictEqual({'var1': 'val1'}, config.variables) new_builder = config.to_builder() new_builder.clear_template_dirs() new_builder.add_variable('var2', 'val2') # Verify previously built config was not affected by changes to new_builder. self.assertSequenceEqual(['template/path1', 'template/path2'], config.template_dirs) self.assertDictEqual({'var1': 'val1'}, config.variables)
def testSourceAndTemplate(self): file_map = { 'docA.md': self.join_lines( '~ title: Test', # Creates a variable called 'title' 'outside block', '<block first>', 'inside block', '</block first>'), } template_loader = jinja2.DictLoader({ 'some/path': self.join_lines( '<title>{{ title }}</title>', # Use the variable defined in the content. '<p>', '{{ blocks.first }}', # Use the block defined in the content. '{{ var }}', # Use the variable defined by VarRule, below. '</p>') }) jinja_env = jinja2.Environment(loader=template_loader) # Test rule application. class UpperCaseRule(Rule): def apply(self, content): return content.upper() class VarRule(VariableRule): def extract(self, content): return {'var': 'val'} config_builder = ConfigBuilder() config_builder.append_preprocess_rules(UpperCaseRule(), VarRule()) with self.mock_open(file_map): result = publish(config_builder.build(), source='docA.md', template='some/path', jinja_env=jinja_env, no_write=True) self.assertEquals( self.join_lines('<title>Test</title>', '<p>', 'INSIDE BLOCK', 'val', '</p>'), result)
def testWrite(self): template_loader = jinja2.DictLoader({ 'some/path': 'content', }) jinja_env = jinja2.Environment(loader=template_loader) # Use a simple mock_open instead of this test's mock_open method, since we don't need to # open multiple files. mock_open = mock.mock_open() with mock.patch('builtins.open', mock_open): result = publish(ConfigBuilder().build(), template='some/path', destination='dest.html', jinja_env=jinja_env) # Verify a file handle to the destination file was opened. mock_open.assert_called_once_with('dest.html', 'w') # Verify the result was actually written to the destination fie handle. handle = mock_open() handle.write.assert_called_once_with('content')
def testOnlySource_withRules(self): file_map = { 'docA.md': 'original content', } class AppliedRule(Rule): def apply(self, content): return content + ' rule1' class NotAppliedRule(Rule): def apply(self, content): return content + 'rule2' config_builder = ConfigBuilder() config_builder.append_preprocess_rules(AppliedRule(src=r'\.md'), NotAppliedRule(dst=r'\.html')) config_builder.append_postprocess_rules(AppliedRule(), NotAppliedRule(src=r'\.py')) with self.mock_open(file_map): result = publish(config_builder.build(), source='docA.md', no_write=True) self.assertEquals('original content rule1 rule1', result)
def testMissingSourceAndTemplate(self): with self.assertRaises(PublishError) as cm: publish(ConfigBuilder().build(), no_write=True) self.assertEquals( "When publishing, source and template cannot both be omitted.", str(cm.exception))
def testVariables_addVariables(self): builder = ConfigBuilder().add_variables({ 'var1': 'val1', 'var2': 'val2', }) self.assertDictEqual({'var1': 'val1', 'var2': 'val2'}, builder.build().variables)
def testVariables_addVariable(self): builder = ConfigBuilder().add_variable('var1', 'val1').add_variable('var2', 'val2') self.assertDictEqual({'var1': 'val1', 'var2': 'val2'}, builder.build().variables) builder.clear_variables() self.assertDictEqual({}, builder.build().variables)
# A valid config file must contain a variable called 'config' that is a Config # object. from templar.api.config import ConfigBuilder config = ConfigBuilder() # Didn't call build.
def testTemplateDirs_preventNonStrings(self): with self.assertRaises(ConfigBuilderError) as cm: ConfigBuilder().add_template_dirs(4) self.assertEqual( 'template_dir must be a string, but instead was: 4', str(cm.exception))
def testRules(self): rule1, rule2, rule3, = Rule(), Rule(), Rule() builder = ConfigBuilder().append_preprocess_rules(rule1) builder.append_compiler_rules(rule2) builder.append_postprocess_rules(rule3) self.assertSequenceEqual([rule1, rule2, rule3], builder.build().rules)
def testPostprocessRules_preventNonRules(self): with self.assertRaises(ConfigBuilderError) as cm: ConfigBuilder().append_postprocess_rules(4) self.assertEqual( 'postprocess_rule must be a Rule object, but instead was: 4', str(cm.exception))
if os.path.isfile("PH_PATH"): f = open("PH_PATH", 'r') for line in f: PH_PATH = line.rstrip() PH_ASSETS_PATH = os.path.join(PH_PATH, "assets") ################## # Configurations # ################## class HomePageLinkRule(SubstitutionRule): pattern = r'<home-page-link>' def substitute(self, match): return PH_PATH config = ConfigBuilder().add_template_dirs(os.path.join( FILEPATH, "templates"), ).add_variables({ 'datetime': '{dt:%A}, {dt:%B} {dt.day}, {dt:%Y} at {dt:%l}:{dt:%M} {dt:%p}'.format( dt=datetime.now()), 'class': 'CS 195', }).append_compiler_rules(MarkdownToHtmlRule(), ).append_postprocess_rules( HomePageLinkRule(src='md$', dst='html$'), HtmlTableOfContents(), ).build()
def testEmptyBuilder(self): config = ConfigBuilder().build() self.assertSequenceEqual([], config.template_dirs) self.assertDictEqual({}, config.variables) self.assertSequenceEqual([], config.preprocess_rules) self.assertSequenceEqual([], config.postprocess_rules)
def testCompilerRules_preventNonRules(self): with self.assertRaises(ConfigBuilderError) as cm: ConfigBuilder().append_compiler_rules(4) self.assertEqual( 'compiler_rule must be a Rule, but instead was: 4', str(cm.exception))
from templar.api.config import ConfigBuilder from templar.api.rules.core import SubstitutionRule from templar.api.rules.compiler_rules import MarkdownToHtmlRule from templar.api.rules.table_of_contents import HtmlTableOfContents class ImageRule(SubstitutionRule): pattern = re.compile(r'(<img.*?)>') def substitute(self, match): return match.group(1) + ' class="img-responsive">' _config_builder = ConfigBuilder().add_template_dirs( 'templates', 'projects/templates', 'cs61a/review/templates', ).add_variables({ 'MASTER_DIR': '', 'CS61A_DIR': '/cs61a', 'REVIEW_DIR': '/cs61a/review', 'NOTES_DIR': '/cs61a/notes', 'BLOG_DIR': '/blog', 'PROJECTS_DIR': '/projects', }).append_compiler_rules(MarkdownToHtmlRule()).append_postprocess_rules( HtmlTableOfContents(), ImageRule(dst=r'\.html'), ) config = _config_builder.build()
from templar.api.config import ConfigBuilder import os.path config = ConfigBuilder().add_template_dirs(os.path.dirname(__file__)).build()