redcar-sparkup 1.0 → 1.01

Sign up to get free protection for your applications and to get access to all the features.
Binary file
@@ -0,0 +1,40 @@
1
+ SPARKUP_PY=sparkup
2
+ VERSION=`date '+%Y%m%d'`
3
+ README=README.md
4
+
5
+ .PHONY: all textmate vim textmate-dist vim-dist plugins plugins-pre generic all-dist
6
+ all: plugins
7
+
8
+ plugins-pre:
9
+ mkdir -p distribution
10
+
11
+ plugins: plugins-pre all-dist
12
+
13
+ textmate-dist: textmate
14
+ cd TextMate && zip -9r ../distribution/sparkup-textmate-${VERSION}.zip . && cd ..
15
+
16
+ vim-dist: vim
17
+ cd vim && zip -9r ../distribution/sparkup-vim-${VERSION}.zip . && cd ..
18
+
19
+ generic-dist: generic
20
+ cd generic && zip -9r ../distribution/sparkup-generic-${VERSION}.zip . && cd ..
21
+
22
+ all-dist:
23
+ zip -9r distribution/sparkup-${VERSION}.zip generic vim textmate README.md -x */sparkup-readme.txt
24
+ cp distribution/sparkup-${VERSION}.zip distribution/sparkup-latest.zip
25
+
26
+ generic:
27
+ cat sparkup.py > generic/sparkup
28
+ chmod +x generic/sparkup
29
+ #cp ${README} generic/sparkup-readme.txt
30
+
31
+ textmate:
32
+ mkdir -p TextMate/Sparkup.tmbundle/Support
33
+ cp ${SPARKUP_PY} TextMate/Sparkup.tmbundle/Support/sparkup.py
34
+ #cp ${README} TextMate/sparkup-readme.txt
35
+
36
+ vim:
37
+ mkdir -p vim/ftplugin/html vim/doc
38
+ cp ${SPARKUP_PY} vim/ftplugin/html/sparkup.py
39
+ # Add asteriks to title, so it gets matched by `:helptags`
40
+ sed '1s/.*/*\0*/' ${README} > vim/doc/sparkup.txt
@@ -0,0 +1,130 @@
1
+ Sparkup
2
+ =======
3
+
4
+ **Sparkup lets you write HTML code faster.** Don't believe us?
5
+ [See it in action!](http://www.youtube.com/watch?v=Jw3jipcenKc)
6
+
7
+ You can write HTML in a CSS-like syntax, and have Sparkup handle the expansion to full HTML
8
+ code. It is meant to help you write long HTML blocks in your text editor by letting you
9
+ type less characters than needed.
10
+
11
+ Sparkup is written in Python, and requires Python 2.5 or newer (2.5 is preinstalled in
12
+ Mac OS X Leopard). Sparkup also offers intregration into common text editors. Support for VIM
13
+ and TextMate are currently included.
14
+
15
+ A short screencast is available here:
16
+ [http://www.youtube.com/watch?v=Jw3jipcenKc](http://www.youtube.com/watch?v=Jw3jipcenKc)
17
+
18
+ Usage and installation
19
+ ----------------------
20
+ You may download Sparkup from Github. [Download the latest version here](http://github.com/rstacruz/sparkup/downloads).
21
+
22
+ - **TextMate**: Simply double-click on the `Sparkup.tmbundle` package in Finder. This
23
+ will install it automatically. In TextMate, open an HTML file (orset the document type to
24
+ HTML) type in something (e.g., `#header > h1`), then press `Ctrl` + `E`. Pressing `Tab`
25
+ will cycle through empty elements.
26
+
27
+ - **VIM**: See the `vim/README.txt` file for details.
28
+
29
+ - **Others/command line use**: You may put `sparkup` in your `$PATH` somewhere. You may then
30
+ invoke it by typing `echo "(input here)" | sparkup`, or `sparkup --help` for a list of commands.
31
+
32
+ Credits
33
+ -------
34
+
35
+ Sparkup is written by Rico Sta. Cruz and is released under the MIT license.
36
+
37
+ This project is inspired by [Zen Coding](http://code.google.com/p/zen-coding/) of
38
+ [Vadim Makeev](http://pepelsbey.net). The Zen HTML syntax is forward-compatible with Sparkup
39
+ (anything that Zen HTML can parse, Sparkup can too).
40
+
41
+ The following people have contributed code to the project:
42
+
43
+ - Guillermo O. Freschi (Tordek @ github)
44
+ Bugfixes to the parsing system
45
+
46
+ - Eric Van Dewoestine (ervandew @ github)
47
+ Improvements to the VIM plugin
48
+
49
+ Examples
50
+ --------
51
+
52
+ **`div`** expands to:
53
+ <div></div>
54
+
55
+ **`div#header`** expands to:
56
+ <div id="header"></div>
57
+
58
+ **`div.align-left#header`** expands to:
59
+ <div id="header" class="align-left"></div>
60
+
61
+ **`div#header + div#footer`** expands to:
62
+ <div id="header"></div>
63
+ <div id="footer"></div>
64
+
65
+ **`#menu > ul`** expands to:
66
+ <div id="menu">
67
+ <ul></ul>
68
+ </div>
69
+
70
+ **`#menu > h3 + ul`** expands to:
71
+ <div id="menu">
72
+ <h3></h3>
73
+ <ul></ul>
74
+ </div>
75
+
76
+ **`#header > h1{Welcome to our site}`** expands to:
77
+ <div id="header">
78
+ <h1>Welcome to our site</h1>
79
+ </div>
80
+
81
+ **`a[href=index.html]{Home}`** expands to:
82
+ <a href="index.html">Home</a>
83
+
84
+ **`ul > li*3`** expands to:
85
+ <ul>
86
+ <li></li>
87
+ <li></li>
88
+ <li></li>
89
+ </ul>
90
+
91
+ **`ul > li.item-$*3`** expands to:
92
+ <ul>
93
+ <li class="item-1"></li>
94
+ <li class="item-2"></li>
95
+ <li class="item-3"></li>
96
+ </ul>
97
+
98
+ **`ul > li.item-$*3 > strong`** expands to:
99
+ <ul>
100
+ <li class="item-1"><strong></strong></li>
101
+ <li class="item-2"><strong></strong></li>
102
+ <li class="item-3"><strong></strong></li>
103
+ </ul>
104
+
105
+ **`table > tr*2 > td.name + td*3`** expands to:
106
+ <table>
107
+ <tr>
108
+ <td class="name"></td>
109
+ <td></td>
110
+ <td></td>
111
+ <td></td>
112
+ </tr>
113
+ <tr>
114
+ <td class="name"></td>
115
+ <td></td>
116
+ <td></td>
117
+ <td></td>
118
+ </tr>
119
+ </table>
120
+
121
+ **`#header > ul > li < p{Footer}`** expands to:
122
+ <!-- The < symbol goes back up the parent; i.e., the opposite of >. -->
123
+ <div id="header">
124
+ <ul>
125
+ <li></li>
126
+ </ul>
127
+ <p>Footer</p>
128
+ </div>
129
+
130
+
@@ -0,0 +1,34 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>beforeRunningCommand</key>
6
+ <string>nop</string>
7
+ <key>command</key>
8
+ <string>#!/usr/bin/env python
9
+ import sys; import os; sys.path.append(os.getenv('TM_BUNDLE_SUPPORT')); import sparkup
10
+
11
+ # You may change these options to your liking.
12
+ # Those starting with # are comments (disabled).
13
+ options = {
14
+ 'textmate': True,
15
+ 'no-last-newline': True,
16
+ #'start-guide-format': 'Begin %s',
17
+ #'end-guide-format': 'End %s',
18
+ }
19
+
20
+ sparkup.Router().start(options=options)</string>
21
+ <key>fallbackInput</key>
22
+ <string>line</string>
23
+ <key>input</key>
24
+ <string>selection</string>
25
+ <key>keyEquivalent</key>
26
+ <string>^e</string>
27
+ <key>name</key>
28
+ <string>Sparkup expand</string>
29
+ <key>output</key>
30
+ <string>insertAsSnippet</string>
31
+ <key>uuid</key>
32
+ <string>73A48D2B-D843-42A1-A288-0D1A6380043B</string>
33
+ </dict>
34
+ </plist>
@@ -0,0 +1,1087 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ version = "0.1.3"
4
+
5
+ import os
6
+ import fileinput
7
+ import getopt
8
+ import sys
9
+ import re
10
+
11
+ # ===============================================================================
12
+
13
+ class Dialect:
14
+ shortcuts = {}
15
+ synonyms = {}
16
+ required = {}
17
+ short_tags = ()
18
+
19
+ class HtmlDialect(Dialect):
20
+ shortcuts = {
21
+ 'cc:ie': {
22
+ 'opening_tag': '<!--[if IE]>',
23
+ 'closing_tag': '<![endif]-->'},
24
+ 'cc:ie6': {
25
+ 'opening_tag': '<!--[if lte IE 6]>',
26
+ 'closing_tag': '<![endif]-->'},
27
+ 'cc:ie7': {
28
+ 'opening_tag': '<!--[if lte IE 7]>',
29
+ 'closing_tag': '<![endif]-->'},
30
+ 'cc:noie': {
31
+ 'opening_tag': '<!--[if !IE]><!-->',
32
+ 'closing_tag': '<!--<![endif]-->'},
33
+ 'html:4t': {
34
+ 'expand': True,
35
+ 'opening_tag':
36
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
37
+ '<html lang="en">\n' +
38
+ '<head>\n' +
39
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
40
+ ' ' + '<title></title>\n' +
41
+ '</head>\n' +
42
+ '<body>',
43
+ 'closing_tag':
44
+ '</body>\n' +
45
+ '</html>'},
46
+ 'html:4s': {
47
+ 'expand': True,
48
+ 'opening_tag':
49
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
50
+ '<html lang="en">\n' +
51
+ '<head>\n' +
52
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
53
+ ' ' + '<title></title>\n' +
54
+ '</head>\n' +
55
+ '<body>',
56
+ 'closing_tag':
57
+ '</body>\n' +
58
+ '</html>'},
59
+ 'html:xt': {
60
+ 'expand': True,
61
+ 'opening_tag':
62
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
63
+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
64
+ '<head>\n' +
65
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
66
+ ' ' + '<title></title>\n' +
67
+ '</head>\n' +
68
+ '<body>',
69
+ 'closing_tag':
70
+ '</body>\n' +
71
+ '</html>'},
72
+ 'html:xs': {
73
+ 'expand': True,
74
+ 'opening_tag':
75
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
76
+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
77
+ '<head>\n' +
78
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
79
+ ' ' + '<title></title>\n' +
80
+ '</head>\n' +
81
+ '<body>',
82
+ 'closing_tag':
83
+ '</body>\n' +
84
+ '</html>'},
85
+ 'html:xxs': {
86
+ 'expand': True,
87
+ 'opening_tag':
88
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
89
+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
90
+ '<head>\n' +
91
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
92
+ ' ' + '<title></title>\n' +
93
+ '</head>\n' +
94
+ '<body>',
95
+ 'closing_tag':
96
+ '</body>\n' +
97
+ '</html>'},
98
+ 'html:5': {
99
+ 'expand': True,
100
+ 'opening_tag':
101
+ '<!DOCTYPE html>\n' +
102
+ '<html lang="en">\n' +
103
+ '<head>\n' +
104
+ ' ' + '<meta charset="UTF-8" />\n' +
105
+ ' ' + '<title></title>\n' +
106
+ '</head>\n' +
107
+ '<body>',
108
+ 'closing_tag':
109
+ '</body>\n' +
110
+ '</html>'},
111
+ 'input:button': {
112
+ 'name': 'input',
113
+ 'attributes': { 'class': 'button', 'type': 'button', 'name': '', 'value': '' }
114
+ },
115
+ 'input:password': {
116
+ 'name': 'input',
117
+ 'attributes': { 'class': 'text password', 'type': 'password', 'name': '', 'value': '' }
118
+ },
119
+ 'input:radio': {
120
+ 'name': 'input',
121
+ 'attributes': { 'class': 'radio', 'type': 'radio', 'name': '', 'value': '' }
122
+ },
123
+ 'input:checkbox': {
124
+ 'name': 'input',
125
+ 'attributes': { 'class': 'checkbox', 'type': 'checkbox', 'name': '', 'value': '' }
126
+ },
127
+ 'input:file': {
128
+ 'name': 'input',
129
+ 'attributes': { 'class': 'file', 'type': 'file', 'name': '', 'value': '' }
130
+ },
131
+ 'input:text': {
132
+ 'name': 'input',
133
+ 'attributes': { 'class': 'text', 'type': 'text', 'name': '', 'value': '' }
134
+ },
135
+ 'input:submit': {
136
+ 'name': 'input',
137
+ 'attributes': { 'class': 'submit', 'type': 'submit', 'value': '' }
138
+ },
139
+ 'input:hidden': {
140
+ 'name': 'input',
141
+ 'attributes': { 'type': 'hidden', 'name': '', 'value': '' }
142
+ },
143
+ 'script:src': {
144
+ 'name': 'script',
145
+ 'attributes': { 'src': '' }
146
+ },
147
+ 'script:jquery': {
148
+ 'name': 'script',
149
+ 'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' }
150
+ },
151
+ 'script:jsapi': {
152
+ 'name': 'script',
153
+ 'attributes': { 'src': 'http://www.google.com/jsapi' }
154
+ },
155
+ 'script:jsapix': {
156
+ 'name': 'script',
157
+ 'text': '\n google.load("jquery", "1.3.2");\n google.setOnLoadCallback(function() {\n \n });\n'
158
+ },
159
+ 'link:css': {
160
+ 'name': 'link',
161
+ 'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'all' },
162
+ },
163
+ 'link:print': {
164
+ 'name': 'link',
165
+ 'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'print' },
166
+ },
167
+ 'link:favicon': {
168
+ 'name': 'link',
169
+ 'attributes': { 'rel': 'shortcut icon', 'type': 'image/x-icon', 'href': '' },
170
+ },
171
+ 'link:touch': {
172
+ 'name': 'link',
173
+ 'attributes': { 'rel': 'apple-touch-icon', 'href': '' },
174
+ },
175
+ 'link:rss': {
176
+ 'name': 'link',
177
+ 'attributes': { 'rel': 'alternate', 'type': 'application/rss+xml', 'title': 'RSS', 'href': '' },
178
+ },
179
+ 'link:atom': {
180
+ 'name': 'link',
181
+ 'attributes': { 'rel': 'alternate', 'type': 'application/atom+xml', 'title': 'Atom', 'href': '' },
182
+ },
183
+ 'meta:ie7': {
184
+ 'name': 'meta',
185
+ 'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=7' },
186
+ },
187
+ 'meta:ie8': {
188
+ 'name': 'meta',
189
+ 'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=8' },
190
+ },
191
+ 'form:get': {
192
+ 'name': 'form',
193
+ 'attributes': { 'method': 'get' },
194
+ },
195
+ 'form:g': {
196
+ 'name': 'form',
197
+ 'attributes': { 'method': 'get' },
198
+ },
199
+ 'form:post': {
200
+ 'name': 'form',
201
+ 'attributes': { 'method': 'post' },
202
+ },
203
+ 'form:p': {
204
+ 'name': 'form',
205
+ 'attributes': { 'method': 'post' },
206
+ },
207
+ }
208
+ synonyms = {
209
+ 'checkbox': 'input:checkbox',
210
+ 'check': 'input:checkbox',
211
+ 'input:c': 'input:checkbox',
212
+ 'button': 'input:button',
213
+ 'input:b': 'input:button',
214
+ 'input:h': 'input:hidden',
215
+ 'hidden': 'input:hidden',
216
+ 'submit': 'input:submit',
217
+ 'input:s': 'input:submit',
218
+ 'radio': 'input:radio',
219
+ 'input:r': 'input:radio',
220
+ 'text': 'input:text',
221
+ 'passwd': 'input:password',
222
+ 'password': 'input:password',
223
+ 'pw': 'input:password',
224
+ 'input:t': 'input:text',
225
+ 'linkcss': 'link:css',
226
+ 'scriptsrc': 'script:src',
227
+ 'jquery': 'script:jquery',
228
+ 'jsapi': 'script:jsapi',
229
+ 'html5': 'html:5',
230
+ 'html4': 'html:4s',
231
+ 'html4s': 'html:4s',
232
+ 'html4t': 'html:4t',
233
+ 'xhtml': 'html:xxs',
234
+ 'xhtmlt': 'html:xt',
235
+ 'xhtmls': 'html:xs',
236
+ 'xhtml11': 'html:xxs',
237
+ 'opt': 'option',
238
+ 'st': 'strong',
239
+ 'css': 'style',
240
+ 'csss': 'link:css',
241
+ 'css:src': 'link:css',
242
+ 'csssrc': 'link:css',
243
+ 'js': 'script',
244
+ 'jss': 'script:src',
245
+ 'js:src': 'script:src',
246
+ 'jssrc': 'script:src',
247
+ }
248
+ short_tags = (
249
+ 'area', 'base', 'basefont', 'br', 'embed', 'hr', \
250
+ 'input', 'img', 'link', 'param', 'meta')
251
+ required = {
252
+ 'a': {'href':''},
253
+ 'base': {'href':''},
254
+ 'abbr': {'title': ''},
255
+ 'acronym':{'title': ''},
256
+ 'bdo': {'dir': ''},
257
+ 'link': {'rel': 'stylesheet', 'href': ''},
258
+ 'style': {'type': 'text/css'},
259
+ 'script': {'type': 'text/javascript'},
260
+ 'img': {'src':'', 'alt':''},
261
+ 'iframe': {'src': '', 'frameborder': '0'},
262
+ 'embed': {'src': '', 'type': ''},
263
+ 'object': {'data': '', 'type': ''},
264
+ 'param': {'name': '', 'value': ''},
265
+ 'form': {'action': '', 'method': 'post'},
266
+ 'table': {'cellspacing': '0'},
267
+ 'input': {'type': '', 'name': '', 'value': ''},
268
+ 'base': {'href': ''},
269
+ 'area': {'shape': '', 'coords': '', 'href': '', 'alt': ''},
270
+ 'select': {'name': ''},
271
+ 'option': {'value': ''},
272
+ 'textarea':{'name': ''},
273
+ 'meta': {'content': ''},
274
+ }
275
+
276
+ class Parser:
277
+ """The parser.
278
+ """
279
+
280
+ # Constructor
281
+ # ---------------------------------------------------------------------------
282
+
283
+ def __init__(self, options=None, str='', dialect=HtmlDialect()):
284
+ """Constructor.
285
+ """
286
+
287
+ self.tokens = []
288
+ self.str = str
289
+ self.options = options
290
+ self.dialect = dialect
291
+ self.root = Element(parser=self)
292
+ self.caret = []
293
+ self.caret.append(self.root)
294
+ self._last = []
295
+
296
+ # Methods
297
+ # ---------------------------------------------------------------------------
298
+
299
+ def load_string(self, str):
300
+ """Loads a string to parse.
301
+ """
302
+
303
+ self.str = str
304
+ self._tokenize()
305
+ self._parse()
306
+
307
+ def render(self):
308
+ """Renders.
309
+ Called by [[Router]].
310
+ """
311
+
312
+ # Get the initial render of the root node
313
+ output = self.root.render()
314
+
315
+ # Indent by whatever the input is indented with
316
+ indent = re.findall("^[\r\n]*(\s*)", self.str)[0]
317
+ output = indent + output.replace("\n", "\n" + indent)
318
+
319
+ # Strip newline if not needed
320
+ if self.options.has("no-last-newline") \
321
+ or self.prefix or self.suffix:
322
+ output = re.sub(r'\n\s*$', '', output)
323
+
324
+ # TextMate mode
325
+ if self.options.has("textmate"):
326
+ output = self._textmatify(output)
327
+
328
+ return output
329
+
330
+ # Protected methods
331
+ # ---------------------------------------------------------------------------
332
+
333
+ def _textmatify(self, output):
334
+ """Returns a version of the output with TextMate placeholders in it.
335
+ """
336
+
337
+ matches = re.findall(r'(></)|("")|(\n\s+)\n|(.|\s)', output)
338
+ output = ''
339
+ n = 1
340
+ for i in matches:
341
+ if i[0]:
342
+ output += '>$%i</' % n
343
+ n += 1
344
+ elif i[1]:
345
+ output += '"$%i"' % n
346
+ n += 1
347
+ elif i[2]:
348
+ output += i[2] + '$%i\n' % n
349
+ n += 1
350
+ elif i[3]:
351
+ output += i[3]
352
+ output += "$0"
353
+ return output
354
+
355
+ def _tokenize(self):
356
+ """Tokenizes.
357
+ Initializes [[self.tokens]].
358
+ """
359
+
360
+ str = self.str.strip()
361
+
362
+ # Find prefix/suffix
363
+ while True:
364
+ match = re.match(r"^(\s*<[^>]+>\s*)", str)
365
+ if match is None: break
366
+ if self.prefix is None: self.prefix = ''
367
+ self.prefix += match.group(0)
368
+ str = str[len(match.group(0)):]
369
+
370
+ while True:
371
+ match = re.findall(r"(\s*<[^>]+>[\s\n\r]*)$", str)
372
+ if not match: break
373
+ if self.suffix is None: self.suffix = ''
374
+ self.suffix = match[0] + self.suffix
375
+ str = str[:-len(match[0])]
376
+
377
+ # Split by the element separators
378
+ for token in re.split('(<|>|\+(?!\\s*\+|$))', str):
379
+ if token.strip() != '':
380
+ self.tokens.append(Token(token, parser=self))
381
+
382
+ def _parse(self):
383
+ """Takes the tokens and does its thing.
384
+ Populates [[self.root]].
385
+ """
386
+
387
+ # Carry it over to the root node.
388
+ if self.prefix or self.suffix:
389
+ self.root.prefix = self.prefix
390
+ self.root.suffix = self.suffix
391
+ self.root.depth += 1
392
+
393
+ for token in self.tokens:
394
+ if token.type == Token.ELEMENT:
395
+ # Reset the "last elements added" list. We will
396
+ # repopulate this with the new elements added now.
397
+ self._last[:] = []
398
+
399
+ # Create [[Element]]s from a [[Token]].
400
+ # They will be created as many as the multiplier specifies,
401
+ # multiplied by how many carets we have
402
+ count = 0
403
+ for caret in self.caret:
404
+ local_count = 0
405
+ for i in range(token.multiplier):
406
+ count += 1
407
+ local_count += 1
408
+ new = Element(token, caret,
409
+ count = count,
410
+ local_count = local_count,
411
+ parser = self)
412
+ self._last.append(new)
413
+ caret.append(new)
414
+
415
+ # For >
416
+ elif token.type == Token.CHILD:
417
+ # The last children added.
418
+ self.caret[:] = self._last
419
+
420
+ # For <
421
+ elif token.type == Token.PARENT:
422
+ # If we're the root node, don't do anything
423
+ parent = self.caret[0].parent
424
+ if parent is not None:
425
+ self.caret[:] = [parent]
426
+ return
427
+
428
+ # Properties
429
+ # ---------------------------------------------------------------------------
430
+
431
+ # Property: dialect
432
+ # The dialect of XML
433
+ dialect = None
434
+
435
+ # Property: str
436
+ # The string
437
+ str = ''
438
+
439
+ # Property: tokens
440
+ # The list of tokens
441
+ tokens = []
442
+
443
+ # Property: options
444
+ # Reference to the [[Options]] instance
445
+ options = None
446
+
447
+ # Property: root
448
+ # The root [[Element]] node.
449
+ root = None
450
+
451
+ # Property: caret
452
+ # The current insertion point.
453
+ caret = None
454
+
455
+ # Property: _last
456
+ # List of the last appended stuff
457
+ _last = None
458
+
459
+ # Property: indent
460
+ # Yeah
461
+ indent = ''
462
+
463
+ # Property: prefix
464
+ # (String) The trailing tag in the beginning.
465
+ #
466
+ # Description:
467
+ # For instance, in `<div>ul>li</div>`, the `prefix` is `<div>`.
468
+ prefix = ''
469
+
470
+ # Property: suffix
471
+ # (string) The trailing tag at the end.
472
+ suffix = ''
473
+ pass
474
+
475
+ # ===============================================================================
476
+
477
+ class Element:
478
+ """An element.
479
+ """
480
+
481
+ def __init__(self, token=None, parent=None, count=None, local_count=None, \
482
+ parser=None, opening_tag=None, closing_tag=None, \
483
+ attributes=None, name=None, text=None):
484
+ """Constructor.
485
+
486
+ This is called by ???.
487
+
488
+ Description:
489
+ All parameters are optional.
490
+
491
+ token - (Token) The token (required)
492
+ parent - (Element) Parent element; `None` if root
493
+ count - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
494
+ local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
495
+ parser - (Parser) The parser
496
+
497
+ attributes - ...
498
+ name - ...
499
+ text - ...
500
+ """
501
+
502
+ self.children = []
503
+ self.attributes = {}
504
+ self.parser = parser
505
+
506
+ if token is not None:
507
+ # Assumption is that token is of type [[Token]] and is
508
+ # a [[Token.ELEMENT]].
509
+ self.name = token.name
510
+ self.attributes = token.attributes.copy()
511
+ self.text = token.text
512
+ self.populate = token.populate
513
+ self.expand = token.expand
514
+ self.opening_tag = token.opening_tag
515
+ self.closing_tag = token.closing_tag
516
+
517
+ # `count` can be given. This will substitude & in classname and ID
518
+ if count is not None:
519
+ for key in self.attributes:
520
+ attrib = self.attributes[key]
521
+ attrib = attrib.replace('&', ("%i" % count))
522
+ if local_count is not None:
523
+ attrib = attrib.replace('$', ("%i" % local_count))
524
+ self.attributes[key] = attrib
525
+
526
+ # Copy over from parameters
527
+ if attributes: self.attributes = attribues
528
+ if name: self.name = name
529
+ if text: self.text = text
530
+
531
+ self._fill_attributes()
532
+
533
+ self.parent = parent
534
+ if parent is not None:
535
+ self.depth = parent.depth + 1
536
+
537
+ if self.populate: self._populate()
538
+
539
+ def render(self):
540
+ """Renders the element, along with it's subelements, into HTML code.
541
+
542
+ [Grouped under "Rendering methods"]
543
+ """
544
+
545
+ output = ""
546
+ try: spaces_count = int(self.parser.options.options['indent-spaces'])
547
+ except: spaces_count = 4
548
+ spaces = ' ' * spaces_count
549
+ indent = self.depth * spaces
550
+
551
+ prefix, suffix = ('', '')
552
+ if self.prefix: prefix = self.prefix + "\n"
553
+ if self.suffix: suffix = self.suffix
554
+
555
+ # Make the guide from the ID (/#header), or the class if there's no ID (/.item)
556
+ # This is for the start-guide, end-guide and post-tag-guides
557
+ guide_str = ''
558
+ if 'id' in self.attributes:
559
+ guide_str += "#%s" % self.attributes['id']
560
+ elif 'class' in self.attributes:
561
+ guide_str += ".%s" % self.attributes['class'].replace(' ', '.')
562
+
563
+ # Build the post-tag guide (e.g., </div><!-- /#header -->),
564
+ # the start guide, and the end guide.
565
+ guide = ''
566
+ start_guide = ''
567
+ end_guide = ''
568
+ if ((self.name == 'div') and \
569
+ (('id' in self.attributes) or ('class' in self.attributes))):
570
+
571
+ if (self.parser.options.has('post-tag-guides')):
572
+ guide = "<!-- /%s -->" % guide_str
573
+
574
+ if (self.parser.options.has('start-guide-format')):
575
+ format = self.parser.options.get('start-guide-format')
576
+ try: start_guide = format % guide_str
577
+ except: start_guide = (format + " " + guide_str).strip()
578
+ start_guide = "%s<!-- %s -->\n" % (indent, start_guide)
579
+
580
+ if (self.parser.options.has('end-guide-format')):
581
+ format = self.parser.options.get('end-guide-format')
582
+ try: end_guide = format % guide_str
583
+ except: end_guide = (format + " " + guide_str).strip()
584
+ end_guide = "\n%s<!-- %s -->" % (indent, end_guide)
585
+
586
+ # Short, self-closing tags (<br />)
587
+ short_tags = self.parser.dialect.short_tags
588
+
589
+ # When it should be expanded..
590
+ # (That is, <div>\n...\n</div> or similar -- wherein something must go
591
+ # inside the opening/closing tags)
592
+ if len(self.children) > 0 \
593
+ or self.expand \
594
+ or prefix or suffix \
595
+ or (self.parser.options.has('expand-divs') and self.name == 'div'):
596
+
597
+ for child in self.children:
598
+ output += child.render()
599
+
600
+ # For expand divs: if there are no children (that is, `output`
601
+ # is still blank despite above), fill it with a blank line.
602
+ if (output == ''): output = indent + spaces + "\n"
603
+
604
+ # If we're a root node and we have a prefix or suffix...
605
+ # (Only the root node can have a prefix or suffix.)
606
+ if prefix or suffix:
607
+ output = "%s%s%s%s%s\n" % \
608
+ (indent, prefix, output, suffix, guide)
609
+
610
+ # Uh..
611
+ elif self.name != '' or \
612
+ self.opening_tag is not None or \
613
+ self.closing_tag is not None:
614
+ output = start_guide + \
615
+ indent + self.get_opening_tag() + "\n" + \
616
+ output + \
617
+ indent + self.get_closing_tag() + \
618
+ guide + end_guide + "\n"
619
+
620
+
621
+ # Short, self-closing tags (<br />)
622
+ elif self.name in short_tags:
623
+ output = "%s<%s />\n" % (indent, self.get_default_tag())
624
+
625
+ # Tags with text, possibly
626
+ elif self.name != '' or \
627
+ self.opening_tag is not None or \
628
+ self.closing_tag is not None:
629
+ output = "%s%s%s%s%s%s%s%s" % \
630
+ (start_guide, indent, self.get_opening_tag(), \
631
+ self.text, \
632
+ self.get_closing_tag(), \
633
+ guide, end_guide, "\n")
634
+
635
+ # Else, it's an empty-named element (like the root). Pass.
636
+ else: pass
637
+
638
+
639
+ return output
640
+
641
+ def get_default_tag(self):
642
+ """Returns the opening tag (without brackets).
643
+
644
+ Usage:
645
+ element.get_default_tag()
646
+
647
+ [Grouped under "Rendering methods"]
648
+ """
649
+
650
+ output = '%s' % (self.name)
651
+ for key, value in self.attributes.iteritems():
652
+ output += ' %s="%s"' % (key, value)
653
+ return output
654
+
655
+ def get_opening_tag(self):
656
+ if self.opening_tag is None:
657
+ return "<%s>" % self.get_default_tag()
658
+ else:
659
+ return self.opening_tag
660
+
661
+ def get_closing_tag(self):
662
+ if self.closing_tag is None:
663
+ return "</%s>" % self.name
664
+ else:
665
+ return self.closing_tag
666
+
667
+ def append(self, object):
668
+ """Registers an element as a child of this element.
669
+
670
+ Usage:
671
+ element.append(child)
672
+
673
+ Description:
674
+ Adds a given element `child` to the children list of this element. It
675
+ will be rendered when [[render()]] is called on the element.
676
+
677
+ See also:
678
+ - [[get_last_child()]]
679
+
680
+ [Grouped under "Traversion methods"]
681
+ """
682
+
683
+ self.children.append(object)
684
+
685
+ def get_last_child(self):
686
+ """Returns the last child element which was [[append()]]ed to this element.
687
+
688
+ Usage:
689
+ element.get_last_child()
690
+
691
+ Description:
692
+ This is the same as using `element.children[-1]`.
693
+
694
+ [Grouped under "Traversion methods"]
695
+ """
696
+
697
+ return self.children[-1]
698
+
699
+ def _populate(self):
700
+ """Expands with default items.
701
+
702
+ This is called when the [[populate]] flag is turned on.
703
+ """
704
+
705
+ if self.name == 'ul':
706
+ elements = [Element(name='li', parent=self, parser=self.parser)]
707
+
708
+ elif self.name == 'dl':
709
+ elements = [
710
+ Element(name='dt', parent=self, parser=self.parser),
711
+ Element(name='dd', parent=self, parser=self.parser)]
712
+
713
+ elif self.name == 'table':
714
+ tr = Element(name='tr', parent=self, parser=self.parser)
715
+ td = Element(name='td', parent=tr, parser=self.parser)
716
+ tr.children.append(td)
717
+ elements = [tr]
718
+
719
+ else:
720
+ elements = []
721
+
722
+ for el in elements:
723
+ self.children.append(el)
724
+
725
+ def _fill_attributes(self):
726
+ """Fills default attributes for certain elements.
727
+
728
+ Description:
729
+ This is called by the constructor.
730
+
731
+ [Protected, grouped under "Protected methods"]
732
+ """
733
+
734
+ # Make sure <a>'s have a href, <img>'s have an src, etc.
735
+ required = self.parser.dialect.required
736
+
737
+ for element, attribs in required.iteritems():
738
+ if self.name == element:
739
+ for attrib in attribs:
740
+ if attrib not in self.attributes:
741
+ self.attributes[attrib] = attribs[attrib]
742
+
743
+ # ---------------------------------------------------------------------------
744
+
745
+ # Property: last_child
746
+ # [Read-only]
747
+ last_child = property(get_last_child)
748
+
749
+ # ---------------------------------------------------------------------------
750
+
751
+ # Property: parent
752
+ # (Element) The parent element.
753
+ parent = None
754
+
755
+ # Property: name
756
+ # (String) The name of the element (e.g., `div`)
757
+ name = ''
758
+
759
+ # Property: attributes
760
+ # (Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)
761
+ attributes = None
762
+
763
+ # Property: children
764
+ # (List of Elements) The children
765
+ children = None
766
+
767
+ # Property: opening_tag
768
+ # (String or None) The opening tag. Optional; will use `name` and
769
+ # `attributes` if this is not given.
770
+ opening_tag = None
771
+
772
+ # Property: closing_tag
773
+ # (String or None) The closing tag
774
+ closing_tag = None
775
+
776
+ text = ''
777
+ depth = -1
778
+ expand = False
779
+ populate = False
780
+ parser = None
781
+
782
+ # Property: prefix
783
+ # Only the root note can have this.
784
+ prefix = None
785
+ suffix = None
786
+
787
+ # ===============================================================================
788
+
789
+ class Token:
790
+ def __init__(self, str, parser=None):
791
+ """Token.
792
+
793
+ Description:
794
+ str - The string to parse
795
+
796
+ In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)
797
+
798
+ For `>`, it will be a `Token` with `type` set to `Token.CHILD`
799
+ """
800
+
801
+ self.str = str.strip()
802
+ self.attributes = {}
803
+ self.parser = parser
804
+
805
+ # Set the type.
806
+ if self.str == '<':
807
+ self.type = Token.PARENT
808
+ elif self.str == '>':
809
+ self.type = Token.CHILD
810
+ elif self.str == '+':
811
+ self.type = Token.SIBLING
812
+ else:
813
+ self.type = Token.ELEMENT
814
+ self._init_element()
815
+
816
+ def _init_element(self):
817
+ """Initializes. Only called if the token is an element token.
818
+ [Private]
819
+ """
820
+
821
+ # Get the tag name. Default to DIV if none given.
822
+ name = re.findall('^([\w\-:]*)', self.str)[0]
823
+ name = name.lower().replace('-', ':')
824
+
825
+ # Find synonyms through this thesaurus
826
+ synonyms = self.parser.dialect.synonyms
827
+ if name in synonyms.keys():
828
+ name = synonyms[name]
829
+
830
+ if ':' in name:
831
+ try: spaces_count = int(self.parser.options.get('indent-spaces'))
832
+ except: spaces_count = 4
833
+ indent = ' ' * spaces_count
834
+
835
+ shortcuts = self.parser.dialect.shortcuts
836
+ if name in shortcuts.keys():
837
+ for key, value in shortcuts[name].iteritems():
838
+ setattr(self, key, value)
839
+ if 'html' in name:
840
+ return
841
+ else:
842
+ self.name = name
843
+
844
+ elif (name == ''): self.name = 'div'
845
+ else: self.name = name
846
+
847
+ # Look for attributes
848
+ attribs = []
849
+ for attrib in re.findall('\[([^\]]*)\]', self.str):
850
+ attribs.append(attrib)
851
+ self.str = self.str.replace("[" + attrib + "]", "")
852
+ if len(attribs) > 0:
853
+ for attrib in attribs:
854
+ try: key, value = attrib.split('=', 1)
855
+ except: key, value = attrib, ''
856
+ self.attributes[key] = value
857
+
858
+ # Try looking for text
859
+ text = None
860
+ for text in re.findall('\{([^\}]*)\}', self.str):
861
+ self.str = self.str.replace("{" + text + "}", "")
862
+ if text is not None:
863
+ self.text = text
864
+
865
+ # Get the class names
866
+ classes = []
867
+ for classname in re.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
868
+ classes.append(classname)
869
+ if len(classes) > 0:
870
+ try: self.attributes['class']
871
+ except: self.attributes['class'] = ''
872
+ self.attributes['class'] += ' ' + ' '.join(classes)
873
+ self.attributes['class'] = self.attributes['class'].strip()
874
+
875
+ # Get the ID
876
+ id = None
877
+ for id in re.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str): pass
878
+ if id is not None:
879
+ self.attributes['id'] = id
880
+
881
+ # See if there's a multiplier (e.g., "li*3")
882
+ multiplier = None
883
+ for multiplier in re.findall('\*\s*([0-9]+)', self.str): pass
884
+ if multiplier is not None:
885
+ self.multiplier = int(multiplier)
886
+
887
+ # Populate flag (e.g., ul+)
888
+ flags = None
889
+ for flags in re.findall('[\+\!]+$', self.str): pass
890
+ if flags is not None:
891
+ if '+' in flags: self.populate = True
892
+ if '!' in flags: self.expand = True
893
+
894
+ def __str__(self):
895
+ return self.str
896
+
897
+ str = ''
898
+ parser = None
899
+
900
+ # For elements
901
+ # See the properties of `Element` for description on these.
902
+ name = ''
903
+ attributes = None
904
+ multiplier = 1
905
+ expand = False
906
+ populate = False
907
+ text = ''
908
+ opening_tag = None
909
+ closing_tag = None
910
+
911
+ # Type
912
+ type = 0
913
+ ELEMENT = 2
914
+ CHILD = 4
915
+ PARENT = 8
916
+ SIBLING = 16
917
+
918
+ # ===============================================================================
919
+
920
+ class Router:
921
+ """The router.
922
+ """
923
+
924
+ # Constructor
925
+ # ---------------------------------------------------------------------------
926
+
927
+ def __init__(self):
928
+ pass
929
+
930
+ # Methods
931
+ # ---------------------------------------------------------------------------
932
+
933
+ def start(self, options=None, str=None, ret=None):
934
+ if (options):
935
+ self.options = Options(router=self, options=options, argv=None)
936
+ else:
937
+ self.options = Options(router=self, argv=sys.argv[1:], options=None)
938
+
939
+ if (self.options.has('help')):
940
+ return self.help()
941
+
942
+ elif (self.options.has('version')):
943
+ return self.version()
944
+
945
+ else:
946
+ return self.parse(str=str, ret=ret)
947
+
948
+ def help(self):
949
+ print "Usage: %s [OPTIONS]" % sys.argv[0]
950
+ print "Expands input into HTML."
951
+ print ""
952
+ for short, long, info in self.options.cmdline_keys:
953
+ if "Deprecated" in info: continue
954
+ if not short == '': short = '-%s,' % short
955
+ if not long == '': long = '--%s' % long.replace("=", "=XXX")
956
+
957
+ print "%6s %-25s %s" % (short, long, info)
958
+ print ""
959
+ print "\n".join(self.help_content)
960
+
961
+ def version(self):
962
+ print "Uhm, yeah."
963
+
964
+ def parse(self, str=None, ret=None):
965
+ self.parser = Parser(self.options)
966
+
967
+ try:
968
+ # Read the files
969
+ # for line in fileinput.input(): lines.append(line.rstrip(os.linesep))
970
+ if str is not None:
971
+ lines = str
972
+ else:
973
+ lines = [sys.stdin.read()]
974
+ lines = " ".join(lines)
975
+
976
+ except KeyboardInterrupt:
977
+ pass
978
+
979
+ except:
980
+ sys.stderr.write("Reading failed.\n")
981
+ return
982
+
983
+ try:
984
+ self.parser.load_string(lines)
985
+ output = self.parser.render()
986
+ if ret: return output
987
+ sys.stdout.write(output)
988
+
989
+ except:
990
+ sys.stderr.write("Parse error. Check your input.\n")
991
+ print sys.exc_info()[0]
992
+ print sys.exc_info()[1]
993
+
994
+ def exit(self):
995
+ sys.exit()
996
+
997
+ help_content = [
998
+ "Please refer to the manual for more information.",
999
+ ]
1000
+
1001
+ # ===============================================================================
1002
+
1003
+ class Options:
1004
+ def __init__(self, router, argv, options=None):
1005
+ # Init self
1006
+ self.router = router
1007
+
1008
+ # `options` can be given as a dict of stuff to preload
1009
+ if options:
1010
+ for k, v in options.iteritems():
1011
+ self.options[k] = v
1012
+ return
1013
+
1014
+ # Prepare for getopt()
1015
+ short_keys, long_keys = "", []
1016
+ for short, long, info in self.cmdline_keys: # 'v', 'version'
1017
+ short_keys += short
1018
+ long_keys.append(long)
1019
+
1020
+ try:
1021
+ getoptions, arguments = getopt.getopt(argv, short_keys, long_keys)
1022
+
1023
+ except getopt.GetoptError:
1024
+ err = sys.exc_info()[1]
1025
+ sys.stderr.write("Options error: %s\n" % err)
1026
+ sys.stderr.write("Try --help for a list of arguments.\n")
1027
+ return router.exit()
1028
+
1029
+ # Sort them out into options
1030
+ options = {}
1031
+ i = 0
1032
+ for option in getoptions:
1033
+ key, value = option # '--version', ''
1034
+ if (value == ''): value = True
1035
+
1036
+ # If the key is long, write it
1037
+ if key[0:2] == '--':
1038
+ clean_key = key[2:]
1039
+ options[clean_key] = value
1040
+
1041
+ # If the key is short, look for the long version of it
1042
+ elif key[0:1] == '-':
1043
+ for short, long, info in self.cmdline_keys:
1044
+ if short == key[1:]:
1045
+ print long
1046
+ options[long] = True
1047
+
1048
+ # Done
1049
+ for k, v in options.iteritems():
1050
+ self.options[k] = v
1051
+
1052
+ def __getattr__(self, attr):
1053
+ return self.get(attr)
1054
+
1055
+ def get(self, attr):
1056
+ try: return self.options[attr]
1057
+ except: return None
1058
+
1059
+ def has(self, attr):
1060
+ try: return self.options.has_key(attr)
1061
+ except: return False
1062
+
1063
+ options = {
1064
+ 'indent-spaces': 4
1065
+ }
1066
+ cmdline_keys = [
1067
+ ('h', 'help', 'Shows help'),
1068
+ ('v', 'version', 'Shows the version'),
1069
+ ('', 'no-guides', 'Deprecated'),
1070
+ ('', 'post-tag-guides', 'Adds comments at the end of DIV tags'),
1071
+ ('', 'textmate', 'Adds snippet info (textmate mode)'),
1072
+ ('', 'indent-spaces=', 'Indent spaces'),
1073
+ ('', 'expand-divs', 'Automatically expand divs'),
1074
+ ('', 'no-last-newline', 'Skip the trailing newline'),
1075
+ ('', 'start-guide-format=', 'To be documented'),
1076
+ ('', 'end-guide-format=', 'To be documented'),
1077
+ ]
1078
+
1079
+ # Property: router
1080
+ # Router
1081
+ router = 1
1082
+
1083
+ # ===============================================================================
1084
+
1085
+ if __name__ == "__main__":
1086
+ z = Router()
1087
+ z.start()