├── dmsl ├── test │ ├── templates │ │ ├── py_raise.html │ │ ├── footer.dmsl │ │ ├── header.dmsl │ │ ├── py_raise.dmsl │ │ ├── py_newline_var.html │ │ ├── py_newline_var.dmsl │ │ ├── py_if_nested.html │ │ ├── basic_tabs.dmsl │ │ ├── py_ending_colon.dmsl │ │ ├── py_extends.dmsl │ │ ├── py_embed.html │ │ ├── basic_ending_colon.dmsl │ │ ├── py_func.html │ │ ├── basic_ending_colon.html │ │ ├── basic_tabs.html │ │ ├── py_ifordering.html │ │ ├── py_format_dict.dmsl │ │ ├── py_ifelse.html │ │ ├── py_embed.dmsl │ │ ├── py_func.dmsl │ │ ├── py_block_default.dmsl │ │ ├── py_ending_colon.html │ │ ├── py_ifordering.dmsl │ │ ├── basic_html.html │ │ ├── py_extends.html │ │ ├── py_formatter.html │ │ ├── py_complex3.html │ │ ├── py_ifelse.dmsl │ │ ├── py_block_default.html │ │ ├── py_include.html │ │ ├── basic_comments.dmsl │ │ ├── py_complex3.dmsl │ │ ├── basic_multilinetext.html │ │ ├── py_mixed_content.dmsl │ │ ├── basic_indent.html │ │ ├── py_include.dmsl │ │ ├── basic_variable_indent.html │ │ ├── basic_html.dmsl │ │ ├── py_formatter.dmsl │ │ ├── py_looping.html │ │ ├── py_complex1.html │ │ ├── py_mixed_content.html │ │ ├── basic_multilinetext.dmsl │ │ ├── py_if_nested.dmsl │ │ ├── template.html │ │ ├── py_complex2.html │ │ ├── basic_variable_indent.dmsl │ │ ├── basic_indent.dmsl │ │ ├── basic_inline.html │ │ ├── py_nested_for.dmsl │ │ ├── py_nested_for.html │ │ ├── basic_tag_hashes.dmsl │ │ ├── py_looping.dmsl │ │ ├── basic_inline.dmsl │ │ ├── py_complex.dmsl │ │ ├── basic_tag_hashes.html │ │ ├── evolve2.dmsl │ │ ├── template.dmsl │ │ ├── py_complex2.dmsl │ │ ├── py_complex1.dmsl │ │ └── evolve.dmsl │ ├── __main__.py │ ├── test_basic.py │ └── test_py.py ├── _py.py ├── __init__.py ├── cutils.pyx ├── cfmt.pyx ├── _cutils.pxd ├── _sandbox.py ├── _parse.py ├── utils.py ├── __main__.py ├── _pre.py └── cdoc.pyx ├── MANIFEST.in ├── .gitignore ├── ROADMAP ├── MIT-LICENSE ├── setup.pypi.py ├── setup.py ├── highlights └── dmsl.vim └── README.rst /dmsl/test/templates/py_raise.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dmsl/test/templates/footer.dmsl: -------------------------------------------------------------------------------- 1 | #footer -------------------------------------------------------------------------------- /dmsl/test/templates/header.dmsl: -------------------------------------------------------------------------------- 1 | #header %h1 {title} 2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_raise.dmsl: -------------------------------------------------------------------------------- 1 | raise Exception('Testing raise Exception("...")') 2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_newline_var.html: -------------------------------------------------------------------------------- 1 |
Daniel
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_newline_var.dmsl: -------------------------------------------------------------------------------- 1 | name = 'Daniel' 2 | %html %body 3 | %div \ 4 | {name} -------------------------------------------------------------------------------- /dmsl/test/templates/py_if_nested.html: -------------------------------------------------------------------------------- 1 |

a

b

c

2 | 3 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_tabs.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %head 3 | %title Hello Tabs! 4 | %body 5 | %h1 Tabs and Spam! 6 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_ending_colon.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %ul for x in range(5): 3 | %li {x!s} is the number: 4 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_extends.dmsl: -------------------------------------------------------------------------------- 1 | extends('py_block_default.dmsl') 2 | 3 | #header[super=] 4 | %h1 OVERRIDE 5 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_embed.html: -------------------------------------------------------------------------------- 1 |

A message. Hello, Joe. How are you?

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_ending_colon.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | %label[for="someid"] Url: 4 | %p Hello 5 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_func.html: -------------------------------------------------------------------------------- 1 | From the void, this is hello_world 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MIT-LICENSE 2 | include ROADMAP 3 | recursive-include dmsl *.html *.dmsl *.c 4 | recursive-exclude dmsl *.pyx 5 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_ending_colon.html: -------------------------------------------------------------------------------- 1 |

Hello

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_tabs.html: -------------------------------------------------------------------------------- 1 | Hello Tabs!

Tabs and Spam!

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_ifordering.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_format_dict.dmsl: -------------------------------------------------------------------------------- 1 | test = lambda x: [0,1,2,3,4,5] 2 | 3 | %html %body 4 | for x in test({'a': 'a'}): 5 | %p {x} 6 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_ifelse.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_embed.dmsl: -------------------------------------------------------------------------------- 1 | greet = lambda x: 'Hello, {0}'.format(x) 2 | 3 | %html 4 | %body 5 | %p A message. :greet('Joe'). How are you? -------------------------------------------------------------------------------- /dmsl/test/templates/py_func.dmsl: -------------------------------------------------------------------------------- 1 | def test(a): 2 | return 'this is {0}'.format(a) 3 | 4 | 5 | %html 6 | %body 7 | %span From the void, :test('hello_world') 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .externalToolBuilders/ 2 | .project 3 | .pydevproject 4 | .settings/ 5 | dmsl/bench/* 6 | *.pyc 7 | *.pyo 8 | *.pyd 9 | *.so 10 | *.c 11 | *.swp 12 | MANIFEST 13 | dist/* 14 | build/* 15 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_block_default.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | #header This is some header content 4 | %div this is some middle content 5 | #footer.bottom this is some footer content -------------------------------------------------------------------------------- /dmsl/test/templates/py_ending_colon.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_ifordering.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | %ul 4 | if True: 5 | %li One 6 | %li Two 7 | %li Three 8 | %li Four -------------------------------------------------------------------------------- /dmsl/test/templates/basic_html.html: -------------------------------------------------------------------------------- 1 |

Basic HTML Test

Hello World

Anchor Element

One
Two
Three
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_extends.html: -------------------------------------------------------------------------------- 1 |
this is some middle content
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_formatter.html: -------------------------------------------------------------------------------- 1 |

Translucent <strong> Bulbs Who says, where

this <div> rocks Who says, how

this

rocks

Who says, what?

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex3.html: -------------------------------------------------------------------------------- 1 |

It Starts

Hello 0Hi 2Hi 3Hello 1Hi 2Hi 3
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_ifelse.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | .main 4 | 5 | if False: 6 | #true 7 | else: 8 | #false 9 | 10 | .footer -------------------------------------------------------------------------------- /dmsl/test/templates/py_block_default.html: -------------------------------------------------------------------------------- 1 |
this is some middle content
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_include.html: -------------------------------------------------------------------------------- 1 | Hello World!

Site content here...

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_comments.dmsl: -------------------------------------------------------------------------------- 1 | ''' 2 | this is a comment 3 | %with some stuff 4 | if in list 5 | ''' 6 | 7 | %html 8 | %body 9 | """ 10 | %p this should be hidden 11 | """ 12 | %p this should show up -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex3.dmsl: -------------------------------------------------------------------------------- 1 | %html %body 2 | #content 3 | if True: 4 | %h2 It Starts 5 | for x in range(2): 6 | %span Hello {x} 7 | for y in range(2, 4): 8 | %span Hi {y} -------------------------------------------------------------------------------- /dmsl/test/templates/basic_multilinetext.html: -------------------------------------------------------------------------------- 1 |

This is a test of multiline text that includes head text with somemiddle content that also has multiline text and some tail text for the element.

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_mixed_content.dmsl: -------------------------------------------------------------------------------- 1 | subs = ['www', 'js', 'gitview', 'vizi', 'music'] 2 | 3 | %html 4 | .portal 5 | %ul for x in subs: 6 | %li %a[href=http://{x}.dasa.cc] {x} 7 | 8 | if 't' == 't': 9 | %p HELLO 10 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_indent.html: -------------------------------------------------------------------------------- 1 |

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_include.dmsl: -------------------------------------------------------------------------------- 1 | title = 'Hello World!' 2 | 3 | %html 4 | %head 5 | %title {title} 6 | %body 7 | include('header.dmsl') 8 | 9 | #content 10 | %p Site content here... 11 | 12 | include('footer.dmsl') 13 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_variable_indent.html: -------------------------------------------------------------------------------- 1 |

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_html.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | %h1 Basic HTML Test 4 | %p %a Hello World 5 | 6 | %div %span 7 | %p %a Anchor Element 8 | 9 | %div One 10 | %div Two 11 | %div Three 12 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_formatter.dmsl: -------------------------------------------------------------------------------- 1 | item = 'Translucent Bulbs' 2 | tag = 'this
rocks' 3 | say = lambda x: 'Who says, {0}'.format(x) 4 | 5 | %html 6 | %body 7 | %p {item} :say('where') 8 | %p {tag!r} :say('how') 9 | %p {tag!s} 10 | %p :say('what?') 11 | -------------------------------------------------------------------------------- /dmsl/test/__main__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | import sys 4 | sys.path.append(os.getcwd()) 5 | 6 | import unittest 7 | from test_basic import * 8 | from test_py import * 9 | #import dmsl 10 | 11 | os.chdir('test/templates') 12 | #dmsl.template_dir = 'test/templates' 13 | unittest.main() 14 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_looping.html: -------------------------------------------------------------------------------- 1 |

Loop Method 1 (not really a loop.. python builtin map)

  • a
  • b
  • c
  • d

Loop Method 2 (python list comprehension)

  • a
  • b
  • c
  • d
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex1.html: -------------------------------------------------------------------------------- 1 |

Test of complex statment mixes.

Hello World

Blank paragraph

  • 0
  • 1
  • 2
  • 3
  • Hello World 4 extra 4

ending paragraph

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_mixed_content.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /ROADMAP: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | This is a work in progress. Here are some TODOs that are on my mind. 5 | 6 | dmsl 0.x series 7 | --------------- 8 | 9 | * Work out various speed enhancements 10 | * Tackle security issues 11 | * Try to uncover any and all issues related to characters in source document 12 | that will cause the parser to error 13 | 14 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_multilinetext.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | %p This is a test of 4 | \multiline text that includes 5 | \head text with some 6 | 7 | %span middle content that also has 8 | \multiline text 9 | 10 | \and some tail text for the element. 11 | 12 | #footer -------------------------------------------------------------------------------- /dmsl/test/templates/py_if_nested.dmsl: -------------------------------------------------------------------------------- 1 | %html %body 2 | if True: 3 | %p a 4 | if True: 5 | %p b 6 | if False: 7 | %p b 8 | elif True: 9 | %p c 10 | elif False: 11 | %p b 12 | else: 13 | %p c 14 | elif False: 15 | %p b 16 | else: 17 | %p c 18 | -------------------------------------------------------------------------------- /dmsl/test/templates/template.html: -------------------------------------------------------------------------------- 1 | Test

Hello, Daniel

Hello, me

Hello, world

Loop

  • a D has juju and more
  • b D has juju and more
  • c D has juju and more
  • d D has juju and more
  • e D has juju and more
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex2.html: -------------------------------------------------------------------------------- 1 |

Test of complex statment mixes.

Hello World

Blank paragraph

My Title

some content here

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_variable_indent.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %head 3 | %title 4 | %link 5 | %script 6 | %body 7 | %div 8 | %h1 9 | %span 10 | %strong 11 | %p 12 | %a 13 | %em 14 | %img 15 | %div 16 | %img 17 | %em 18 | %a 19 | %strong -------------------------------------------------------------------------------- /dmsl/test/templates/basic_indent.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %head 3 | %title 4 | %link 5 | %script 6 | %body 7 | %div 8 | %h1 9 | %span 10 | %strong 11 | %p 12 | %a 13 | %em 14 | %img 15 | %div 16 | %img 17 | %em 18 | %a 19 | %strong -------------------------------------------------------------------------------- /dmsl/_py.py: -------------------------------------------------------------------------------- 1 | #from _parse_pre import _parse_pre 2 | from _pre import _pre 3 | 4 | def _compile(py_queue, fn, kwargs): 5 | py_str = '\n '.join(py_queue) 6 | if py_str == '': 7 | return None 8 | arg_list = ','.join([key for key in kwargs.keys()]) 9 | py_str = 'def _py('+arg_list+'):\n __py_parse__, __blocks__ = {}, {}\n '+py_str+'\n return locals()' 10 | return compile(py_str, fn, 'exec'), py_str 11 | 12 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_inline.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_nested_for.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %head 3 | %title Nested For-Loops 4 | 5 | %body 6 | table = [dict(a=1, b=2, c=3, d=4) for x in range(4)] 7 | 8 | %h1 Nested For-Loops 9 | 10 | %table 11 | for row in table: 12 | %tr 13 | [' %td {0}'.format(x) for x in row.values()] 14 | 15 | for x in range(5): 16 | %p {x} 17 | for y in range(3): 18 | %a {y} -------------------------------------------------------------------------------- /dmsl/test/templates/py_nested_for.html: -------------------------------------------------------------------------------- 1 | Nested For-Loops

Nested For-Loops

1324
1324
1324
1324

0

012

1

012

2

012

3

012

4

012 2 | -------------------------------------------------------------------------------- /dmsl/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | import dmsl 4 | dmsl.set_template_dir('my/templates') 5 | dmsl.parse('index.dmsl', {'content': 'Hello World!'}) 6 | """ 7 | __version__ = '3' 8 | from _parse import Template 9 | import _sandbox 10 | from _sandbox import extensions 11 | 12 | def set_template_dir(value): 13 | _sandbox._open.template_dir = value 14 | 15 | def set_debug(b): 16 | Template.debug = b 17 | 18 | def parse(t, **kwargs): 19 | return Template(t).render(**kwargs) 20 | 21 | 22 | -------------------------------------------------------------------------------- /dmsl/test/templates/basic_tag_hashes.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | #header.top[cookies=yes] 4 | %tag#id.class.class[more=less][some=little] 5 | %tag.class#id.class[always=never][no=yes] 6 | %tag.class.class#id 7 | #id.class.class 8 | #id 9 | .class[who=what][where=when] 10 | %h1.big#good[attr1=val1][attr2=val2] 11 | %p.class1.class2#id1.class3 12 | .class1#id2.class2[attr1=val1][attr2=val2] 13 | #places[attr=val1][attr2=val2][attr3=val3] 14 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_looping.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | items = ['a', 'b', 'c', 'd'] 4 | 5 | is_last = lambda i: len(items) is i+1 and '[class=last]' or '' 6 | 7 | %h2 Loop Method 1 (not really a loop.. python builtin map) 8 | 9 | li = lambda x: '%li {0}'.format(x) 10 | %ul 11 | map(li, items) 12 | 13 | %h2 Loop Method 2 (python list comprehension) 14 | %ul 15 | ['%li{0} {1}'.format(is_last(i), x) for i, x in enumerate(items)] 16 | 17 | #footer -------------------------------------------------------------------------------- /dmsl/test/templates/basic_inline.dmsl: -------------------------------------------------------------------------------- 1 | %html 2 | %body 3 | #filler #side1 #box1 4 | #wrapper #container 5 | #top 6 | %h2 %a[href=/] Hello World 7 | %ul.company_info 8 | %li %a[href=/] Home 9 | %li %a[href=/contact] Contact 10 | %li %a[href=/about] About Us 11 | %li %a[href=/more] More Info 12 | 13 | %ul for x in ['a', 'b', 'c', 'd']: 14 | %li {x} 15 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex.dmsl: -------------------------------------------------------------------------------- 1 | s = "Hello World" 2 | numbers = range(10) 3 | 4 | %html %body 5 | #content 6 | %h1 Test of complex statment mixes. 7 | %div 8 | if "Hello" in s: 9 | %h2 {s} 10 | 11 | %p Blank paragraph 12 | 13 | if "World" in s: 14 | .box 15 | %h3 My Title 16 | .text %ul 17 | for n in numbers: 18 | extra = "extra " + str(n) 19 | %li {extra} 20 | #blank -------------------------------------------------------------------------------- /dmsl/test/templates/basic_tag_hashes.html: -------------------------------------------------------------------------------- 1 |

2 | -------------------------------------------------------------------------------- /dmsl/test/templates/evolve2.dmsl: -------------------------------------------------------------------------------- 1 | heading = 'Basic HTML Test' 2 | greet = lambda x: 'Hello, {0}'.format(x) 3 | %html 4 | %head 5 | :css /css/lib/ 6 | main.css 7 | two.css 8 | three.css 9 | %body 10 | %h1 {heading} 11 | %p %a Hello World 12 | 13 | %div %span 14 | %p %a Anchor Element 15 | 16 | %div One 17 | %div Two 18 | %div Three 19 | 20 | if True is True: 21 | %div Hello 22 | %span World 23 | 24 | %div Test 25 | #blank -------------------------------------------------------------------------------- /dmsl/test/templates/template.dmsl: -------------------------------------------------------------------------------- 1 | title = 'Test' 2 | user = 'Daniel' 3 | items = ['a', 'b', 'c', 'd', 'e'] 4 | 5 | %html 6 | %head 7 | %title {title} 8 | %body 9 | include('header.daml') 10 | 11 | greet = lambda x: "%p Hello, {0}".format(x) 12 | greet(user) 13 | greet('me') 14 | greet('world') 15 | 16 | juju = lambda x: "{0} has juju".format(x) 17 | 18 | is_last = lambda x: (len(items) == x+1) and '[class=last]' or '' 19 | 20 | %h2 Loop 21 | %ul 22 | for i, item in enumerate(items): 23 | %li:is_last(i) {item} :juju('D') and more 24 | 25 | include('footer.daml') -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex2.dmsl: -------------------------------------------------------------------------------- 1 | s = "Hello World" 2 | numbers = range(3) 3 | 4 | %html %body 5 | #content 6 | %h1 Test of complex statment mixes. 7 | %div 8 | if "Hello" in s: 9 | %h2 {s} 10 | 11 | %p Blank paragraph 12 | if True: 13 | .box 14 | %h3 My Title 15 | .text %ul 16 | for n in numbers: 17 | a = 'a' 18 | %li {n!s} 19 | %a test 20 | %li Ending 21 | #blank 22 | #blank2 23 | %p some content here 24 | -------------------------------------------------------------------------------- /dmsl/test/templates/py_complex1.dmsl: -------------------------------------------------------------------------------- 1 | s = "Hello World" 2 | numbers = range(5) 3 | 4 | %html %body 5 | #content 6 | %h1 Test of complex statment mixes. 7 | %div 8 | if "Hello" in s: 9 | %h2 {s} 10 | 11 | %p Blank paragraph 12 | 13 | #wrapper if "World" in s: 14 | if True: 15 | %ul for n in numbers: 16 | extra = "extra " + str(n) 17 | if n == 4: 18 | sx = s + " " + str(n) 19 | %li {sx} {extra} 20 | else: 21 | %li {n!s} 22 | #blank 23 | 24 | %p ending paragraph 25 | -------------------------------------------------------------------------------- /dmsl/test/templates/evolve.dmsl: -------------------------------------------------------------------------------- 1 | heading = 'Basic HTML Test' 2 | 3 | if False is False: 4 | tmp = None 5 | 6 | greet = lambda x: 'Hello, {0}'.format(x) 7 | 8 | %html 9 | %head 10 | :css /css/lib/ 11 | main.css 12 | two.css 13 | three.css 14 | %body 15 | %h1 {heading} 16 | :block content 17 | #content Hello {heading} 18 | %p %a Hello World 19 | 20 | %div %span 21 | %p %a Anchor Element 22 | 23 | %div One 24 | %div Two 25 | %div Three 26 | 27 | %ul for x in ['a', 'b', 'c']: 28 | %li {x} :greet('me') 29 | 30 | for x in range(4): 31 | for y in range(2): 32 | %p {x}, {y} 33 | %a My link 34 | if 'a' == 'a': 35 | %i a 36 | %p Woah 37 | 38 | if True is True: 39 | %div Hello 40 | %span World 41 | 42 | %div Test 43 | #blank 44 | :block content 45 | #c OVERRIDE {heading} -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010, Daniel Skinner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /setup.pypi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from distutils.core import setup 3 | from distutils.extension import Extension 4 | 5 | setup( 6 | name='dmsl', 7 | version='0.4.1', 8 | author='Daniel Skinner', 9 | author_email='daniel@dasa.cc', 10 | url='http://dmsl.dasa.cc', 11 | license = "MIT License", 12 | packages = ["dmsl"], 13 | description='Markup Language featuring html outlining via css-selectors, embedded python, and extensibility.', 14 | long_description = """\ 15 | Features CSS selectors and indention for declaring page layout. Embed 16 | python in your documents such as functions, lambda's, variable declarations, 17 | for loops, list comprehensions, etc, writing it just as you would normally. 18 | 19 | Follow development at http://github.com/dasacc22/dmsl 20 | """, 21 | classifiers = [ 22 | "Development Status :: 4 - Beta", 23 | "Intended Audience :: Developers", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | "Programming Language :: Python", 27 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 28 | "Environment :: Web Environment", 29 | ], 30 | ext_modules = [ Extension("dmsl.cdoc", ["dmsl/cdoc.c"]), 31 | Extension("dmsl.cutils", ["dmsl/cutils.c"]), 32 | Extension("dmsl.cfmt", ["dmsl/cfmt.c"])] 33 | ) 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from distutils.core import setup 3 | from distutils.extension import Extension 4 | from Cython.Distutils import build_ext 5 | 6 | setup( 7 | name='dmsl', 8 | version='0.4', 9 | author='Daniel Skinner', 10 | author_email='daniel@dasa.cc', 11 | url='http://dmsl.dasa.cc', 12 | license = "MIT License", 13 | packages = ["dmsl"], 14 | requires = ["cython"], 15 | description='da Markup Language featuring html outlining via css-selectors, embedded python, and extensibility.', 16 | long_description = """\ 17 | Features CSS selectors and indention for declaring page layout. Embed 18 | python in your documents such as functions, lambda's, variable declarations, 19 | for loops, list comprehensions, etc, writing it just as you would normally. 20 | 21 | Follow development at http://github.com/dasacc22/dmsl 22 | """, 23 | classifiers = [ 24 | "Development Status :: 3 - Alpha", 25 | "Intended Audience :: Developers", 26 | "License :: OSI Approved :: MIT License", 27 | "Operating System :: OS Independent", 28 | "Programming Language :: Python", 29 | "Topic :: Internet :: WWW/HTTP :: Dynamic Content", 30 | "Environment :: Web Environment", 31 | ], 32 | cmdclass = {'build_ext': build_ext}, 33 | ext_modules = [ Extension("dmsl.cdoc", ["dmsl/cdoc.pyx"]), 34 | Extension("dmsl.cutils", ["dmsl/cutils.pyx"]), 35 | Extension("dmsl.cfmt", ["dmsl/cfmt.pyx"])] 36 | ) 37 | -------------------------------------------------------------------------------- /dmsl/cutils.pyx: -------------------------------------------------------------------------------- 1 | from _cutils cimport _parse_attr, _parse_ws, _split_space 2 | 3 | def parse_attr(unicode s): 4 | return _parse_attr(s) 5 | 6 | def parse_ws(unicode s): 7 | return _parse_ws(s) 8 | 9 | def split_space(unicode s): 10 | return _split_space(s) 11 | 12 | def sub_str(unicode a, unicode b): 13 | cdef Py_ssize_t i = len(b) 14 | if i == 0: 15 | return a 16 | return a[:-i] 17 | 18 | def sub_strs(*args): 19 | r = args[0] 20 | for x in range(1, len(args)): 21 | i = len(args[x]) 22 | if i == 0: 23 | continue 24 | r = r[:-i] 25 | return r 26 | 27 | def parse_inline(unicode s, int i): 28 | cdef Py_ssize_t a, b, c 29 | 30 | if u':' in s: 31 | a = s.index(':', i) 32 | else: 33 | return u'' 34 | if u'(' in s: 35 | b = s.index(u'(') 36 | else: 37 | return u'' 38 | if u' ' in s[a:b] or a > b: # check a>b for attributes that have : 39 | try: 40 | a = s.index(u':', a+1) 41 | parse_inline(s, a) 42 | except ValueError: 43 | return u'' 44 | 45 | c = s.index(u')')+1 46 | return s[a+1:c] 47 | 48 | def var_assign(unicode s): 49 | ''' 50 | Tests a python string to determine if it is a variable assignment 51 | a = 1 # returns True 52 | map(a, b) # returns False 53 | ''' 54 | cdef Py_ssize_t a, b 55 | 56 | a = s.find('(') 57 | b = s.find('=') 58 | if b != -1 and (b < a or a == -1): 59 | return True 60 | return False 61 | 62 | -------------------------------------------------------------------------------- /highlights/dmsl.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: Damsel 3 | " Maintainer: Daniel Skinner 4 | " Latest Revision: 16 May 2011 5 | " 6 | " To install, add the following two lines to ~/.vimrc 7 | " au BufRead,BufNewFile *.dmsl set filetype=dmsl 8 | " au! Syntax dmsl source /path/to/dmsl.vim 9 | 10 | if exists("b:current_syntax") 11 | finish 12 | endif 13 | 14 | syn match dmslDirective /[%#.][a-zA-Z0-9\-_]*/he=e-1 contains=dmslTag 15 | syn match dmslTag contained "[a-zA-Z0-9]" 16 | 17 | syn match dmslFormat "{.*}" 18 | 19 | syn match dmslAttr "\[.*\]" contains=dmslAttrKey,dmslAttrValue,dmslFormat 20 | syn match dmslAttrKey contained /[a-zA-Z0-9]*=/he=e-1,me=e-1 21 | syn match dmslAttrValue contained /=[a-zA-Z0-9\./"\ \:\-\;\,]*/hs=s+1 22 | 23 | syn match dmslPython /^\ *[:a-z].*$/ contains=dmslPythonStatement,dmslPythonRepeat,dmslPythonConditional,dmslPythonException,dmslPythonOperator,dmslPythonString 24 | syn keyword dmslPythonStatement contained break continue del return as pass raise global assert lambda yield with nonlocal False None True 25 | syn keyword dmslPythonRepeat contained for while 26 | syn keyword dmslPythonConditional contained if elif else 27 | syn keyword dmslPythonException contained try except finally 28 | syn keyword dmslPythonOperator contained and is in not or 29 | syn match dmslPythonString contained /\'.*\'/ 30 | 31 | hi def link dmslAttr Identifier 32 | hi def link dmslAttrKey Type 33 | hi def link dmslAttrValue String 34 | hi def link dmslDirective Special 35 | hi def link dmslTag Label 36 | " hi def link dmslPython Macro 37 | hi def link dmslFormat Macro 38 | hi dmslPython ctermbg=17 39 | "hi dmslFormat ctermfg=79 40 | hi dmslPythonStatement ctermfg=39 ctermbg=17 41 | hi dmslPythonRepeat ctermfg=39 ctermbg=17 42 | hi dmslPythonConditional ctermfg=39 ctermbg=17 43 | hi dmslPythonException ctermfg=39 ctermbg=17 44 | hi dmslPythonOperator ctermfg=39 ctermbg=17 45 | hi dmslPythonString ctermfg=51 ctermbg=17 46 | -------------------------------------------------------------------------------- /dmsl/test/test_basic.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os.path 3 | import unittest 4 | from _parse import Template 5 | import codecs 6 | 7 | class TestBasic(unittest.TestCase): 8 | def setUp(self): 9 | self.t = { 10 | 'basic_ending_colon': None, 11 | 'basic_html': None, 12 | 'basic_indent': None, 13 | 'basic_inline': None, 14 | 'basic_multilinetext': None, 15 | 'basic_tabs': None, 16 | 'basic_tag_hashes': None, 17 | 'basic_variable_indent': None 18 | } 19 | 20 | for k, v in self.t.items(): 21 | # template file 22 | a = k+'.dmsl' 23 | # expected output 24 | b = open(os.path.join('', k+'.html')).read() 25 | self.t[k] = (a, b) 26 | 27 | def test_basic_ending_colon(self): 28 | parsed, expected = self.t['basic_ending_colon'] 29 | parsed = Template(parsed).render() 30 | self.assertEqual(parsed.strip(), expected.strip()) 31 | 32 | def test_basic_html(self): 33 | parsed, expected = self.t['basic_html'] 34 | parsed = Template(parsed).render() 35 | self.assertEqual(parsed.strip(), expected.strip()) 36 | 37 | def test_basic_indent(self): 38 | parsed, expected = self.t['basic_indent'] 39 | parsed = Template(parsed).render() 40 | self.assertEqual(parsed.strip(), expected.strip()) 41 | 42 | def test_basic_inline(self): 43 | parsed, expected = self.t['basic_inline'] 44 | parsed = Template(parsed).render() 45 | self.assertEqual(parsed.strip(), expected.strip()) 46 | 47 | def test_basic_multilinetext(self): 48 | parsed, expected = self.t['basic_multilinetext'] 49 | parsed = Template(parsed).render() 50 | self.assertEqual(parsed.strip(), expected.strip()) 51 | 52 | def test_basic_tabs(self): 53 | parsed, expected = self.t['basic_tabs'] 54 | parsed = Template(parsed).render() 55 | self.assertEqual(parsed.strip(), expected.strip()) 56 | 57 | def test_basic_tag_hashes(self): 58 | parsed, expected = self.t['basic_tag_hashes'] 59 | parsed = Template(parsed).render() 60 | self.assertEqual(parsed.strip(), expected.strip()) 61 | 62 | def test_basic_variable_indent(self): 63 | parsed, expected = self.t['basic_variable_indent'] 64 | parsed = Template(parsed).render() 65 | self.assertEqual(parsed.strip(), expected.strip()) 66 | 67 | -------------------------------------------------------------------------------- /dmsl/cfmt.pyx: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | cdef inline tuple _parse_format(char* s, tuple args, dict kwargs): 4 | cdef Py_ssize_t i, inc, key_start, key_end, offset, esc 5 | cdef bytes e 6 | cdef bytes result 7 | cdef bytes new_s = s 8 | cdef char c 9 | cdef object obj 10 | 11 | cdef list _args = list(args) 12 | cdef list traverse 13 | cdef list keys = [] 14 | 15 | key_start = -1 16 | key_end = -1 17 | inc = len(args) #increment arg count 18 | offset = 0 19 | 20 | for i, c in enumerate(s): 21 | if c is '{': 22 | if key_start == -1: 23 | key_start = i 24 | else: 25 | key_start = -1 26 | elif key_start != -1: 27 | if c is '}' or c is ':': 28 | key_end = i 29 | elif c is '!': 30 | key_end = i+2 31 | 32 | if key_end != -1: 33 | keys.append(s[key_start+1:key_end]) 34 | new_s = new_s[:key_start+1+offset] + str(inc) + new_s[key_end+offset:] 35 | offset -= key_end-key_start-2 36 | inc += 1 37 | key_start = -1 38 | key_end = -1 39 | 40 | for e in keys: 41 | if e[-2:] == '!s': 42 | esc = False 43 | e = e[:-2] 44 | elif e[-2:] == '!r': 45 | esc = True 46 | e = e[:-2] 47 | else: 48 | esc = True 49 | 50 | if e.isdigit(): 51 | i = int(e) 52 | obj = args[i] 53 | elif '[' in e: 54 | traverse = [x.replace(']', '') for x in e.split('[')] 55 | obj = kwargs[traverse[0]] 56 | for x in traverse[1:]: 57 | obj = obj[x] 58 | elif '.' in e: 59 | traverse = [x for x in e.split('.')] 60 | obj = kwargs[traverse[0]] 61 | for x in traverse[1:]: 62 | obj = getattr(obj, x) 63 | else: 64 | obj = kwargs[e] 65 | 66 | if isinstance(obj, (float, int)): 67 | _args.append(obj) 68 | elif esc: 69 | result = repr(obj).replace('<', '<').replace('>', '>') 70 | if isinstance(obj, str): 71 | result = result[1:-1] 72 | elif isinstance(obj, unicode): 73 | result = result[2:-1] 74 | _args.append(result) 75 | else: 76 | _args.append(obj) 77 | 78 | return (new_s, _args) 79 | 80 | def fmt(__fmt_string__, *args, **kwargs): 81 | __fmt_string__, _args = _parse_format(__fmt_string__, args, kwargs) 82 | return __fmt_string__.format(*_args) 83 | 84 | 85 | -------------------------------------------------------------------------------- /dmsl/_cutils.pxd: -------------------------------------------------------------------------------- 1 | cdef inline tuple _parse_attr(unicode s): 2 | cdef Py_ssize_t i, mark_start, mark_end, key_start, val_start, literal_start 3 | cdef Py_UNICODE c 4 | 5 | mark_start = -1 6 | mark_end = -1 7 | 8 | key_start = -1 9 | val_start = -1 10 | literal_start = -1 11 | 12 | #cdef dict d = {} 13 | cdef list l = [] 14 | 15 | for i, c in enumerate(s): 16 | if key_start != -1: 17 | if val_start != -1: 18 | if i is val_start+1 and (c is u'"' or c is u"'"): 19 | literal_start = i 20 | elif literal_start != -1 and c is s[literal_start]: 21 | #d[s[key_start+1:val_start]] = s[literal_start+1:i] 22 | l.append((s[key_start+1:val_start], s[literal_start+1:i])) 23 | key_start = -1 24 | val_start = -1 25 | literal_start = -1 26 | elif literal_start == -1 and c is u']': 27 | #d[s[key_start+1:val_start]] = s[val_start+1:i] 28 | l.append((s[key_start+1:val_start], s[val_start+1:i])) 29 | key_start = -1 30 | val_start = -1 31 | elif c is u'=': 32 | val_start = i 33 | elif c is u'[': 34 | key_start = i 35 | if mark_start == -1: 36 | mark_start = i 37 | elif c is u' ': 38 | mark_end = i 39 | break 40 | 41 | if mark_start == -1: 42 | return s, l 43 | if mark_end == -1: 44 | return s[:mark_start], l 45 | else: 46 | return s[:mark_start]+s[mark_end:], l 47 | 48 | cdef inline tuple _parse_tag(unicode s): 49 | cdef unicode x 50 | 51 | r = [_split_period(x) for x in _split_pound(s)] 52 | return r[0][0][1:], r[1][0][1:], (r[0][1]+r[1][1]).replace(u'.', u' ')[1:] 53 | 54 | cdef inline tuple _parse_ws(unicode s): 55 | cdef Py_ssize_t i 56 | cdef Py_UNICODE c 57 | 58 | for i, c in enumerate(s): 59 | if c != u' ' and c != u'\t': 60 | return s[:i], s[i:].rstrip() 61 | return u'', s.rstrip() 62 | 63 | cdef inline tuple _split_space(unicode s): 64 | cdef Py_ssize_t i 65 | cdef Py_UNICODE c 66 | 67 | for i, c in enumerate(s): 68 | if c == u' ': 69 | return s[:i], s[i+1:] 70 | return s, u'' 71 | 72 | cdef inline tuple _split_pound(unicode s): 73 | cdef Py_ssize_t i 74 | cdef Py_UNICODE c 75 | 76 | for i, c in enumerate(s): 77 | if c == u'#': 78 | return s[:i], s[i:] 79 | return s, u'' 80 | 81 | cdef inline tuple _split_period(unicode s): 82 | cdef Py_ssize_t i 83 | cdef Py_UNICODE c 84 | 85 | for i, c in enumerate(s): 86 | if c == u'.': 87 | return s[:i], s[i:] 88 | return s, u'' 89 | 90 | -------------------------------------------------------------------------------- /dmsl/_sandbox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | try: 4 | import __builtin__ 5 | except ImportError: 6 | import builtins as __builtin__ #Python 3.0 7 | 8 | from copy import copy 9 | import os.path 10 | from cfmt import fmt 11 | import codecs 12 | 13 | ### Default set of dmsl extensions 14 | def css(s, _locals): 15 | s = s.splitlines() 16 | n = s[0] 17 | s = s[1:] 18 | return [u'%link[rel=stylesheet][href={0}{1}]'.format(n, x) for x in s] 19 | 20 | def js(s, _locals): 21 | s = s.splitlines() 22 | n = s[0] 23 | s = s[1:] 24 | return ['%script[src={0}{1}]'.format(n, x) for x in s] 25 | ### 26 | 27 | def form(s, _locals): 28 | s = s.splitlines() 29 | n = s[0] 30 | d, n = n.split(' ', 1) 31 | s = s[1:] 32 | r = ['%form[action={0}][method=post]'.format(n)] 33 | for e in s: 34 | _type, _id = e.split(' ') 35 | label = _id.replace('_', ' ').replace('number', '#').title() 36 | if _type == 'hidden': 37 | r.append((' %input#{0}[name={0}][type={1}][value="{2!s}"]').format(_id, _type, _locals[d][_id])) 38 | elif _type == 'text': 39 | r.append(' %label[for={0}] {1}'.format(_id, label)) 40 | r.append((' %input#{0}[name={0}][type={1}][value="{2!s}"]').format(_id, _type, _locals[d][_id])) 41 | elif _type == 'submit': 42 | r.append(' %input[type=submit][value={0}]'.format(label)) 43 | return r 44 | 45 | def _open(f): 46 | return codecs.open(os.path.join(_open.template_dir, f), encoding='utf-8', errors='replace') 47 | _open.template_dir = '' 48 | 49 | default_sandbox = { '__builtins__': None, 50 | 'css': css, 51 | 'dict': __builtin__.dict, 52 | 'enumerate': __builtin__.enumerate, 53 | 'Exception': Exception, 54 | 'form': form, 55 | 'float': __builtin__.float, 56 | 'fmt': fmt, 57 | 'globals': __builtin__.globals, 58 | 'int': __builtin__.int, 59 | 'js': js, 60 | 'len': __builtin__.len, 61 | 'list': __builtin__.list, 62 | 'locals': __builtin__.locals, 63 | 'map': __builtin__.map, 64 | 'max': __builtin__.max, 65 | 'min': __builtin__.min, 66 | 'open': _open, 67 | 'range': __builtin__.range, 68 | 'repr': __builtin__.repr, 69 | 'reversed': __builtin__.reversed, 70 | 'set': __builtin__.set, 71 | 'sorted': __builtin__.sorted, 72 | 'str': __builtin__.str} 73 | 74 | # Python3 75 | if hasattr(__builtin__, 'False'): 76 | default_sandbox['False'] = getattr(__builtin__, 'False') 77 | 78 | if hasattr(__builtin__, 'True'): 79 | default_sandbox['True'] = getattr(__builtin__, 'True') 80 | 81 | # Python2 82 | if hasattr(__builtin__, 'unicode'): 83 | default_sandbox['unicode'] = getattr(__builtin__, 'unicode') 84 | 85 | # 86 | 87 | def new(): 88 | return copy(default_sandbox) 89 | 90 | extensions = {} 91 | -------------------------------------------------------------------------------- /dmsl/_parse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from copy import copy, deepcopy 4 | 5 | import _sandbox 6 | from _pre import _pre 7 | from _py import _compile 8 | from cdoc import doc_pre, doc_py 9 | 10 | def func():pass 11 | func = type(func) 12 | 13 | class RenderException(Exception): 14 | def __init__(self, f, py_str, exc_type, exc_value, exc_traceback): 15 | import traceback 16 | tb = traceback.extract_tb(exc_traceback) 17 | self.msg = [''] 18 | py_str = py_str.split('\n') 19 | 20 | for line in tb: 21 | fn = line[0] 22 | ln = line[1] 23 | fnc = line[2] 24 | src = line[3] 25 | try: 26 | src = py_str[ln].strip() 27 | for i, x in enumerate(f): 28 | if src in x: 29 | ln = i 30 | break 31 | except: 32 | pass 33 | self.msg.append(' File {0}, line {1}, in {2}\n {3}'.format(fn, ln, fnc, src)) 34 | self.msg.append(repr(exc_value)) 35 | #self.msg.append('\n--- DMSL PYTHON QUEUE ---') 36 | #self.msg.extend(py_q) 37 | self.msg = '\n'.join(self.msg) 38 | 39 | def __str__(self): 40 | return self.msg 41 | 42 | 43 | class Template(object): 44 | debug = False 45 | 46 | def __init__(self, filename): 47 | self.sandbox = _sandbox.new() 48 | self.sandbox.update(_sandbox.extensions) 49 | 50 | if isinstance(filename, list): 51 | self.f = filename 52 | self.fn = '' 53 | else: 54 | self.f = _sandbox._open(filename).read().splitlines() 55 | self.fn = filename 56 | 57 | self.r, self.py_q = _pre(self.f) 58 | self.r = doc_pre(self.r) 59 | 60 | def render(self, *args, **kwargs): 61 | self.sandbox['args'] = args 62 | self.sandbox['kwargs'] = kwargs 63 | 64 | r = copy(self.r) 65 | 66 | if len(self.py_q) == 0: 67 | return r.tostring() 68 | else: 69 | self.code, self.py_str = _compile(self.py_q, self.fn, kwargs) 70 | self.code = func(self.code.co_consts[0], self.sandbox) 71 | 72 | try: 73 | py_locals = self.code(**kwargs) 74 | except Exception as e: 75 | if isinstance(e, TypeError) or isinstance(e, KeyError): 76 | import sys 77 | if self.debug: 78 | print self.py_str 79 | raise RenderException(self.f, self.py_str, *sys.exc_info()) 80 | else: 81 | raise e 82 | 83 | # Check for empty doc, ussually result of python only code 84 | if r is None: 85 | return '' 86 | 87 | py_id = id(self.py_q) 88 | py_parse = py_locals['__py_parse__'] 89 | doc_py(r, py_id, py_parse) 90 | return r.tostring() 91 | 92 | 93 | if __name__ == '__main__': 94 | import sys 95 | import codecs 96 | from time import time 97 | _f = sys.argv[1] 98 | #print parse(_f) 99 | t = Template(_f) 100 | if '-p' in sys.argv: 101 | def run(): 102 | for x in range(2000): 103 | t.render() 104 | import cProfile, pstats 105 | prof = cProfile.Profile() 106 | prof.run('run()') 107 | stats = pstats.Stats(prof) 108 | stats.strip_dirs() 109 | stats.sort_stats('cumulative') 110 | stats.print_stats(25) 111 | else: 112 | print t.render() 113 | 114 | -------------------------------------------------------------------------------- /dmsl/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | These are python equivalents of the cython parsing extensions, some are hardly 3 | suitable for an actual pure python module of daml. 4 | ''' 5 | 6 | directives = [u'%', u'#', u'.', u'\\'] 7 | 8 | def sub_str(a, b): 9 | i = len(b) 10 | if i == 0: 11 | return a 12 | return a[:-i] 13 | 14 | def parse_ws(s): 15 | for i, c in enumerate(s): 16 | if c != u' ': 17 | return s[:i], s[i:].rstrip() 18 | return u'', s.rstrip() 19 | 20 | def split_space(s): 21 | for i, c in enumerate(s): 22 | if c == u' ': 23 | return s[:i], s[i+1:] 24 | return s, u'' 25 | 26 | def split_pound(s): 27 | for i, c in enumerate(s): 28 | if c == u'#': 29 | return s[:i], s[i:] 30 | return s, u'' 31 | 32 | def split_period(s): 33 | for i, c in enumerate(s): 34 | if c == u'.': 35 | return s[:i], s[i:] 36 | return s, u'' 37 | 38 | def parse_tag(s): 39 | ''' 40 | accepts input such as 41 | %tag.class#id.one.two 42 | and returns 43 | ('tag', 'id', 'class one two') 44 | ''' 45 | r = [split_period(x) for x in split_pound(s)] 46 | return r[0][0][1:], r[1][0][1:], (r[0][1]+r[1][1]).replace(u'.', u' ')[1:] 47 | 48 | def parse_attr(s): 49 | mark_start = None 50 | mark_end = None 51 | 52 | key_start = None 53 | val_start = None 54 | literal_start = None 55 | 56 | d = {} 57 | 58 | for i, c in enumerate(s): 59 | if key_start is not None: 60 | if val_start is not None: 61 | if i == val_start+1 and (c == u'"' or c == u"'"): 62 | literal_start = i 63 | elif literal_start is not None and c == s[literal_start]: 64 | d[s[key_start+1:val_start]] = s[literal_start+1:i] 65 | key_start = None 66 | val_start = None 67 | literal_start = None 68 | elif literal_start is None and c == u']': 69 | d[s[key_start+1:val_start]] = s[val_start+1:i] 70 | key_start = None 71 | val_start = None 72 | elif c == u'=': 73 | val_start = i 74 | elif c == u'[': 75 | key_start = i 76 | if mark_start is None: 77 | mark_start = i 78 | elif c == u' ': 79 | mark_end = i 80 | break 81 | 82 | if mark_start is None: 83 | return s, d 84 | if mark_end is None: 85 | return s[:mark_start], d 86 | else: 87 | return s[:mark_start]+s[mark_end:], d 88 | 89 | def parse_inline(s, i): 90 | if u':' in s: 91 | a = s.index(u':', i) 92 | else: 93 | return u'' 94 | if u'(' in s: 95 | b = s.index(u'(') 96 | else: 97 | return u'' 98 | if u' ' in s[a:b] or a > b: # check a>b for attributes that have : 99 | try: 100 | a = s.index(u':', a+1) 101 | parse_inline(s, a) 102 | except ValueError: 103 | return u'' 104 | 105 | c = s.index(u')')+1 106 | return s[a+1:c] 107 | 108 | def is_assign(s): 109 | ''' 110 | Tests a python string to determine if it is a variable assignment 111 | a = 1 # returns True 112 | map(a, b) # returns False 113 | ''' 114 | a = s.find('(') 115 | b = s.find('=') 116 | if b != -1 and (b < a or a == -1): 117 | return True 118 | else: 119 | return False 120 | 121 | def is_directive(c): 122 | x = u'%' 123 | y = u'#' 124 | z = u'.' 125 | 126 | if c != x and c != y and c != z: 127 | return False 128 | return True 129 | -------------------------------------------------------------------------------- /dmsl/__main__.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ast 3 | import timeit 4 | import os 5 | 6 | from _parse import Template 7 | import _sandbox 8 | 9 | def parse(template, context, _locals, timed, cache, repeat): 10 | # use of extensions to inject _locals 11 | _sandbox.extensions = _locals 12 | try: 13 | if timed and cache: 14 | t = timeit.Timer('tpl.render()', 'from __main__ import Template\ntpl = Template("{0}")\ncontext={1}'.format(template, str(context))) 15 | print '%.2f ms %s' % (1000 * t.timeit(100)/100, template) 16 | elif timed: 17 | t = timeit.Timer('Template("{0}").render()'.format(template), 'from __main__ import Template') 18 | print '%.2f ms %s' % (1000 * t.timeit(repeat)/repeat, template) 19 | else: 20 | t = Template(template) 21 | print(t.render(**context)) 22 | #print '\n'+etree.tostring(etree.fromstring(t.render(**context)), pretty_print=True) 23 | except Exception as e: 24 | print 'Exception rendering ', template 25 | print e 26 | 27 | def parse_templates(templates, context, _locals, timed, cache, repeat): 28 | for template in templates: 29 | path = os.path.join(_sandbox._open.template_dir, template) 30 | 31 | if os.path.isfile(path): 32 | parse(template, context, _locals, timed, cache, repeat) 33 | elif os.path.isdir(path): 34 | files = get_files(path) 35 | for e in files: 36 | # make paths relative to template directory again 37 | e = e.replace(_sandbox._open.template_dir, '', 1) 38 | parse(e, context, _locals, timed, cache, repeat) 39 | 40 | def get_files(directory): 41 | files = [] 42 | for e in os.listdir(directory): 43 | path = os.path.join(directory, e) 44 | if os.path.isfile(path) and os.path.splitext(path)[1] == '.dmsl': 45 | files.append(path) 46 | elif os.path.isdir(path): 47 | files.extend(get_files(path)) 48 | return files 49 | 50 | parser = argparse.ArgumentParser(description='Render dmsl templates.') 51 | parser.add_argument('templates', metavar='F', type=str, nargs='+', help='Location of dmsl template file(s). If given a directory, will traverse and locate dmsl templates.') 52 | parser.add_argument('--kwargs', dest='kwargs', type=ast.literal_eval, nargs=1, default=[{}], help='Specify a dict as a string, i.e. "{\'a\': \'b\'}", thats parsed with ast.literal_eval for use as a \ 53 | template\'s kwargs during parse. This is the same as calling Template(\'test.dmsl\').render(**kwargs)') 54 | parser.add_argument('--locals', dest='_locals', type=ast.literal_eval, nargs=1, default=[{}], help='Specify a dict that will be used to inject locals for use by template (Useful for testing template blocks). \ 55 | See --kwargs for example. If timing parse, keep in mind that these variables already exist in memory and are not instantiated in the template.') 56 | parser.add_argument('--template-dir', dest='template_dir', type=str, nargs=1, default=None, help='If a template directory is given, templates should be specified local to that directory. Useful for testing templates \ 57 | with include and extends.') 58 | parser.add_argument('--timeit', dest='timed', action='store_const', const=True, default=False, help='Time the duration of parse.') 59 | parser.add_argument('--cache', dest='cache', action='store_const', const=True, default=False, help='When timing parse, prerender portions of the template and time final render.') 60 | parser.add_argument('--repeat', dest='repeat', type=int, nargs=1, default=[100], help='When timing parse, specify number of runs to make.') 61 | parser.add_argument('--debug', dest='debug', action='store_const', const=True, default=False, help='Parser step output for debugging module and templates. Negates any other options set (except --template-dir) and only applicable for parsing a single template file.') 62 | 63 | args = parser.parse_args() 64 | 65 | if args.template_dir is not None: 66 | _sandbox._open.template_dir = args.template_dir[0] 67 | 68 | if not args.debug: 69 | parse_templates(args.templates, args.kwargs[0], args._locals[0], args.timed, args.cache, args.repeat[0]) 70 | else: 71 | import pprint 72 | from _pre import _pre 73 | from _py import _compile 74 | 75 | pp = pprint.PrettyPrinter(depth=3) 76 | 77 | fn = args.templates[0] 78 | f = _sandbox._open(fn).read().splitlines() 79 | r, py_q = _pre(f) 80 | print('\n!!! r !!!\n') 81 | pp.pprint(r) 82 | print('\n@@@ py_q @@@\n') 83 | pp.pprint(py_q) 84 | print('\n### py_str ###\n') 85 | code, py_str = _compile(py_q, fn) 86 | print(py_str) 87 | print('\n$$$$$$$$$$\n') 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /dmsl/test/test_py.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os.path 3 | import unittest 4 | #from _parse import c_parse as parse 5 | from _parse import Template 6 | import codecs 7 | 8 | class TestPy(unittest.TestCase): 9 | def setUp(self): 10 | self.t = { 11 | 'py_block_default': None, 12 | 'py_complex1': None, 13 | 'py_complex2': None, 14 | 'py_complex3': None, 15 | 'py_embed': None, 16 | 'py_ending_colon': None, 17 | 'py_extends': None, 18 | 'py_formatter': None, 19 | 'py_func': None, 20 | 'py_ifelse': None, 21 | 'py_ifordering': None, 22 | 'py_if_nested': None, 23 | 'py_include': None, 24 | 'py_looping': None, 25 | 'py_mixed_content': None, 26 | 'py_nested_for': None, 27 | 'py_newline_var': None, 28 | 'py_raise': None 29 | } 30 | 31 | for k, v in self.t.items(): 32 | # template file 33 | a = k+'.dmsl' 34 | # expected output 35 | b = open(os.path.join('', k+'.html')).read() 36 | self.t[k] = (a, b) 37 | 38 | def test_py_block_default(self): 39 | parsed, expected = self.t['py_block_default'] 40 | parsed = Template(parsed).render() 41 | self.assertEqual(parsed.strip(), expected.strip()) 42 | 43 | def test_py_complex1(self): 44 | parsed, expected = self.t['py_complex1'] 45 | parsed = Template(parsed).render() 46 | self.assertEqual(parsed.strip(), expected.strip()) 47 | 48 | def test_py_complex2(self): 49 | parsed, expected = self.t['py_complex2'] 50 | parsed = Template(parsed).render() 51 | self.assertEqual(parsed.strip(), expected.strip()) 52 | 53 | def test_py_complex3(self): 54 | parsed, expected = self.t['py_complex3'] 55 | parsed = Template(parsed).render() 56 | self.assertEqual(parsed.strip(), expected.strip()) 57 | 58 | def test_py_embed(self): 59 | parsed, expected = self.t['py_embed'] 60 | parsed = Template(parsed).render() 61 | self.assertEqual(parsed.strip(), expected.strip()) 62 | 63 | def test_py_ending_colon(self): 64 | parsed, expected = self.t['py_ending_colon'] 65 | parsed = Template(parsed).render() 66 | self.assertEqual(parsed.strip(), expected.strip()) 67 | 68 | def test_py_extends(self): 69 | parsed, expected = self.t['py_extends'] 70 | parsed = Template(parsed).render() 71 | self.assertEqual(parsed.strip(), expected.strip()) 72 | 73 | def test_py_formatter(self): 74 | parsed, expected = self.t['py_formatter'] 75 | parsed = Template(parsed).render() 76 | self.assertEqual(parsed.strip(), expected.strip()) 77 | 78 | def test_py_func(self): 79 | parsed, expected = self.t['py_func'] 80 | parsed = Template(parsed).render() 81 | self.assertEqual(parsed.strip(), expected.strip()) 82 | 83 | def test_py_ifelse(self): 84 | parsed, expected = self.t['py_ifelse'] 85 | parsed = Template(parsed).render() 86 | self.assertEqual(parsed.strip(), expected.strip()) 87 | 88 | def test_py_ifordering(self): 89 | parsed, expected = self.t['py_ifordering'] 90 | parsed = Template(parsed).render() 91 | self.assertEqual(parsed.strip(), expected.strip()) 92 | 93 | def test_py_if_nested(self): 94 | parsed, expected = self.t['py_if_nested'] 95 | parsed = Template(parsed).render() 96 | self.assertEqual(parsed.strip(), expected.strip()) 97 | 98 | def test_py_include(self): 99 | parsed, expected = self.t['py_include'] 100 | parsed = Template(parsed).render() 101 | self.assertEqual(parsed.strip(), expected.strip()) 102 | 103 | def test_py_looping(self): 104 | parsed, expected = self.t['py_looping'] 105 | parsed = Template(parsed).render() 106 | self.assertEqual(parsed.strip(), expected.strip()) 107 | 108 | def test_py_mixed_content(self): 109 | parsed, expected = self.t['py_mixed_content'] 110 | parsed = Template(parsed).render() 111 | self.assertEqual(parsed.strip(), expected.strip()) 112 | 113 | def test_py_nested_for(self): 114 | parsed, expected = self.t['py_nested_for'] 115 | parsed = Template(parsed).render() 116 | self.assertEqual(parsed.strip(), expected.strip()) 117 | 118 | def test_py_newline_var(self): 119 | parsed, expected = self.t['py_newline_var'] 120 | parsed = Template(parsed).render() 121 | self.assertEqual(parsed.strip(), expected.strip()) 122 | 123 | def test_py_raise(self): 124 | parsed, expected = self.t['py_raise'] 125 | #TODO fix this test for 2.6 126 | self.assertRaises(Exception, Template(parsed).render) 127 | #self.assertEquals(str(e.exception), 'Testing raise Exception("...")') 128 | 129 | 130 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | This project is a work in progress. Refer to dmsl/test/templates/ for additional syntax not covered here. 2 | 3 | Follow development at http://github.com/dasacc22/dmsl or view this readme at http://damsel.dasa.cc 4 | 5 | * `Building From Source`_ 6 | * `Running Unit Tests`_ 7 | * `Submitting Bugs`_ 8 | * `Speed Tests`_ 9 | * `Features and Examples`_ 10 | 11 | * `Command Line Interface`_ 12 | * `Django Example`_ 13 | * `Generic Source Example`_ 14 | 15 | * `Language Features`_ 16 | 17 | * `Elements and Attributes`_ 18 | * `Embedding Python`_ 19 | * `Filters`_ 20 | * `Reusable Templates`_ 21 | 22 | Building From Source 23 | ==================== 24 | The latest version of dmsl on pypi is 0.4.1 and has no package dependencies. Python sources are required to compile 25 | C extensions 26 | 27 | :: 28 | 29 | sudo pip install dmsl 30 | 31 | If building from git, dmsl depends on Cython >= 0.15.1 so install as per your distribution, for example 32 | 33 | :: 34 | 35 | sudo easy_install cython 36 | 37 | After satisfying cython dependency, clone the repo from github, build and install globally with the following 38 | 39 | :: 40 | 41 | git clone git://github.com/dasacc22/dmsl.git 42 | cd dmsl 43 | sudo python setup.py install 44 | 45 | or if you wish to not install globally, you can build the C extensions in place 46 | and use from directory 47 | 48 | :: 49 | 50 | python setup.py build_ext -i 51 | 52 | Running Unit Tests 53 | ================== 54 | To run unit tests, build in place and execute test/ directory 55 | 56 | :: 57 | 58 | python setup.py build_ext -i 59 | cd dmsl/ 60 | python test/ 61 | 62 | Submitting Bugs 63 | =============== 64 | Please submit bugs and feature requests to the github issue tracker. 65 | 66 | Speed Tests 67 | =========== 68 | Damsel is fast while keeping the codebase relatively small. Below are results 69 | from the genshi svn benchmark bigtable.py for different template languages. 70 | 71 | This test creates a table with 1000 rows, each containing 10 columns from calling 72 | dict.values() and setting each as the column text. It's a silly test, but let's 73 | look at how this can be handled in dmsl:: 74 | 75 | %table for row in table: 76 | %tr for col in row.values(): 77 | %td {col} 78 | 79 | And the results:: 80 | 81 | Damsel Template 44.45 ms 82 | Mako Template 21.64 ms 83 | Genshi Template 127.03 ms 84 | Django Template 274.57 ms 85 | 86 | Features and Examples 87 | ===================== 88 | 89 | Command Line Interface 90 | ---------------------- 91 | Damsel can run via command line. For python2.7 92 | 93 | python -m dmsl -h # see for more info 94 | 95 | For python2.6 96 | 97 | pip install argparse 98 | python -m dmsl.__main__ -h 99 | 100 | Django Example 101 | -------------- 102 | I'm not a django developer but I did run through a portion of the tutorial to 103 | provide enough informationhere to get started. 104 | 105 | Edit settings.py and add the following:: 106 | 107 | import dmsl 108 | dmsl.set_template_dir('/path/to/dmsl/templates') 109 | 110 | The following two samples are adapted from the polls example from django docs. 111 | Edit views.py and render a response like this:: 112 | 113 | from polls.models import Poll 114 | from django.http import HttpResponse 115 | import dmsl 116 | 117 | def index(request): 118 | latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] 119 | context = {'latest_poll_list': latest_poll_list} 120 | 121 | # there's two ways to render the template depending on if you're implement a caching mechanism 122 | # dmsl.Template returns an object you can store in memory for rendering later 123 | # Note the ** to expand the dict as keyword arguments. 124 | response = dmsl.Template('index.dmsl').render(**context) 125 | # optionally you could simply do the following which simply calls the above 126 | # response = dmsl.parse('index.dmsl', **context) 127 | return HttpResponse(response) 128 | 129 | The following is the contents of index.dmsl:: 130 | 131 | %html %body 132 | if not latest_poll_list: 133 | %p No polls are available 134 | 135 | %ul for poll in latest_poll_list: 136 | %li %a[href="/polls/{poll.id}/"] {poll.question} 137 | 138 | Errors in a template will throw a RenderException. Insepct the "Exception Value:" on the django error page for the dmsl file 139 | and line number listed next to it. 140 | 141 | That should be enough to get a savvy django developer started. I'll get a more complete example done in the future. 142 | 143 | Generic Source Example 144 | ---------------------- 145 | To use in source:: 146 | 147 | import dmsl 148 | dmsl.set_template_dir('./templates') 149 | dmsl.Template('index.dmsl').render(**{'content': 'Hello World'}) 150 | 151 | Language Features 152 | ================= 153 | 154 | Elements and Attributes 155 | ----------------------- 156 | Damsel features html outlining similar to css selectors. The most notable difference is using a percent (%) to specify a regular tag:: 157 | 158 | %html 159 | %body Hello World 160 | 161 | Damsel is indention based, but works just fine with variable indention with a minimum of two spaces and as long as blocks align as intended:: 162 | 163 | %html 164 | %body 165 | %p This works just fine 166 | 167 | Tags can also be inlined if they are only wrappers:: 168 | 169 | %html %body %ul 170 | %li %span Home 171 | %li %span Page 172 | 173 | Classes and IDs can be specified the same as CSS. If no tag is specified, a DIV is created by default:: 174 | 175 | %html %body 176 | #top %h1.title Hello World 177 | #content %p.text 178 | 179 | Attributes are specified as in CSS. Breaking attributes across multiple lines is not yet implemented:: 180 | 181 | %html %body 182 | %img[border=0][style="margin: 20px;"] 183 | %a#home.link[href="http://www.dasa.cc"] 184 | 185 | Embedding Python 186 | ---------------- 187 | Damsel also supports embedding python in the document. There's no special syntax for use aside from embedding a function call inline of a tag, starting the call with a colon (:). HTML outlining and python can be intermixed for different effect. Embedding a variable within an outline element is done via the standard python string `Formatter `_:: 188 | 189 | n = 4 190 | greet = lambda x: 'Hello, '+x 191 | %html %body for x in range(n): 192 | y = x*2.5 193 | %p Number is {x}. :greet('Daniel'). Here's the number multiplied and formatted, {y:.2f} 194 | 195 | str.format is also available but is not safe for formatting user input. In cases where you want to call this directly with safety checks, fmt is available in the sandbox:: 196 | 197 | %html %body %ul 198 | [fmt('%li {0}', x) for x in range(10)] 199 | 200 | By default, html sequences are escaped when using the python formatter. You can control this by using the two builtin conversion types, !r and !s. When repr an object, this will escape the item, while the latter leaves it as is:: 201 | 202 | bad = ' hello' 203 | %html %body 204 | %p This output will be escaped, {bad} 205 | %p This is same as above, {bad!r} 206 | %p This output will not be escaped, {bad!s} causing this text to be bold 207 | 208 | Python can be used to control the flow of the document as well:: 209 | 210 | val = False 211 | %html %body 212 | %p Test the value of val 213 | 214 | if val: 215 | %p val is True 216 | else: 217 | %p val is False 218 | 219 | It's important to note how the document becomes aligned. Intermixed outline elements will be left-aligned to their nearest python counterpart. So above, %p val is False will be the resulting object, and will be properly aligned where the if statement is, placing it as a node of body. 220 | 221 | The evaluation of python code takes place in a sandbox that can be extended with custom objects and functions. So for example, in your controller code:: 222 | 223 | import pymongo.objectid 224 | import dmsl 225 | dmsl.extensions['ObjectId'] = pymongo.objectid.ObjectId 226 | 227 | ObjectId will then be available for use in your dmsl templates. 228 | 229 | Filters 230 | ------- 231 | Another extensible feature of dmsl are filters. A filter allows you to write a slightly altered syntax for calling a python function. Take for example the builtin js filter used for specifying multiple javascript files in a particular location:: 232 | 233 | def js(s, _locals): 234 | s = s.splitlines() 235 | n = s[0] 236 | s = s[1:] 237 | return ['%script[src={0}{1}][type="text/javascript"]'.format(n, x) for x in s] 238 | 239 | In a dmsl template, this (as other filters) can be accessed like so:: 240 | 241 | %html %head 242 | :js /js/lib/ 243 | jquery.min.js 244 | jquery.defaultinput.js 245 | utils.js 246 | js.js 247 | 248 | This would be the same as explicitly typing it out:: 249 | 250 | %html %head 251 | %script[src="/js/lib/jquery.min.js"][type="text/javascript"] 252 | %script[src="/js/lib/jquery.defaultinput.js"][type="text/javascript"] 253 | %script[src="/js/lib/utils.js"][type="text/javascript"] 254 | %script[src="/js/lib/js.js"][type="text/javascript"] 255 | 256 | Filters can be used for most anything from a docutils or markdown processor, automatic form generation based on keywords and variables, or to whatever you might imagine. 257 | 258 | Reusable Templates 259 | ------------------ 260 | Being able to create templates are a must and there are two methods implemented in dmsl to do so. The first is the standard include statement. Consider the following file, top.dmsl:: 261 | 262 | #top 263 | %h1 Hello World 264 | %p.desc This is a test. 265 | 266 | This file can then be included into another, for example, overlay.dmsl:: 267 | 268 | %html %body 269 | include('top.dmsl') 270 | #content 271 | %p One 272 | %p Two 273 | 274 | The top.dmsl contents will be aligned appropriately based upon its location in overlay.dmsl. The second method for creating a proper template is the ability to extend a dmsl template. This is handled by a call to the extends function, and then specifying which portion of the template we want to extend. Specifying which portion to extend is based on the ID assigned to a tag. Take the overlay.dmsl example from above. There are two elements we can extend, #top and #content. We can either override the contents, or append new elements to them. Let's do this in index.dmsl:: 275 | 276 | extends('overlay.dmsl') 277 | 278 | #top %h1 This will override all elements in top 279 | #content[super=] 280 | %p three 281 | 282 | Here, we simply specify the the tag hash we want to access and then provide the nested content. If a super attribute is specified, this tells dmsl to append the content to the current element we're extending. This super attribute will **not** be a part of the final output. This method also forces strict conformance to a single ID per element, so if you're use to given multiple nodes the exact same ID, now is a good time to stop. 283 | 284 | More examples coming soon, refer to test/templates for more. 285 | -------------------------------------------------------------------------------- /dmsl/_pre.py: -------------------------------------------------------------------------------- 1 | from cutils import parse_attr, split_space, parse_inline, parse_ws, sub_str, sub_strs, var_assign 2 | from _sandbox import _open 3 | 4 | directives = ['%', '#', '.', '\\'] 5 | py_stmts = ['for', 'if', 'while', 'try', 'except', 'with', 'def'] 6 | 7 | def is_py_stmt(l): 8 | if l[-1] != u':': 9 | return False 10 | for stmt in py_stmts: 11 | if l.startswith(stmt): 12 | return True 13 | return False 14 | 15 | def parse_inlines(s): 16 | if u':' not in s: 17 | return s, '' 18 | 19 | l = [] 20 | inline = parse_inline(s, 0) 21 | i = 0 22 | while inline != u'': 23 | s = s.replace(u':'+inline, u'{'+str(i)+u'}') 24 | l.append(inline) 25 | inline = parse_inline(s, 0) 26 | i += 1 27 | l = u','.join(l) 28 | if l != u'': 29 | l += u',' 30 | return s, l 31 | 32 | def expand_line(ws, l, i, f): 33 | el, attr = parse_attr(l) 34 | tag, txt = split_space(el) 35 | 36 | # Check for inlined tag hashes 37 | if txt != u'' and (txt[0] in directives or is_py_stmt(txt) or (txt[0] == u'[' and txt[-1] == u']')): 38 | l = l.replace(txt, u'') 39 | f[i] = ws+l 40 | f.insert(i+1, ws+u' '+txt) 41 | return l 42 | 43 | txt_cmd = u'__py_parse__["{0}_{1}"] = {2}' 44 | txt_fmt = u'__py_parse__["{0}_{1}"] = fmt(u"""{2}""", {3}**locals())' 45 | 46 | def add_strs(*args): 47 | s = '' 48 | for arg in args: 49 | s += arg 50 | return s 51 | 52 | def _pre(_f): 53 | f = _f[:] 54 | 55 | py_queue = [] 56 | py_id = id(py_queue) 57 | py_count = 0 58 | 59 | mixed_ws = None 60 | mixed_ws_last = None 61 | get_new_mixed_ws = False 62 | 63 | comment = None 64 | 65 | i = 0 66 | 67 | while i < len(f): 68 | ws, l = parse_ws(f[i]) 69 | if not l: 70 | del f[i] 71 | continue 72 | 73 | ### check for comments 74 | if l[:3] in [u'"""', u"'''"]: 75 | if comment is None: 76 | comment = l[:3] 77 | del f[i] 78 | continue 79 | elif l[:3] == comment: 80 | comment = None 81 | del f[i] 82 | continue 83 | 84 | if comment is not None: 85 | del f[i] 86 | continue 87 | ### 88 | 89 | ### maybe something better? 90 | if l[:8] == u'extends(': 91 | del f[i] 92 | _f = _open(l.split("'")[1]).readlines() 93 | for j, x in enumerate(_f): 94 | f.insert(i+j, x) 95 | continue 96 | if l[:8] == u'include(': 97 | del f[i] 98 | _f = _open(l.split("'")[1]).readlines() 99 | for j, x in enumerate(_f): 100 | f.insert(i+j, ws+x) 101 | continue 102 | ### 103 | 104 | # check for continued lines 105 | if l[-1] == u'\\': 106 | while l[-1] == u'\\': 107 | _ws, _l = parse_ws(f.pop(i+1)) 108 | l = l[:-1] + _l 109 | f[i] = ws+l 110 | 111 | if l[0] in directives: 112 | l = expand_line(ws, l, i, f) 113 | elif l[0] == u'[' and l[-1] == u']': # else if list comprehension 114 | py_queue.append(txt_cmd.format(py_id, py_count, l)) 115 | f[i] = ws+u'{{{0}}}'.format(py_count) 116 | py_count += 1 117 | i += 1 118 | continue 119 | # handle raise 120 | elif l[:5] == u'raise': 121 | py_queue.append(l) 122 | del f[i] 123 | i += 1 124 | continue 125 | # else if not a filter or mixed content 126 | elif l[0] != u':' and l[-1] != u':': 127 | if var_assign(l): 128 | py_queue.append(l) 129 | del f[i] 130 | else: 131 | py_queue.append(txt_cmd.format(py_id, py_count, l)) 132 | f[i] = ws+u'{{{0}}}'.format(py_count) 133 | py_count += 1 134 | i += 1 135 | continue 136 | 137 | # inspect for format variables 138 | # and l[:3] is to prevent triggering dicts as formats in for, if, while statements 139 | if u'{' in l and l[:3] not in ['for', 'if ', 'whi']: # and mixed is None: 140 | l, inlines = parse_inlines(l) 141 | py_queue.append(txt_fmt.format(py_id, py_count, l, inlines)) 142 | f[i] = ws+u'{{{0}}}'.format(py_count) 143 | py_count += 1 144 | i += 1 145 | continue 146 | 147 | # handle filter 148 | if l[0] == u':': 149 | func, sep, args = l[1:].partition(u' ') 150 | filter = [func+u'(u"""'+args] 151 | j = i+1 152 | fl_ws = None # first-line whitespace 153 | while j < len(f): 154 | _ws, _l = parse_ws(f[j]) 155 | if _ws <= ws: 156 | break 157 | fl_ws = fl_ws or _ws 158 | del f[j] 159 | filter.append(sub_str(_ws, fl_ws)+_l) 160 | filter.append(u'""", locals())') 161 | 162 | if func == u'block': 163 | f[i] = ws+u'{{block}}{{{0}}}'.format(args) 164 | py_queue.append(txt_cmd.format(py_id, py_count, u'\n'.join(filter))) 165 | else: 166 | f[i] = ws+u'{{{0}}}'.format(py_count) 167 | py_queue.append(txt_cmd.format(py_id, py_count, u'\n'.join(filter))) 168 | py_count += 1 169 | 170 | # handle mixed content 171 | elif is_py_stmt(l): 172 | orig_mixed_ws = ws 173 | mixed_ws = [] 174 | mixed_ws_offset = [] 175 | mixed_ws_last = ws 176 | 177 | content_ws = [] 178 | content_ws_offset = [] 179 | content_ws_last = None 180 | 181 | mixed_content_ws_offset = [] 182 | 183 | get_new_mixed_ws = True 184 | get_new_content_ws = True 185 | level = 1 186 | 187 | content_ws_offset_append = False 188 | 189 | py_queue.append(u'__mixed_content__ = []') 190 | py_queue.append(l) 191 | del f[i] 192 | mixed_closed = False 193 | while i < len(f): 194 | _ws, _l = parse_ws(f[i]) 195 | if not _l: 196 | del f[i] 197 | continue 198 | 199 | if content_ws_last is None: 200 | content_ws_last = _ws 201 | 202 | if _ws <= orig_mixed_ws and _l[:4] not in [u'else', u'elif']: 203 | py_queue.append(u'__py_parse__["{0}_{1}"] = list(__mixed_content__)'.format(py_id, py_count)) 204 | f.insert(i, mixed_ws[0]+u'{{{0}}}'.format(py_count)) 205 | py_count += 1 206 | mixed_closed = True 207 | break 208 | 209 | # check for ws changes here, python statement ws only 210 | if get_new_mixed_ws: 211 | get_new_mixed_ws = False 212 | mixed_ws_offset.append(sub_strs(_ws, mixed_ws_last)) 213 | if content_ws_offset_append: 214 | mixed_content_ws_offset.append(sub_strs(_ws, mixed_ws_last)) 215 | mixed_ws.append(mixed_ws_last) 216 | 217 | 218 | # handles catching n[-1] of if,elif,else and related to align correctly 219 | while mixed_ws and _ws == mixed_ws[-1]: 220 | mixed_ws_offset.pop() 221 | mixed_ws.pop() 222 | 223 | #if _ws == mixed_ws[-1]: 224 | # mixed_ws_offset.pop() 225 | # mixed_ws.pop() 226 | 227 | # handles catching n[1:-1] of if,elif,else and related to align correctly 228 | while mixed_ws and _ws <= mixed_ws[-1]: 229 | mixed_ws_offset.pop() 230 | mixed_ws.pop() 231 | 232 | ''' 233 | if get_new_content_ws: 234 | get_new_content_ws = False 235 | content_ws_offset.append(sub_strs(_ws, content_ws_last)) 236 | content_ws.append(content_ws_last) 237 | ''' 238 | 239 | if _l[0] in directives: 240 | _l = expand_line(_ws, _l, i, f) 241 | _l, inlines = parse_inlines(_l) 242 | 243 | ''' 244 | if _ws > content_ws[-1]: 245 | content_ws_offset.append(sub_strs(_ws, content_ws[-1])) 246 | content_ws.append(_ws) 247 | while _ws < content_ws[-1]: 248 | content_ws_offset.pop() 249 | content_ws.pop() 250 | ''' 251 | #tmp_ws = add_strs(*content_ws_offset+mixed_content_ws_offset) 252 | tmp_ws = sub_strs(_ws, orig_mixed_ws, *mixed_ws_offset) 253 | #print '###', _l, mixed_ws_offset 254 | py_queue.append(add_strs(*mixed_ws_offset)+u'__mixed_content__.append(fmt("""{0}{1}""", {2}**locals()))'.format(tmp_ws, _l, inlines)) 255 | 256 | del f[i] 257 | continue 258 | # is this a list comprehension? 259 | elif _l[0] == '[' and _l[-1] == ']': 260 | py_queue.append(add_strs(*mixed_ws_offset)+u'__mixed_content__.extend({0})'.format(_l)) 261 | del f[i] 262 | else: 263 | if _l[-1] == ':': 264 | mixed_ws_last = _ws 265 | get_new_mixed_ws = True 266 | content_ws_offset_append = True 267 | 268 | py_queue.append(add_strs(*mixed_ws_offset)+_l) 269 | 270 | del f[i] 271 | continue 272 | # maybe this could be cleaner? instead of copy and paste 273 | if not mixed_closed: 274 | py_queue.append(u'__py_parse__["{0}_{1}"] = list(__mixed_content__)'.format(py_id, py_count)) 275 | f.insert(i, mixed_ws[0]+u'{{{0}}}'.format(py_count)) 276 | py_count += 1 277 | # handle standalone embedded function calls 278 | elif ':' in l: 279 | l, inlines = parse_inlines(l) 280 | if inlines != '': 281 | py_queue.append(txt_fmt.format(py_id, py_count, l, inlines)) 282 | f[i] = ws+u'{{{0}}}'.format(py_count) 283 | py_count += 1 284 | i += 1 285 | continue 286 | 287 | i += 1 288 | 289 | return f, py_queue 290 | 291 | 292 | if __name__ == '__main__': 293 | import codecs 294 | import sys 295 | _f = codecs.open(sys.argv[1], 'r', 'utf-8').read().expandtabs(4).splitlines() 296 | f, q = _pre(_f) 297 | print '\n=== F ===' 298 | for x in f: 299 | print `x` 300 | print '\n=== Q ===' 301 | for x in q: 302 | print `x` 303 | 304 | -------------------------------------------------------------------------------- /dmsl/cdoc.pyx: -------------------------------------------------------------------------------- 1 | from _cutils cimport _parse_ws, _parse_attr, _parse_tag, _split_space 2 | from copy import copy 3 | 4 | cdef inline _findall(Element el, unicode srch, list result): 5 | cdef Element child 6 | 7 | for child in el._children: 8 | if child._tag == srch: 9 | result.append(child) 10 | if len(child._children) != 0: 11 | _findall(child, srch, result) 12 | 13 | cdef inline unicode _tostring(Element el): 14 | cdef Element child 15 | cdef unicode s 16 | cdef list keyset = [] 17 | cdef list attribset = [] 18 | 19 | s = u'<' + el._tag 20 | 21 | if el._attrib: 22 | el._attrib.reverse() 23 | for k, v in el._attrib: 24 | if k in keyset: 25 | continue 26 | attribset.append((k, v)) 27 | keyset.append(k) 28 | attribset.reverse() 29 | s += u''.join([u' '+k+u'="'+v+u'"' for k, v in attribset])#.items()]) 30 | 31 | s += u'>' + el._text + u''.join([_tostring(child) for child in el._children]) + u'' 32 | 33 | if el._tail: 34 | s += el._tail 35 | 36 | return s 37 | 38 | cdef inline unicode _post(unicode s): 39 | return u''+s.replace(u'<', u'<').replace(u'>', u'>').replace(u'&', u'&') 40 | 41 | cdef inline Element _copy(Element orig): 42 | cdef Element orig_child, child 43 | 44 | cdef Element el = Element() 45 | el.tag = copy(orig.tag) 46 | el.text = copy(orig.text) 47 | el.tail = copy(orig.tail) 48 | #el.attrib = orig.attrib.copy() 49 | el.attrib = copy(orig.attrib) 50 | for orig_child in orig.children: 51 | child = _copy(orig_child) 52 | child.parent = el 53 | el.children.append(child) 54 | return el 55 | 56 | 57 | cdef class Element: 58 | cdef Element _parent 59 | cdef list _children 60 | cdef unicode _tag 61 | cdef unicode _text 62 | cdef unicode _tail 63 | #cdef dict _attrib 64 | cdef list _attrib 65 | 66 | def __cinit__(self): 67 | #self._attrib = {} 68 | self._attrib = [] 69 | self._children = [] 70 | 71 | def __copy__(self): 72 | return _copy(self) 73 | 74 | cdef findall(self, unicode s): 75 | cdef list result = [] 76 | _findall(self, s, result) 77 | return result 78 | 79 | def tostring(self): 80 | return u'' + _tostring(self) 81 | 82 | property attrib: 83 | def __get__(self): 84 | return self._attrib 85 | 86 | #def __set__(self, dict attrib): 87 | def __set__(self, list attrib): 88 | self._attrib = attrib 89 | 90 | property tag: 91 | def __get__(self): 92 | return self._tag 93 | 94 | def __set__(self, unicode tag): 95 | self._tag = tag 96 | 97 | property text: 98 | def __get__(self): 99 | return self._text 100 | 101 | def __set__(self, unicode text): 102 | self._text = text 103 | 104 | property tail: 105 | def __get__(self): 106 | return self._tail 107 | 108 | def __set__(self, unicode tail): 109 | self._tail = tail 110 | 111 | property parent: 112 | def __get__(self): 113 | return self._parent 114 | 115 | def __set__(self, Element parent): 116 | self._parent = parent 117 | 118 | property children: 119 | def __get__(self): 120 | return self._children 121 | 122 | def __set__(self, list children): 123 | self._children = children 124 | 125 | 126 | cdef Element SubElement(Element parent, unicode tag): 127 | cdef Element el = Element() 128 | el.tag = tag 129 | el.parent = parent 130 | parent.children.append(el) 131 | return el 132 | 133 | 134 | cdef Element SubElementByIndex(Element parent, unicode tag, int index): 135 | cdef Element el = Element() 136 | el.tag = tag 137 | el.parent = parent 138 | parent.children.insert(index, el) 139 | return el 140 | 141 | 142 | cdef _doc_py(Element r, long py_id, dict py_parse): 143 | cdef list py_list = r.findall(u'_py_') 144 | cdef Element e 145 | cdef Element p 146 | cdef unicode t 147 | cdef unicode k 148 | 149 | for e in py_list: 150 | t = e.text[1:-1] 151 | k = u'{0}_{1}'.format(py_id, t) 152 | o = py_parse[k] 153 | if isinstance(o, (list, tuple)): 154 | p = e.parent 155 | index = None 156 | if len(p.children) != 0: 157 | index = p.children.index(e) 158 | p.children.remove(e) 159 | _build_from_parent(p, index, map(unicode, o)) 160 | else: 161 | _build_element(e, unicode(o)) 162 | 163 | def doc_py(r, py_id, py_parse): 164 | _doc_py(r, py_id, py_parse) 165 | 166 | def doc_pre(f): 167 | return _doc_pre(f) 168 | 169 | cdef Element _doc_pre(list f): 170 | cdef Element root, el, e 171 | 172 | root = Element() 173 | root.tag = u'root' 174 | r = {} 175 | _ids = {} 176 | prev = None 177 | plntxt = {} 178 | for line in f: 179 | ws, l = _parse_ws(line) 180 | 181 | ### plntxt queue 182 | if l[0] == u'\\': 183 | if ws in plntxt: 184 | plntxt[ws].append(l[1:]) 185 | else: 186 | plntxt[ws] = [l[1:]] 187 | continue 188 | if plntxt: 189 | for _ws, text in plntxt.items(): 190 | text = ' '+' '.join(text) 191 | el = r[prev] 192 | if _ws > prev: 193 | el.text += text 194 | else: # _ws == prev 195 | el.tail = el.tail and el.tail+text or text 196 | plntxt = {} 197 | ### 198 | 199 | if l[0] == u'{': 200 | if ws == u'': # TODO use cases of no root node will make troublesome 201 | continue 202 | _tag, _id, _class, attr, text = u'_py_', u'', u'', None, l 203 | else: 204 | u, attr = _parse_attr(l) 205 | hash, text = _split_space(u) 206 | _tag, _id, _class = _parse_tag(hash) 207 | 208 | if ws == u'': 209 | if _id in _ids: 210 | e = _ids[_id] 211 | is_super = 0 212 | for i, t in enumerate(attr): 213 | if t[0] == u'super': 214 | is_super = 1 215 | attr.pop(i) 216 | break 217 | if not is_super: 218 | e.children = [] 219 | #for child in e.children: 220 | # e.children.remove(child) 221 | #if attr.pop(u'super', None) is None: 222 | # for child in e.children: 223 | # e.children.remove(child) 224 | else: 225 | e = SubElement(root, _tag or u'div') 226 | elif ws > prev: 227 | e = SubElement(r[prev], _tag or u'div') 228 | elif ws == prev: 229 | e = SubElement(r[prev].parent, _tag or u'div') 230 | elif ws < prev: 231 | e = SubElement(r[ws].parent, _tag or u'div') 232 | 233 | for _ws in r.keys(): 234 | if _ws > ws: 235 | r.pop(_ws) 236 | 237 | e.text = text 238 | if _id: 239 | #e.attrib[u'id'] = _id 240 | e.attrib.append((u'id', _id)) 241 | _ids[_id] = e 242 | if _class: 243 | #e.attrib[u'class'] = _class 244 | e.attrib.append((u'class', _class)) 245 | if attr: 246 | #e.attrib.update(attr) 247 | e.attrib.extend(attr) 248 | 249 | r[ws] = e 250 | prev = ws 251 | 252 | if len(root.children) is 0: 253 | return None 254 | 255 | return root.children[0] 256 | 257 | def _build_element(e, line): 258 | ws, l = _parse_ws(line) 259 | 260 | u, attr = _parse_attr(l) 261 | hash, text = _split_space(u) 262 | _tag, _id, _class = _parse_tag(hash) 263 | 264 | e.tag = _tag or u'div' 265 | e.text = text 266 | if _id: 267 | #e.attrib[u'id'] = _id 268 | e.attrib.append((u'id', _id)) 269 | if _class: 270 | #e.attrib[u'class'] = _class 271 | e.attrib.append((u'class', _class)) 272 | if attr: 273 | #e.attrib.update(attr) 274 | e.attrib.extend(attr) 275 | 276 | def _build_from_parent(p, index, f): 277 | r = {'root': p} 278 | prev = '' 279 | plntxt = {} 280 | 281 | #from time import time 282 | #ws_time = 0 283 | #plntxt_time = 0 284 | #tag_time = 0 285 | #el_time = 0 286 | #attrib_time = 0 287 | 288 | cache = {} 289 | 290 | for line in f: 291 | #st = time() 292 | ws, l = _parse_ws(line) 293 | #ws_time += time()-st 294 | 295 | ### plntxt queue 296 | #st = time() 297 | if l[0] == u'\\': 298 | if ws in plntxt: 299 | plntxt[ws].append(l[1:]) 300 | else: 301 | plntxt[ws] = [l[1:]] 302 | continue 303 | if plntxt: 304 | for _ws, text in plntxt.items(): 305 | text = u' '+u' '.join(text) 306 | el = r[prev] 307 | if _ws > prev: 308 | el.text += text 309 | else: # _ws == prev 310 | el.tail = el.tail and el.tail+text or text 311 | plntxt = {} 312 | #plntxt_time += time()-st 313 | ### 314 | 315 | #st = time() 316 | if cache.has_key(l): 317 | _tag, _id, _class, attr, text = cache[l] 318 | else: 319 | #4.5 320 | u, attr = _parse_attr(l) 321 | #0.8 322 | hash, text = _split_space(u) 323 | #7.7 324 | _tag, _id, _class = _parse_tag(hash) 325 | #13 326 | cache[l] = (_tag, _id, _class, attr, text) 327 | #tag_time += time()-st 328 | 329 | #st = time() 330 | if ws == u'': 331 | _p = None 332 | if index is not None: 333 | e = SubElementByIndex(p, _tag or u'div', index) 334 | index += 1 335 | else: 336 | e = SubElement(p, _tag or u'div') 337 | elif ws > prev: 338 | _p = r[prev] 339 | e = SubElement(_p, _tag or u'div') 340 | elif ws == prev: 341 | _p = r[prev].parent 342 | e = SubElement(_p, _tag or u'div') 343 | elif ws < prev: 344 | _p = r[ws].parent 345 | e = SubElement(_p, _tag or u'div') 346 | 347 | for _ws in r.keys(): 348 | if _ws > ws: 349 | r.pop(_ws) 350 | 351 | #el_time += time()-st 352 | 353 | #st = time() 354 | # fctr 5 355 | e.text = text 356 | if _id: 357 | #e.attrib[u'id'] = _id 358 | e.attrib.append((u'id', _id)) 359 | if _class: 360 | #e.attrib[u'class'] = _class 361 | e.attrib.append((u'class', _class)) 362 | if attr: 363 | #e.attrib.update(attr) 364 | e.attrib.extend(attr) 365 | # fctr 1 366 | r[ws] = e 367 | # fctr 1.5 368 | prev = ws 369 | #attrib_time += time()-st 370 | #print '!!!!!' 371 | #print 'ws_time: %.2f ms' % (ws_time*1000) 372 | #print 'plntxt_time: %.2f ms' % (plntxt_time*1000) 373 | #print 'tag_time: %.2f ms' % (tag_time*1000) 374 | #print 'el_time: %.2f ms' % (el_time*1000) 375 | #print 'attrib_time: %.2f ms' % (attrib_time*1000) 376 | #print '!!!!!' 377 | 378 | --------------------------------------------------------------------------------