redcar-sparkup 1.0 → 1.01

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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()