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)
Loop Method 2 (python list comprehension)
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 |
TestHello, 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
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-LoopsNested For-Loops
0
0121
0122
0123
0124
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''+el._tag+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 |
--------------------------------------------------------------------------------