├── .gitignore
├── Makefile
├── README.md
├── TextMate
└── Sparkup.tmbundle
│ ├── Commands
│ └── Sparkup expand.tmCommand
│ ├── Support
│ └── sparkup.py
│ └── info.plist
├── ftdetect
└── hsb.vim
├── ftplugin
├── mit-license.txt
├── sparkup-unittest.py
├── sparkup.py
└── vim
├── README.txt
└── ftplugin
├── html
├── sparkup.py
└── sparkup.vim
├── htmldjango
├── smarty
└── xml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .project
3 | doc
4 | distribution/
5 | .sourcescribe_index
6 | *.swp
7 | *.swo
8 | *.pyc
9 | cscope.out
10 | *~
11 |
12 | # symlinks created for pathogen
13 | /ftplugin
14 | /doc
15 |
16 | # Distribution files
17 | # */sparkup
18 | # */sparkup.py
19 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for sparkup distribution
2 | # TODO: this should use a separate build dir to copy SPARKUP_PY into.
3 | # SPARKUP_PY should not reside in the Vim runtime dir (getting not updated via Git!)
4 | SPARKUP_PY=sparkup.py
5 | VERSION=`date '+%Y%m%d'`
6 | README=README.md
7 |
8 | .PHONY: all textmate vim textmate-dist vim-dist plugins plugins-pre generic all-dist
9 | all: plugins
10 |
11 | plugins-pre:
12 | mkdir -p distribution
13 |
14 | plugins: plugins-pre all-dist
15 |
16 | textmate-dist: textmate
17 | cd TextMate && zip -9r ../distribution/sparkup-textmate-${VERSION}.zip . && cd ..
18 |
19 | vim-dist: vim
20 | cd vim && zip -9r ../distribution/sparkup-vim-${VERSION}.zip . && cd ..
21 |
22 | generic-dist: generic
23 | cd generic && zip -9r ../distribution/sparkup-generic-${VERSION}.zip . && cd ..
24 |
25 | all-dist:
26 | zip -9r distribution/sparkup-${VERSION}.zip generic vim textmate README.md -x */sparkup-readme.txt
27 | cp distribution/sparkup-${VERSION}.zip distribution/sparkup-latest.zip
28 |
29 | generic:
30 | cat ${SPARKUP_PY} > generic/sparkup
31 | chmod +x generic/sparkup
32 | #cp ${README} generic/sparkup-readme.txt
33 |
34 | textmate:
35 | #cp ${README} TextMate/sparkup-readme.txt
36 |
37 | vim: vim/doc/sparkup.txt
38 |
39 | # create pathogen friendly structure
40 | vim-pathogen: vim ftplugin doc
41 |
42 | ftplugin doc:
43 | ln -s vim/$@
44 |
45 | # Add asterisks to title, so it gets matched by `:helptags`
46 | vim/doc/sparkup.txt: ${README}
47 | mkdir -p $(@D)
48 | sed '1s/.*/*\0*/' $< > $@
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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 | Fixed by Zhao:
8 | This is a fork of original version. This version support both python 2 and 3.
9 |
10 | You can write HTML in a CSS-like syntax, and have Sparkup handle the expansion to full HTML
11 | code. It is meant to help you write long HTML blocks in your text editor by letting you
12 | type less characters than needed.
13 |
14 | Sparkup is written in Python, and requires Python 2.5 or newer (2.5 is preinstalled in
15 | Mac OS X Leopard). Sparkup also offers integration into common text editors. Support for VIM
16 | and TextMate are currently included.
17 |
18 | A short screencast is available here:
19 | [http://www.youtube.com/watch?v=Jw3jipcenKc](http://www.youtube.com/watch?v=Jw3jipcenKc)
20 |
21 | Usage and installation
22 | ----------------------
23 | You may download Sparkup from GitHub. [Download the latest version here](http://github.com/rstacruz/sparkup/downloads).
24 |
25 | - **TextMate**: Simply double-click on the `Sparkup.tmbundle` package in Finder. This
26 | will install it automatically. In TextMate, open an HTML file (or set the document type to
27 | HTML) type in something (e.g., `#header > h1`), then press `Ctrl` + `E`. Pressing `Tab`
28 | will cycle through empty elements.
29 |
30 | - **VIM**: See the `vim/README.txt` file for installation. In VIM,
31 | create or open an HTML file (or set the filetype to ``html``), type in something (e.g.
32 | `#header > h1`), then press `` whilst in **insert mode** to expand to HTML.
33 | Pressing `` will cycle through empty elements. Variables specified in
34 | `vim/README.txt` can be used to customise key mappings, and to add **normal mode** mappings
35 | as well.
36 |
37 | - **Others/command line use**: You may put `sparkup` in your `$PATH` somewhere. You may then
38 | invoke it by typing `echo "(input here)" | sparkup`, or `sparkup --help` for a list of commands.
39 |
40 | Credits
41 | -------
42 |
43 | Sparkup is written by Rico Sta. Cruz and is released under the MIT license.
44 |
45 | This project is inspired by [Zen Coding](http://code.google.com/p/zen-coding/) of
46 | [Vadim Makeev](http://pepelsbey.net). The Zen HTML syntax is forward-compatible with Sparkup
47 | (anything that Zen HTML can parse, Sparkup can too).
48 |
49 | The following people have contributed code to the project:
50 |
51 | - Guillermo O. Freschi (Tordek @ GitHub)
52 | Bugfixes to the parsing system
53 |
54 | - Eric Van Dewoestine (ervandew @ GitHub)
55 | Improvements to the VIM plugin
56 |
57 | Examples
58 | --------
59 |
60 | **`div`** expands to:
61 |
62 | ```html
63 |
64 | ```
65 |
66 | **`div#header`** expands to:
67 |
68 | ```html
69 |
70 | ```
71 |
72 | **`div.align-left#header`** expands to:
73 |
74 | ```html
75 |
76 | ```
77 |
78 | **`div#header + div#footer`** expands to:
79 |
80 | ```html
81 |
82 |
83 | ```
84 |
85 | **`#menu > ul`** expands to:
86 |
87 | ```html
88 |
91 | ```
92 |
93 | **`#menu > h3 + ul`** expands to:
94 |
95 | ```html
96 |
100 | ```
101 |
102 | **`#header > h1{Welcome to our site}`** expands to:
103 |
104 | ```html
105 |
108 | ```
109 |
110 | **`a[href=index.html]{Home}`** expands to:
111 |
112 | ```html
113 | Home
114 | ```
115 |
116 | **`ul > li*3`** expands to:
117 |
118 | ```html
119 |
124 | ```
125 |
126 | **`ul > li.item-$*3`** expands to:
127 |
128 | ```html
129 |
134 | ```
135 |
136 | **`ul > li.item-$*3 > strong`** expands to:
137 |
138 | ```html
139 |
144 | ```
145 |
146 | **`table > tr*2 > td.name + td*3`** expands to:
147 |
148 | ```html
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 | ```
164 |
165 | **`#header > ul > li < p{Footer}`** expands to:
166 |
167 | ```html
168 |
169 |
175 | ```
176 |
--------------------------------------------------------------------------------
/TextMate/Sparkup.tmbundle/Commands/Sparkup expand.tmCommand:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | beforeRunningCommand
6 | nop
7 | command
8 | #!/usr/bin/env python2
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 | 'indent-tabs': False,
17 | #'start-guide-format': 'Begin %s',
18 | #'end-guide-format': 'End %s',
19 | }
20 |
21 | sparkup.Router().start(options=options)
22 | fallbackInput
23 | line
24 | input
25 | selection
26 | keyEquivalent
27 | ^e
28 | name
29 | Sparkup expand
30 | output
31 | insertAsSnippet
32 | uuid
33 | 73A48D2B-D843-42A1-A288-0D1A6380043B
34 |
35 |
36 |
--------------------------------------------------------------------------------
/TextMate/Sparkup.tmbundle/Support/sparkup.py:
--------------------------------------------------------------------------------
1 | ../../../sparkup.py
--------------------------------------------------------------------------------
/TextMate/Sparkup.tmbundle/info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 | Sparkup
7 | ordering
8 |
9 | AA687F82-BF47-477C-A832-D3671736EA81
10 |
11 | uuid
12 | 0CDE6908-2EF0-4E86-A9C1-5AC12E320414
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ftdetect/hsb.vim:
--------------------------------------------------------------------------------
1 | au BufNewFile,BufRead *.hbs set filetype=html
2 |
--------------------------------------------------------------------------------
/ftplugin:
--------------------------------------------------------------------------------
1 | vim/ftplugin
--------------------------------------------------------------------------------
/mit-license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, Rico Sta. Cruz.
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/sparkup-unittest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2
2 | # -*- coding: utf-8 -*-
3 | import sys
4 | import sparkup
5 |
6 |
7 | class SparkupTest:
8 | options = {
9 | 'textmate': True,
10 | 'no-last-newline': True,
11 | 'post-tag-guides': True,
12 | }
13 | options = {
14 | 'default': {'textmate': True, 'no-last-newline': True, 'post-tag-guides': True},
15 | 'guides': {'textmate': True, 'no-last-newline': True, 'post-tag-guides': True, 'start-guide-format': 'Begin %s'},
16 | 'namespaced-elements': {'textmate': True, 'no-last-newline': True, 'post-tag-guides': True, 'namespaced-elements': True }
17 | }
18 | cases = {
19 | 'Simple test': {
20 | 'options': 'default',
21 | 'input': 'div',
22 | 'output': '$1
$0'
23 | },
24 | 'Class test': {
25 | 'input': 'div.lol',
26 | 'output': '$1
$0'
27 | },
28 | 'ID and class test': {
29 | 'input': 'div.class#id',
30 | 'output': '$1
$0'
31 | },
32 | 'ID and class test 2': {
33 | 'input': 'div#id.class',
34 | 'output': '$1
$0'
35 | },
36 | 'Attributes test': {
37 | 'input': 'div#id.class[style=color:blue]',
38 | 'output': '$1
$0'
39 | },
40 | 'Multiple attributes test': {
41 | 'input': 'div[align=center][style=color:blue][rel=none]',
42 | 'output': '$1
$0'
43 | },
44 | 'Multiple class test': {
45 | 'input': 'div.c1.c2.c3',
46 | 'output': '$1
$0'
47 | },
48 | 'Shortcut test': {
49 | 'input': 'input:button',
50 | 'output': ' $0'
51 | },
52 | 'Shortcut synonym test': {
53 | 'input': 'button',
54 | 'output': '$1 $0',
55 | },
56 | 'Child test': {
57 | 'input': 'div>ul>li',
58 | 'output': "$0"
59 | },
60 | 'Sibling test': {
61 | 'input': 'div#x + ul+ h3.class',
62 | 'output': '$1
\n\n$3 $0'
63 | },
64 | 'Child + sibling test': {
65 | 'input': 'div > ul > li + span',
66 | 'output': '$0'
67 | },
68 | 'Multiplier test 1': {
69 | 'input': 'ul > li*3',
70 | 'output': '$0'
71 | },
72 | 'Multiplier test 2': {
73 | 'input': 'ul > li.item-$*3',
74 | 'output': '$0'
75 | },
76 | 'Multiplier test 3': {
77 | 'input': 'ul > li.item-$*3 > a',
78 | 'output': '\n \n $2 \n \n \n $4 \n \n \n $6 \n \n $0'
79 | },
80 | 'Ampersand test': {
81 | 'input': 'td > tr.row-$*3 > td.cell-&*2',
82 | 'output': '\n \n $1 \n $2 \n \n \n $3 \n $4 \n \n \n $5 \n $6 \n \n$0'
83 | },
84 | 'Menu test': {
85 | 'input': 'ul#menu > li*3 > a > span',
86 | 'output': '$0'
87 | },
88 | 'Back test': {
89 | 'input': 'ul#menu > li*3 > a < < div',
90 | 'output': '\n$7
$0'
91 | },
92 | 'Expand test': {
93 | 'input': 'p#menu > table+ + ul',
94 | 'output': '\n \n
$0'
95 | },
96 | 'Text with dot test': {
97 | 'input': 'p { text.com }',
98 | 'output': ' text.com
$0'
99 | },
100 | 'Attribute with dot test': {
101 | 'input': 'p [attrib=text.com]',
102 | 'output': '$1
$0'
103 | },
104 | 'PHP tag test': {
105 | 'input': 'php',
106 | 'output': '$0',
107 | },
108 | 'Eruby tag test': {
109 | 'input': 'erb:p',
110 | 'output': '<%= %>$0',
111 | },
112 | 'ERB block test': {
113 | 'input': 'erb:b',
114 | 'output': '<% $2 %>\n $1\n<% end %>$0'
115 | },
116 | 'Tag name case (#49)': {
117 | 'input': 'groupId{foobar}',
118 | 'output': 'foobar $0'
119 | },
120 | 'Nested curly braces test': {
121 | 'input': 'p{{{ title }}}',
122 | 'output': '{{ title }}
$0'
123 | },
124 | 'Nested curly braces test (#54)': {
125 | 'input': 'html>head>title{${title}}',
126 | 'output': '\n \n ${title} \n \n$0'
127 | },
128 | 'HTML component element with dash test': {
129 | 'input': 'my-html-component',
130 | 'output': '$1 $0'
131 | },
132 | 'XML namespaced element': {
133 | 'options': 'namespaced-elements',
134 | 'input': 'namespaced-ul',
135 | 'output': '$1 $0'
136 | },
137 | # Add: text test, broken test, multi-attribute tests, indentation test, start and end comments test
138 | }
139 |
140 | def run(self):
141 | """Run Forrest run!"""
142 | failures = 0
143 |
144 | print("Test results:")
145 | for name, case in self.cases.iteritems():
146 | try:
147 | options_key = case['options']
148 | except:
149 | options_key = 'default'
150 |
151 | try:
152 | options = self.options[options_key]
153 | except:
154 | options = self.options['default']
155 |
156 | # Output buffer
157 | r = sparkup.Router()
158 | input = case['input']
159 | output = r.start(options=options, str=input, ret=True)
160 | del r
161 |
162 | # Did it work?
163 | result = output == case['output']
164 | if result:
165 | result_str = " OK "
166 | else:
167 | result_str = "FAIL"
168 |
169 | print(" - %-30s [%s]" % (name, result_str))
170 | if not result:
171 | failures += 1
172 | print("= %s" % input.replace("\n", "\n= "))
173 | print("Actual output (condensed):")
174 | print(" | '%s'" % output.replace("\n", r"\n").replace('"', '\"'))
175 | print("Actual output:")
176 | print(" | %s" % output.replace("\n", "\n | "))
177 | print("Expected:")
178 | print(" | %s" % case['output'].replace("\n", "\ n| "))
179 |
180 | return failures
181 |
182 | if __name__ == '__main__':
183 | s = SparkupTest()
184 | sys.exit(s.run())
185 |
--------------------------------------------------------------------------------
/sparkup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | version = "0.1.4"
4 |
5 | import getopt
6 | import sys
7 | import re
8 |
9 | # =============================================================================
10 |
11 | def iteritems(obj):
12 | """iteritems() in python2 and items() in python3"""
13 | if sys.version[0] == '2':
14 | return obj.iteritems()
15 | else:
16 | return obj.items()
17 |
18 | class Dialect:
19 | shortcuts = {}
20 | synonyms = {}
21 | required = {}
22 | short_tags = ()
23 |
24 | class XmlDialect(Dialect):
25 | shortcuts = {}
26 | synonyms = {}
27 | short_tags = ()
28 | required = {}
29 |
30 | class HtmlDialect(Dialect):
31 | # Constructor, accepts an indent which should come from an Options object
32 | # so it integrates well with their editing environment.
33 | def __init__(self, indent=4):
34 | self.indent = indent
35 |
36 | self.shortcuts = {
37 | 'cc:ie': {
38 | 'opening_tag': ''},
40 | 'cc:ie8': {
41 | 'opening_tag': ''},
43 | 'cc:ie9': {
44 | 'opening_tag': ''},
46 | 'cc:noie': {
47 | 'opening_tag': '',
48 | 'closing_tag': ''},
49 | 'php:t': {
50 | 'expand': True,
51 | 'opening_tag': '',
53 | },
54 | 'erb:p': {
55 | 'opening_tag': '<%= ',
56 | 'closing_tag': ' %>',
57 | },
58 | 'erb:c': {
59 | 'opening_tag': '%<# ',
60 | 'closing_tag': ' %>',
61 | },
62 | 'erb:d': {
63 | 'opening_tag': '<% ',
64 | 'closing_tag': ' %>',
65 | },
66 | 'erb:b': {
67 | 'expand': True,
68 | 'opening_tag' : '<% $2 %>',
69 | 'closing_tag' : '<% end %>',
70 | },
71 | 'erb:bp': {
72 | 'expand': True,
73 | 'opening_tag' : '<%= $2 %>',
74 | 'closing_tag' : '<% end %>',
75 | },
76 | 'html:4t': {
77 | 'expand': True,
78 | 'opening_tag':
79 | '\n' +
80 | '\n' +
81 | '\n' +
82 | (' ' * self.indent) + ' \n' +
83 | (' ' * self.indent) + ' \n' +
84 | '\n' +
85 | '',
86 | 'closing_tag':
87 | '\n' +
88 | ''},
89 | 'html:4s': {
90 | 'expand': True,
91 | 'opening_tag':
92 | '\n' +
93 | '\n' +
94 | '\n' +
95 | (' ' * self.indent) + ' \n' +
96 | (' ' * self.indent) + ' \n' +
97 | '\n' +
98 | '',
99 | 'closing_tag':
100 | '\n' +
101 | ''},
102 | 'html:xt': {
103 | 'expand': True,
104 | 'opening_tag':
105 | '\n' +
106 | '\n' +
107 | '\n' +
108 | (' ' * self.indent) + ' \n' +
109 | (' ' * self.indent) + ' \n' +
110 | '\n' +
111 | '',
112 | 'closing_tag':
113 | '\n' +
114 | ''},
115 | 'html:xs': {
116 | 'expand': True,
117 | 'opening_tag':
118 | '\n' +
119 | '\n' +
120 | '\n' +
121 | (' ' * self.indent) + ' \n' +
122 | (' ' * self.indent) + ' \n' +
123 | '\n' +
124 | '',
125 | 'closing_tag':
126 | '\n' +
127 | ''},
128 | 'html:xxs': {
129 | 'expand': True,
130 | 'opening_tag':
131 | '\n' +
132 | '\n' +
133 | '\n' +
134 | (' ' * self.indent) + ' \n' +
135 | (' ' * self.indent) + ' \n' +
136 | '\n' +
137 | '',
138 | 'closing_tag':
139 | '\n' +
140 | ''},
141 | 'html:5': {
142 | 'expand': True,
143 | 'opening_tag':
144 | '\n' +
145 | '\n' +
146 | '\n' +
147 | (' ' * self.indent) + ' \n' +
148 | (' ' * self.indent) + ' \n' +
149 | '\n' +
150 | '',
151 | 'closing_tag':
152 | '\n' +
153 | ''},
154 | 'input:button': {
155 | 'name': 'input',
156 | 'attributes': { 'class': 'button', 'type': 'button', 'name': '', 'value': '' }
157 | },
158 | 'input:password': {
159 | 'name': 'input',
160 | 'attributes': { 'class': 'text password', 'type': 'password', 'name': '', 'value': '' }
161 | },
162 | 'input:radio': {
163 | 'name': 'input',
164 | 'attributes': { 'class': 'radio', 'type': 'radio', 'name': '', 'value': '' }
165 | },
166 | 'input:checkbox': {
167 | 'name': 'input',
168 | 'attributes': { 'class': 'checkbox', 'type': 'checkbox', 'name': '', 'value': '' }
169 | },
170 | 'input:file': {
171 | 'name': 'input',
172 | 'attributes': { 'class': 'file', 'type': 'file', 'name': '', 'value': '' }
173 | },
174 | 'input:text': {
175 | 'name': 'input',
176 | 'attributes': { 'class': 'text', 'type': 'text', 'name': '', 'value': '' }
177 | },
178 | 'input:submit': {
179 | 'name': 'input',
180 | 'attributes': { 'class': 'submit', 'type': 'submit', 'value': '' }
181 | },
182 | 'input:hidden': {
183 | 'name': 'input',
184 | 'attributes': { 'type': 'hidden', 'name': '', 'value': '' }
185 | },
186 | 'script:src': {
187 | 'name': 'script',
188 | 'attributes': { 'src': '' }
189 | },
190 | 'script:jquery': {
191 | 'name': 'script',
192 | 'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js' }
193 | },
194 | 'script:jquery2': {
195 | 'name': 'script',
196 | 'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js' }
197 | },
198 | 'script:jsapi': {
199 | 'name': 'script',
200 | 'attributes': { 'src': 'http://www.google.com/jsapi' }
201 | },
202 | 'script:jsapix': {
203 | 'name': 'script',
204 | 'text': '\n google.load("jquery", "1.3.2");\n google.setOnLoadCallback(function() {\n \n });\n'
205 | },
206 | 'link:css': {
207 | 'name': 'link',
208 | 'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'all' },
209 | },
210 | 'link:print': {
211 | 'name': 'link',
212 | 'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'print' },
213 | },
214 | 'link:favicon': {
215 | 'name': 'link',
216 | 'attributes': { 'rel': 'shortcut icon', 'type': 'image/x-icon', 'href': '' },
217 | },
218 | 'link:touch': {
219 | 'name': 'link',
220 | 'attributes': { 'rel': 'apple-touch-icon', 'href': '' },
221 | },
222 | 'link:rss': {
223 | 'name': 'link',
224 | 'attributes': { 'rel': 'alternate', 'type': 'application/rss+xml', 'title': 'RSS', 'href': '' },
225 | },
226 | 'link:atom': {
227 | 'name': 'link',
228 | 'attributes': { 'rel': 'alternate', 'type': 'application/atom+xml', 'title': 'Atom', 'href': '' },
229 | },
230 | 'meta:ieedge': {
231 | 'name': 'meta',
232 | 'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=edge' },
233 | },
234 | 'form:get': {
235 | 'name': 'form',
236 | 'attributes': { 'method': 'get' },
237 | },
238 | 'form:g': {
239 | 'name': 'form',
240 | 'attributes': { 'method': 'get' },
241 | },
242 | 'form:post': {
243 | 'name': 'form',
244 | 'attributes': { 'method': 'post' },
245 | },
246 | 'form:p': {
247 | 'name': 'form',
248 | 'attributes': { 'method': 'post' },
249 | },
250 | }
251 | self.synonyms = {
252 | 'php': 'php:t',
253 | 'checkbox': 'input:checkbox',
254 | 'check': 'input:checkbox',
255 | 'input:c': 'input:checkbox',
256 | 'input:b': 'input:button',
257 | 'input:h': 'input:hidden',
258 | 'hidden': 'input:hidden',
259 | 'submit': 'input:submit',
260 | 'input:s': 'input:submit',
261 | 'radio': 'input:radio',
262 | 'input:r': 'input:radio',
263 | 'text': 'input:text',
264 | 'pass': 'input:password',
265 | 'passwd': 'input:password',
266 | 'password': 'input:password',
267 | 'pw': 'input:password',
268 | 'input': 'input:text',
269 | 'input:t': 'input:text',
270 | 'linkcss': 'link:css',
271 | 'scriptsrc': 'script:src',
272 | 'jquery': 'script:jquery',
273 | 'jsapi': 'script:jsapi',
274 | 'html5': 'html:5',
275 | 'html4': 'html:4s',
276 | 'html4s': 'html:4s',
277 | 'html4t': 'html:4t',
278 | 'xhtml': 'html:xxs',
279 | 'xhtmlt': 'html:xt',
280 | 'xhtmls': 'html:xs',
281 | 'xhtml11': 'html:xxs',
282 | 'opt': 'option',
283 | 'st': 'strong',
284 | 'css': 'style',
285 | 'csss': 'link:css',
286 | 'css:src': 'link:css',
287 | 'csssrc': 'link:css',
288 | 'js': 'script',
289 | 'jss': 'script:src',
290 | 'js:src': 'script:src',
291 | 'jssrc': 'script:src',
292 | }
293 | self.short_tags = (
294 | 'area', 'base', 'basefont', 'br', 'embed', 'hr',
295 | 'input', 'img', 'link', 'param', 'meta')
296 | self.required = {
297 | 'a': {'href':''},
298 | 'base': {'href':''},
299 | 'abbr': {'title': ''},
300 | 'acronym':{'title': ''},
301 | 'bdo': {'dir': ''},
302 | 'link': {'rel': 'stylesheet', 'href': ''},
303 | 'style': {'type': 'text/css'},
304 | 'script': {'type': 'text/javascript'},
305 | 'img': {'src':'', 'alt':''},
306 | 'iframe': {'src': '', 'frameborder': '0'},
307 | 'embed': {'src': '', 'type': ''},
308 | 'object': {'data': '', 'type': ''},
309 | 'param': {'name': '', 'value': ''},
310 | 'form': {'action': '', 'method': 'post'},
311 | 'input': {'type': '', 'name': '', 'value': ''},
312 | 'area': {'shape': '', 'coords': '', 'href': '', 'alt': ''},
313 | 'select': {'name': ''},
314 | 'option': {'value': ''},
315 | 'textarea':{'name': ''},
316 | 'meta': {'content': ''},
317 | }
318 | short_tags = (
319 | 'area', 'base', 'basefont', 'br', 'embed', 'hr',
320 | 'input', 'img', 'link', 'param', 'meta')
321 | required = {
322 | 'a': {'href':''},
323 | 'base': {'href':''},
324 | 'abbr': {'title': ''},
325 | 'acronym':{'title': ''},
326 | 'bdo': {'dir': ''},
327 | 'link': {'rel': 'stylesheet', 'href': ''},
328 | 'style': {'type': 'text/css'},
329 | 'script': {'type': 'text/javascript'},
330 | 'img': {'src':'', 'alt':''},
331 | 'iframe': {'src': '', 'frameborder': '0'},
332 | 'embed': {'src': '', 'type': ''},
333 | 'object': {'data': '', 'type': ''},
334 | 'param': {'name': '', 'value': ''},
335 | 'form': {'action': '', 'method': 'post'},
336 | 'input': {'type': '', 'name': '', 'value': ''},
337 | 'area': {'shape': '', 'coords': '', 'href': '', 'alt': ''},
338 | 'select': {'name': ''},
339 | 'option': {'value': ''},
340 | 'textarea':{'name': ''},
341 | 'meta': {'content': ''},
342 | }
343 |
344 | class Parser:
345 | """The parser.
346 | """
347 |
348 | # Constructor
349 | # -------------------------------------------------------------------------
350 |
351 | def __init__(self, options=None, str=''):
352 | """Constructor.
353 | """
354 |
355 | self.tokens = []
356 | self.str = str
357 | self.options = options
358 | if self.options.has("xml"):
359 | self.dialect = XmlDialect()
360 | else:
361 | self.dialect = HtmlDialect(int(self.options.options['indent-spaces']))
362 | self.root = Element(parser=self)
363 | self.caret = []
364 | self.caret.append(self.root)
365 | self._last = []
366 |
367 | # Methods
368 | # -------------------------------------------------------------------------
369 |
370 | def load_string(self, str):
371 | """Loads a string to parse.
372 | """
373 |
374 | self.str = str
375 | self._tokenize()
376 | self._parse()
377 |
378 | def render(self):
379 | """Renders.
380 | Called by [[Router]].
381 | """
382 |
383 | # Get the initial render of the root node
384 | output = self.root.render()
385 |
386 | # Indent by whatever the input is indented with
387 | indent = re.findall("^[\r\n]*(\s*)", self.str)[0]
388 | output = indent + output.replace("\n", "\n" + indent)
389 |
390 | # Strip newline if not needed
391 | if self.options.has("no-last-newline") \
392 | or self.prefix or self.suffix:
393 | output = re.sub(r'\n\s*$', '', output)
394 |
395 | # TextMate mode
396 | if self.options.has("textmate"):
397 | output = self._textmatify(output)
398 |
399 | return output
400 |
401 | # Protected methods
402 | # -------------------------------------------------------------------------
403 |
404 | def _textmatify(self, output):
405 | """Returns a version of the output with TextMate placeholders in it.
406 | """
407 |
408 | matches = re.findall(r'(>)|("")|(\n\s+)\n|(.|\s)', output)
409 | output = ''
410 | n = 1
411 | for i in matches:
412 | if i[0]:
413 | output += '>$%i' % n
414 | n += 1
415 | elif i[1]:
416 | output += '"$%i"' % n
417 | n += 1
418 | elif i[2]:
419 | output += i[2] + '$%i\n' % n
420 | n += 1
421 | elif i[3]:
422 | output += i[3]
423 | output += "$0"
424 | return output
425 |
426 | def _tokenize(self):
427 | """Tokenizes.
428 | Initializes [[self.tokens]].
429 | """
430 |
431 | str = self.str.strip()
432 |
433 | # Find prefix/suffix
434 | while True:
435 | match = re.match(r"^(\s*<[^>]+>\s*)", str)
436 | if match is None: break
437 | if self.prefix is None: self.prefix = ''
438 | self.prefix += match.group(0)
439 | str = str[len(match.group(0)):]
440 |
441 | while True:
442 | match = re.findall(r"(\s*<[^>]+>[\s\n\r]*)$", str)
443 | if not match: break
444 | if self.suffix is None: self.suffix = ''
445 | self.suffix = match[0] + self.suffix
446 | str = str[:-len(match[0])]
447 |
448 | # Split by the element separators
449 | for token in re.split('(<|>|\+(?!\\s*\+|$))', str):
450 | if token.strip() != '':
451 | self.tokens.append(Token(token, parser=self))
452 |
453 | def _parse(self):
454 | """Takes the tokens and does its thing.
455 | Populates [[self.root]].
456 | """
457 |
458 | # Carry it over to the root node.
459 | if self.prefix or self.suffix:
460 | self.root.prefix = self.prefix
461 | self.root.suffix = self.suffix
462 | self.root.depth += 1
463 |
464 | for token in self.tokens:
465 | if token.type == Token.ELEMENT:
466 | # Reset the "last elements added" list. We will
467 | # repopulate this with the new elements added now.
468 | self._last[:] = []
469 |
470 | # Create [[Element]]s from a [[Token]].
471 | # They will be created as many as the multiplier specifies,
472 | # multiplied by how many carets we have
473 | count = 0
474 | for caret in self.caret:
475 | local_count = 0
476 | for i in range(token.multiplier):
477 | count += 1
478 | local_count += 1
479 | new = Element(token, caret,
480 | count = count,
481 | local_count = local_count,
482 | parser = self)
483 | self._last.append(new)
484 | caret.append(new)
485 |
486 | # For >
487 | elif token.type == Token.CHILD:
488 | # The last children added.
489 | self.caret[:] = self._last
490 |
491 | # For <
492 | elif token.type == Token.PARENT:
493 | # If we're the root node, don't do anything
494 | parent = self.caret[0].parent
495 | if parent is not None:
496 | self.caret[:] = [parent]
497 | return
498 |
499 | # Properties
500 | # -------------------------------------------------------------------------
501 |
502 | # Property: dialect
503 | # The dialect of XML
504 | dialect = None
505 |
506 | # Property: str
507 | # The string
508 | str = ''
509 |
510 | # Property: tokens
511 | # The list of tokens
512 | tokens = []
513 |
514 | # Property: options
515 | # Reference to the [[Options]] instance
516 | options = None
517 |
518 | # Property: root
519 | # The root [[Element]] node.
520 | root = None
521 |
522 | # Property: caret
523 | # The current insertion point.
524 | caret = None
525 |
526 | # Property: _last
527 | # List of the last appended stuff
528 | _last = None
529 |
530 | # Property: indent
531 | # Yeah
532 | indent = ''
533 |
534 | # Property: prefix
535 | # (String) The trailing tag in the beginning.
536 | #
537 | # Description:
538 | # For instance, in `ul>li
`, the `prefix` is ``.
539 | prefix = ''
540 |
541 | # Property: suffix
542 | # (string) The trailing tag at the end.
543 | suffix = ''
544 | pass
545 |
546 | # =============================================================================
547 |
548 | class Element:
549 | """An element.
550 | """
551 |
552 | def __init__(self, token=None, parent=None, count=None, local_count=None,
553 | parser=None, opening_tag=None, closing_tag=None,
554 | attributes=None, name=None, text=None):
555 | """Constructor.
556 |
557 | This is called by ???.
558 |
559 | Description:
560 | All parameters are optional.
561 |
562 | token - (Token) The token (required)
563 | parent - (Element) Parent element; `None` if root
564 | count - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
565 | local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
566 | parser - (Parser) The parser
567 |
568 | attributes - ...
569 | name - ...
570 | text - ...
571 | """
572 |
573 | self.children = []
574 | self.attributes = {}
575 | self.parser = parser
576 |
577 | if token is not None:
578 | # Assumption is that token is of type [[Token]] and is
579 | # a [[Token.ELEMENT]].
580 | self.name = token.name
581 | self.attributes = token.attributes.copy()
582 | self.text = token.text
583 | self.populate = token.populate
584 | self.expand = token.expand
585 | self.opening_tag = token.opening_tag
586 | self.closing_tag = token.closing_tag
587 |
588 | # `count` can be given. This will substitude & in classname and ID
589 | if count is not None:
590 | for key in self.attributes:
591 | attrib = self.attributes[key]
592 | attrib = attrib.replace('&', ("%i" % count))
593 | if local_count is not None:
594 | attrib = attrib.replace('$', ("%i" % local_count))
595 | self.attributes[key] = attrib
596 |
597 | # Copy over from parameters
598 | if attributes: self.attributes = attributes
599 | if name: self.name = name
600 | if text: self.text = text
601 |
602 | self._fill_attributes()
603 |
604 | self.parent = parent
605 | if parent is not None:
606 | self.depth = parent.depth + 1
607 |
608 | if self.populate: self._populate()
609 |
610 | def render(self):
611 | """Renders the element, along with it's subelements, into HTML code.
612 |
613 | [Grouped under "Rendering methods"]
614 | """
615 |
616 | output = ""
617 |
618 | try: tabs = bool(self.parser.options.options['indent-tabs'])
619 | except: tabs = False
620 |
621 | if tabs:
622 | spaces = '\t'
623 | else:
624 | try: spaces_count = int(self.parser.options.options['indent-spaces'])
625 | except: spaces_count = 4
626 | spaces = ' ' * spaces_count
627 |
628 | indent = self.depth * spaces
629 |
630 | prefix, suffix = ('', '')
631 | if self.prefix: prefix = self.prefix + "\n"
632 | if self.suffix: suffix = self.suffix
633 |
634 | # Make the guide from the ID (/#header), or the class if there's no ID (/.item)
635 | # This is for the start-guide, end-guide and post-tag-guides
636 | guide_str = ''
637 | if 'id' in self.attributes:
638 | guide_str += "#%s" % self.attributes['id']
639 | elif 'class' in self.attributes:
640 | guide_str += ".%s" % self.attributes['class'].replace(' ', '.')
641 |
642 | # Build the post-tag guide (e.g.,
),
643 | # the start guide, and the end guide.
644 | guide = ''
645 | start_guide = ''
646 | end_guide = ''
647 | if ((self.name == 'div') and
648 | (('id' in self.attributes) or ('class' in self.attributes))):
649 |
650 | if (self.parser.options.has('post-tag-guides')):
651 | guide = "" % guide_str
652 |
653 | if (self.parser.options.has('start-guide-format')):
654 | format = self.parser.options.get('start-guide-format')
655 | try: start_guide = format % guide_str
656 | except: start_guide = (format + " " + guide_str).strip()
657 | start_guide = "%s\n" % (indent, start_guide)
658 |
659 | if (self.parser.options.has('end-guide-format')):
660 | format = self.parser.options.get('end-guide-format')
661 | try: end_guide = format % guide_str
662 | except: end_guide = (format + " " + guide_str).strip()
663 | end_guide = "\n%s" % (indent, end_guide)
664 |
665 | # Short, self-closing tags ( )
666 | short_tags = self.parser.dialect.short_tags
667 |
668 | # When it should be expanded..
669 | # (That is, \n...\n
or similar -- wherein something must go
670 | # inside the opening/closing tags)
671 | if len(self.children) > 0 \
672 | or self.expand \
673 | or prefix or suffix \
674 | or (self.parser.options.has('expand-divs') and self.name == 'div'):
675 |
676 | for child in self.children:
677 | output += child.render()
678 |
679 | # For expand divs: if there are no children (that is, `output`
680 | # is still blank despite above), fill it with a blank line.
681 | if (output == ''): output = indent + spaces + "\n"
682 |
683 | # If we're a root node and we have a prefix or suffix...
684 | # (Only the root node can have a prefix or suffix.)
685 | if prefix or suffix:
686 | output = "%s%s%s%s%s\n" % \
687 | (indent, prefix, output, suffix, guide)
688 |
689 | # Uh..
690 | elif self.name != '' or \
691 | self.opening_tag is not None or \
692 | self.closing_tag is not None:
693 | output = start_guide + \
694 | indent + self.get_opening_tag() + "\n" + \
695 | output + \
696 | indent + self.get_closing_tag() + \
697 | guide + end_guide + "\n"
698 |
699 |
700 | # Short, self-closing tags ( or depending on configuration)
701 | elif self.name in short_tags:
702 | if self.parser.options.has('no-html5-self-closing'):
703 | output = "%s<%s />\n" % (indent, self.get_default_tag())
704 | else:
705 | output = "%s<%s>\n" % (indent, self.get_default_tag())
706 |
707 | # Tags with text, possibly
708 | elif self.name != '' or \
709 | self.opening_tag is not None or \
710 | self.closing_tag is not None:
711 | between_tags = self.text
712 | if self.parser.options.has('open-empty-tags'):
713 | between_tags += "\n\n" + indent
714 | output = "%s%s%s%s%s%s%s%s" % \
715 | (start_guide, indent, self.get_opening_tag(),
716 | between_tags,
717 | self.get_closing_tag(),
718 | guide, end_guide, "\n")
719 |
720 | # Else, it's an empty-named element (like the root). Pass.
721 | else:
722 | pass
723 |
724 | return output
725 |
726 | def get_default_tag(self):
727 | """Returns the opening tag (without brackets).
728 |
729 | Usage:
730 | element.get_default_tag()
731 |
732 | [Grouped under "Rendering methods"]
733 | """
734 |
735 | output = '%s' % (self.name)
736 | for key, value in iteritems(self.attributes):
737 | output += ' %s="%s"' % (key, value)
738 | return output
739 |
740 | def get_opening_tag(self):
741 | if self.opening_tag is None:
742 | return "<%s>" % self.get_default_tag()
743 | else:
744 | return self.opening_tag
745 |
746 | def get_closing_tag(self):
747 | if self.closing_tag is None:
748 | return "%s>" % self.name
749 | else:
750 | return self.closing_tag
751 |
752 | def append(self, object):
753 | """Registers an element as a child of this element.
754 |
755 | Usage:
756 | element.append(child)
757 |
758 | Description:
759 | Adds a given element `child` to the children list of this element. It
760 | will be rendered when [[render()]] is called on the element.
761 |
762 | See also:
763 | - [[get_last_child()]]
764 |
765 | [Grouped under "Traversion methods"]
766 | """
767 |
768 | self.children.append(object)
769 |
770 | def get_last_child(self):
771 | """Returns the last child element which was [[append()]]ed to this element.
772 |
773 | Usage:
774 | element.get_last_child()
775 |
776 | Description:
777 | This is the same as using `element.children[-1]`.
778 |
779 | [Grouped under "Traversion methods"]
780 | """
781 |
782 | return self.children[-1]
783 |
784 | def _populate(self):
785 | """Expands with default items.
786 |
787 | This is called when the [[populate]] flag is turned on.
788 | """
789 |
790 | if self.name == 'ul':
791 | elements = [Element(name='li', parent=self, parser=self.parser)]
792 |
793 | elif self.name == 'dl':
794 | elements = [
795 | Element(name='dt', parent=self, parser=self.parser),
796 | Element(name='dd', parent=self, parser=self.parser)]
797 |
798 | elif self.name == 'table':
799 | tr = Element(name='tr', parent=self, parser=self.parser)
800 | td = Element(name='td', parent=tr, parser=self.parser)
801 | tr.children.append(td)
802 | elements = [tr]
803 |
804 | else:
805 | elements = []
806 |
807 | for el in elements:
808 | self.children.append(el)
809 |
810 | def _fill_attributes(self):
811 | """Fills default attributes for certain elements.
812 |
813 | Description:
814 | This is called by the constructor.
815 |
816 | [Protected, grouped under "Protected methods"]
817 | """
818 |
819 | # Make sure 's have a href, 's have an src, etc.
820 | required = self.parser.dialect.required
821 |
822 | for element, attribs in iteritems(required):
823 | if self.name == element:
824 | for attrib in attribs:
825 | if attrib not in self.attributes:
826 | self.attributes[attrib] = attribs[attrib]
827 |
828 | # -------------------------------------------------------------------------
829 |
830 | # Property: last_child
831 | # [Read-only]
832 | last_child = property(get_last_child)
833 |
834 | # -------------------------------------------------------------------------
835 |
836 | # Property: parent
837 | # (Element) The parent element.
838 | parent = None
839 |
840 | # Property: name
841 | # (String) The name of the element (e.g., `div`)
842 | name = ''
843 |
844 | # Property: attributes
845 | # (Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)
846 | attributes = None
847 |
848 | # Property: children
849 | # (List of Elements) The children
850 | children = None
851 |
852 | # Property: opening_tag
853 | # (String or None) The opening tag. Optional; will use `name` and
854 | # `attributes` if this is not given.
855 | opening_tag = None
856 |
857 | # Property: closing_tag
858 | # (String or None) The closing tag
859 | closing_tag = None
860 |
861 | text = ''
862 | depth = -1
863 | expand = False
864 | populate = False
865 | parser = None
866 |
867 | # Property: prefix
868 | # Only the root note can have this.
869 | prefix = None
870 | suffix = None
871 |
872 | # =============================================================================
873 |
874 | class Token:
875 | def __init__(self, str, parser=None):
876 | """Token.
877 |
878 | Description:
879 | str - The string to parse
880 |
881 | In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)
882 |
883 | For `>`, it will be a `Token` with `type` set to `Token.CHILD`
884 | """
885 |
886 | self.str = str.strip()
887 | self.attributes = {}
888 | self.parser = parser
889 |
890 | # Set the type.
891 | if self.str == '<':
892 | self.type = Token.PARENT
893 | elif self.str == '>':
894 | self.type = Token.CHILD
895 | elif self.str == '+':
896 | self.type = Token.SIBLING
897 | else:
898 | self.type = Token.ELEMENT
899 | self._init_element()
900 |
901 | def _init_element(self):
902 | """Initializes. Only called if the token is an element token.
903 | [Private]
904 | """
905 |
906 | # Get the tag name. Default to DIV if none given.
907 | name = re.findall('^([\w\-:]*)', self.str)[0]
908 | if self.parser.options.options['namespaced-elements'] == True:
909 | name = name.replace('-', ':')
910 |
911 | # Find synonyms through this thesaurus
912 | synonyms = self.parser.dialect.synonyms
913 | if name in synonyms.keys():
914 | name = synonyms[name]
915 |
916 | if ':' in name:
917 | shortcuts = self.parser.dialect.shortcuts
918 | if name in shortcuts.keys():
919 | for key, value in iteritems(shortcuts[name]):
920 | setattr(self, key, value)
921 | if 'html' in name:
922 | return
923 | else:
924 | self.name = name
925 |
926 | elif (name == ''): self.name = 'div'
927 | else: self.name = name
928 |
929 | # Look for attributes
930 | attribs = []
931 | for attrib in re.findall('\[([^\]]*)\]', self.str):
932 | attribs.append(attrib)
933 | self.str = self.str.replace("[" + attrib + "]", "")
934 | if len(attribs) > 0:
935 | for attrib in attribs:
936 | try: key, value = attrib.split('=', 1)
937 | except: key, value = attrib, ''
938 | self.attributes[key] = value
939 |
940 | # Try looking for text
941 | text = None
942 | for text in re.findall('\{(.*?)\}(?!\})', self.str):
943 | self.str = self.str.replace("{" + text + "}", "")
944 | if text is not None:
945 | self.text = text
946 |
947 | # Get the class names
948 | classes = []
949 | for classname in re.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
950 | classes.append(classname)
951 | if len(classes) > 0:
952 | try: self.attributes['class']
953 | except: self.attributes['class'] = ''
954 | self.attributes['class'] += ' ' + ' '.join(classes)
955 | self.attributes['class'] = self.attributes['class'].strip()
956 |
957 | # Get the ID
958 | id = None
959 | for id in re.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str): pass
960 | if id is not None:
961 | self.attributes['id'] = id
962 |
963 | # See if there's a multiplier (e.g., "li*3")
964 | multiplier = None
965 | for multiplier in re.findall('\*\s*([0-9]+)', self.str): pass
966 | if multiplier is not None:
967 | self.multiplier = int(multiplier)
968 |
969 | # Populate flag (e.g., ul+)
970 | flags = None
971 | for flags in re.findall('[\+\!]+$', self.str): pass
972 | if flags is not None:
973 | if '+' in flags: self.populate = True
974 | if '!' in flags: self.expand = True
975 |
976 | def __str__(self):
977 | return self.str
978 |
979 | str = ''
980 | parser = None
981 |
982 | # For elements
983 | # See the properties of `Element` for description on these.
984 | name = ''
985 | attributes = None
986 | multiplier = 1
987 | expand = False
988 | populate = False
989 | text = ''
990 | opening_tag = None
991 | closing_tag = None
992 |
993 | # Type
994 | type = 0
995 | ELEMENT = 2
996 | CHILD = 4
997 | PARENT = 8
998 | SIBLING = 16
999 |
1000 | # =============================================================================
1001 |
1002 | class Router:
1003 | """The router.
1004 | """
1005 |
1006 | # Constructor
1007 | # -------------------------------------------------------------------------
1008 |
1009 | def __init__(self):
1010 | pass
1011 |
1012 | # Methods
1013 | # -------------------------------------------------------------------------
1014 |
1015 | def start(self, options=None, str=None, ret=None):
1016 | if (options):
1017 | self.options = Options(router=self, options=options, argv=None)
1018 | else:
1019 | self.options = Options(router=self, argv=sys.argv[1:], options=None)
1020 |
1021 | if (self.options.has('help')):
1022 | return self.help()
1023 |
1024 | elif (self.options.has('version')):
1025 | return self.version()
1026 |
1027 | else:
1028 | return self.parse(str=str, ret=ret)
1029 |
1030 | def help(self):
1031 | print("Usage: %s [OPTIONS]" % sys.argv[0])
1032 | print("Expands input into HTML.")
1033 | print("")
1034 | for short, long, info in self.options.cmdline_keys:
1035 | if "Deprecated" in info: continue
1036 | if not short == '': short = '-%s,' % short
1037 | if not long == '': long = '--%s' % long.replace("=", "=XXX")
1038 |
1039 | print("%6s %-25s %s" % (short, long, info))
1040 | print("")
1041 | print("\n".join(self.help_content))
1042 |
1043 | def version(self):
1044 | print("Uhm, yeah.")
1045 |
1046 | def parse(self, str=None, ret=None):
1047 | self.parser = Parser(self.options)
1048 |
1049 | try:
1050 | # Read the files
1051 | if str is not None:
1052 | lines = str
1053 | else:
1054 | lines = sys.stdin.read()
1055 |
1056 | except KeyboardInterrupt:
1057 | pass
1058 |
1059 | except:
1060 | sys.stderr.write("Reading failed.\n")
1061 | return
1062 |
1063 | try:
1064 | self.parser.load_string(lines)
1065 | output = self.parser.render()
1066 | if ret: return output
1067 | sys.stdout.write(output)
1068 |
1069 | except:
1070 | sys.stderr.write("Parse error. Check your input.\n")
1071 | print(sys.exc_info()[0])
1072 | print(sys.exc_info()[1])
1073 |
1074 | def exit(self):
1075 | sys.exit()
1076 |
1077 | help_content = [
1078 | "Please refer to the manual for more information.",
1079 | ]
1080 |
1081 | # =============================================================================
1082 |
1083 | class Options:
1084 | def __init__(self, router, argv, options=None):
1085 | # Init self
1086 | self.router = router
1087 |
1088 | # `options` can be given as a dict of stuff to preload
1089 | if options:
1090 | for k, v in iteritems(options):
1091 | self.options[k] = v
1092 | return
1093 |
1094 | # Prepare for getopt()
1095 | short_keys, long_keys = "", []
1096 | for short, long, info in self.cmdline_keys: # 'v', 'version'
1097 | short_keys += short
1098 | long_keys.append(long)
1099 |
1100 | try:
1101 | getoptions, arguments = getopt.getopt(argv, short_keys, long_keys)
1102 |
1103 | except getopt.GetoptError:
1104 | err = sys.exc_info()[1]
1105 | sys.stderr.write("Options error: %s\n" % err)
1106 | sys.stderr.write("Try --help for a list of arguments.\n")
1107 | return router.exit()
1108 |
1109 | # Sort them out into options
1110 | options = {}
1111 | for option in getoptions:
1112 | key, value = option # '--version', ''
1113 | if (value == ''): value = True
1114 |
1115 | # If the key is long, write it
1116 | if key[0:2] == '--':
1117 | clean_key = key[2:]
1118 | options[clean_key] = value
1119 |
1120 | # If the key is short, look for the long version of it
1121 | elif key[0:1] == '-':
1122 | for short, long, info in self.cmdline_keys:
1123 | if short == key[1:]:
1124 | print(long)
1125 | options[long] = True
1126 |
1127 | # Done
1128 | for k, v in iteritems(options):
1129 | self.options[k] = v
1130 |
1131 | def __getattr__(self, attr):
1132 | return self.get(attr)
1133 |
1134 | def get(self, attr):
1135 | try: return self.options[attr]
1136 | except: return None
1137 |
1138 | def has(self, attr):
1139 | try: return self.options.has_key(attr)
1140 | except: return False
1141 |
1142 | options = {
1143 | 'indent-spaces': 4,
1144 | 'namespaced-elements': False,
1145 | }
1146 | cmdline_keys = [
1147 | ('h', 'help', 'Shows help'),
1148 | ('v', 'version', 'Shows the version'),
1149 | ('', 'no-guides', 'Deprecated'),
1150 | ('', 'post-tag-guides', 'Adds comments at the end of DIV tags'),
1151 | ('', 'textmate', 'Adds snippet info (textmate mode)'),
1152 | ('', 'indent-spaces=', 'Indent spaces'),
1153 | ('', 'indent-tabs', 'Indent with tabs'),
1154 | ('', 'expand-divs', 'Automatically expand divs'),
1155 | ('', 'no-last-newline', 'Skip the trailing newline'),
1156 | ('', 'start-guide-format=', 'To be documented'), # << TODO
1157 | ('', 'end-guide-format=', 'To be documented'), # << TODO
1158 | ('', 'xml', 'Skip html attribute fillings'),
1159 | ('', 'no-html5-self-closing', 'Use HTML4 instead of HTML5 '),
1160 | ('', 'open-empty-tags', 'leaves an empty space between empty tags instead of leaving them on one line'),
1161 | ]
1162 |
1163 | # Property: router
1164 | # Router
1165 | router = 1
1166 |
1167 | # =============================================================================
1168 |
1169 | if __name__ == "__main__":
1170 | z = Router()
1171 | z.start()
1172 |
--------------------------------------------------------------------------------
/vim/README.txt:
--------------------------------------------------------------------------------
1 | Installation
2 | ------------
3 |
4 | With Pathogen
5 | ^^^^^^^^^^^^^
6 |
7 | If you are using tpope's vim-pathogen, install as follows:
8 |
9 | cd ~/.vim/bundle ; git clone https://github.com/rstacruz/sparkup.git
10 | cd sparkup
11 | make vim-pathogen
12 |
13 |
14 | With Vundle
15 | ^^^^^^^^^^^
16 |
17 | If using Vundle, you can specify Sparkup as a bundle and installation will happen
18 | automatically. Add this to your Vim configuration:
19 |
20 | Plugin 'rstacruz/sparkup', {'rtp': 'vim/'}
21 |
22 | and run the standard installation command for Vundle:
23 |
24 | :PluginInstall
25 |
26 |
27 | Manual installation
28 | ^^^^^^^^^^^^^^^^^^^
29 |
30 | 1. Copy the contents of vim/ftplugin/ to your ~/.vim/ftplugin directory.
31 |
32 | (Assuming your current dir is sparkup/vim/)
33 | $ cp -R ftplugin ~/.vim/
34 |
35 | 2. Copy the sparkup.py file to your ~/.vim directory
36 |
37 | (Assuming your current dir is sparkup/vim/)
38 | $ cp ../sparkup.py ~/.vim/
39 |
40 |
41 | Configuration
42 | -------------
43 |
44 | Customise the Sparkup's configuration within Vim by specifying some or all of the following
45 | as variables within your Vim configuration using the ``let`` directive.
46 |
47 | g:sparkup (Default: 'sparkup') -
48 | Location of the sparkup executable. You shouldn't need to change this
49 | setting if you used the install option above.
50 |
51 | g:sparkupArgs (Default: '--no-last-newline') -
52 | Additional args passed to sparkup.
53 |
54 | g:sparkupExecuteMapping (Default: '') -
55 | Mapping used to execute sparkup within insert mode.
56 |
57 | g:sparkupNextMapping (Default: '') -
58 | Mapping used to jump to the next empty tag/attribute within insert mode.
59 |
60 | g:sparkupMaps (Default: 1) -
61 | Set up automatic mappings for Sparkup. If set to 0, this can be
62 | used to disable creation of any mappings, which is useful if
63 | full customisation is required.
64 |
65 | g:sparkupMapsNormal (Default: 0) -
66 | Set up mappings for normal mode within Vim. The same execute and next
67 | mappings configured above will apply to normal mode if this option is
68 | set.
69 |
70 |
--------------------------------------------------------------------------------
/vim/ftplugin/html/sparkup.py:
--------------------------------------------------------------------------------
1 | ../../../sparkup.py
--------------------------------------------------------------------------------
/vim/ftplugin/html/sparkup.vim:
--------------------------------------------------------------------------------
1 | " Sparkup
2 | " Installation:
3 | " Copy the contents of vim/ftplugin/ to your ~/.vim/ftplugin directory:
4 | "
5 | " $ cp -R vim/ftplugin ~/.vim/ftplugin/
6 | "
7 | " or use one of the automated methods specified in the README.txt file.
8 | "
9 | " Configuration:
10 | " g:sparkup (Default: 'sparkup') -
11 | " Location of the sparkup executable. You shouldn't need to change this
12 | " setting if you used the install option above.
13 | "
14 | " g:sparkupArgs (Default: '--no-last-newline') -
15 | " Additional args passed to sparkup.
16 | "
17 | " g:sparkupExecuteMapping (Default: '') -
18 | " Mapping used to execute sparkup within insert mode.
19 | "
20 | " g:sparkupNextMapping (Default: '') -
21 | " Mapping used to jump to the next empty tag/attribute within insert mode.
22 | "
23 | " g:sparkupMaps (Default: 1) -
24 | " Set up automatic mappings for Sparkup. If set to 0, this can be
25 | " used to disable creation of any mappings, which is useful if
26 | " full customisation is required.
27 | "
28 | " g:sparkupMapsNormal (Default: 0) -
29 | " Set up mappings for normal mode within Vim. The same execute and next
30 | " mappings configured above will apply to normal mode if this option is
31 | " set.
32 |
33 | if !exists('g:sparkupExecuteMapping')
34 | let g:sparkupExecuteMapping = ''
35 | endif
36 |
37 | if !exists('g:sparkupNextMapping')
38 | let g:sparkupNextMapping = ''
39 | endif
40 |
41 | if !exists('g:sparkupMaps')
42 | let g:sparkupMaps = 1
43 | endif
44 |
45 | if !exists('g:sparkupMapsNormal')
46 | let g:sparkupMapsNormal = 0
47 | endif
48 |
49 | inoremap SparkupExecute u:call Sparkup()
50 | inoremap SparkupNext u:call SparkupNext()
51 |
52 | if g:sparkupMaps
53 | if ! hasmapto('SparkupExecute', 'i')
54 | exec 'imap ' . g:sparkupExecuteMapping . ' SparkupExecute'
55 | endif
56 | if ! hasmapto('SparkupNext', 'i')
57 | exec 'imap ' . g:sparkupNextMapping . ' SparkupNext'
58 | endif
59 | if g:sparkupMapsNormal
60 | if ! hasmapto('SparkupExecute', 'n')
61 | exec 'nnoremap ' . g:sparkupExecuteMapping . ' :call Sparkup()'
62 | endif
63 | if ! hasmapto('SparkupNext', 'n')
64 | exec 'nnoremap ' . g:sparkupNextMapping . ' :call SparkupNext()'
65 | endif
66 | endif
67 | endif
68 |
69 | if exists('*s:Sparkup')
70 | finish
71 | endif
72 |
73 | function! s:Sparkup()
74 | if !exists('s:sparkup')
75 | let s:sparkup = exists('g:sparkup') ? g:sparkup : 'sparkup'
76 |
77 | if !executable(s:sparkup)
78 | " If g:sparkup is not configured (and/or not found in $PATH),
79 | " look for sparkup.vim in Vim's runtimepath.
80 | " XXX: quite expensive for a Pathogen-like environment (where &rtp is huge)
81 | let paths = substitute(escape(&runtimepath, ' '), '\(,\|$\)', '/**\1', 'g')
82 | let s:sparkup = fnamemodify(findfile('sparkup.py', paths), ':p')
83 |
84 | if !filereadable(s:sparkup)
85 | echohl WarningMsg
86 | echom 'Warning: could not find sparkup/sparkup.py on your path or in your vim runtime path.'
87 | echohl None
88 | unlet s:sparkup
89 | return
90 | endif
91 | endif
92 | let s:sparkup = '"' . s:sparkup . '"'
93 | " Workaround for windows, where the Python file cannot be executed via shebang
94 | if has('win32') || has('win64')
95 | let s:sparkup = 'python ' . s:sparkup
96 | endif
97 | endif
98 |
99 | " Build arguments list (not cached, g:sparkupArgs might change, also
100 | " &filetype, &expandtab etc)
101 | let sparkupArgs = exists('g:sparkupArgs') ? g:sparkupArgs : '--no-last-newline'
102 | " Pass '--xml' option, if 'xml' is used as filetype (default: none/'html')
103 | " NOTE: &filetype can contain multiple values, e.g. 'smarty.html'
104 | if index(split(&filetype, '\.'), 'xml') >= 0
105 | let sparkupArgs .= ' --xml'
106 | endif
107 | " If the user's settings are to indent with tabs, do so!
108 | " TODO textmate version of this functionality
109 | if !&expandtab
110 | let sparkupArgs .= ' --indent-tabs'
111 | endif
112 |
113 | let sparkupCmd = s:sparkup . printf(' %s --indent-spaces=%s', sparkupArgs, &shiftwidth)
114 | exec '.!' . sparkupCmd
115 | call s:SparkupNext()
116 | endfunction
117 |
118 | function! s:SparkupNext()
119 | " 1: empty tag, 2: empty attribute, 3: empty line
120 | let n = search('><\/\|\(""\)\|\(^\s*$\)', 'Wp')
121 | if n == 3
122 | startinsert!
123 | else
124 | let p = getpos(".")
125 | let p[2] = p[2] + 1
126 | call setpos(".", p)
127 | startinsert
128 | endif
129 | endfunction
130 |
--------------------------------------------------------------------------------
/vim/ftplugin/htmldjango:
--------------------------------------------------------------------------------
1 | html
--------------------------------------------------------------------------------
/vim/ftplugin/smarty:
--------------------------------------------------------------------------------
1 | html
--------------------------------------------------------------------------------
/vim/ftplugin/xml:
--------------------------------------------------------------------------------
1 | html
--------------------------------------------------------------------------------