Skip to content
Browse files

Updated vim plugin with easier instructions; Updated README

  • Loading branch information...
1 parent 3d10dc9 commit 430c17ef2d5ec750dae481d5aab216684b5ef23d @rstacruz committed Nov 27, 2009
Showing with 1,126 additions and 11 deletions.
  1. +7 −3 Makefile
  2. +4 −0 README.md
  3. +23 −0 vim/README.txt
  4. +1,087 −0 vim/ftplugin/html/sparkup.py
  5. +5 −8 vim/ftplugin/html/sparkup.vim
View
10 Makefile
@@ -1,5 +1,9 @@
-.PHONY: all textmate
+SPARKUP_PY=sparkup
+.PHONY: all textmate vim
+all: textmate vim
textmate:
mkdir -p TextMate/Sparkup.tmbundle/Support
- cp sparkup TextMate/Sparkup.tmbundle/Support/sparkup.py
-all: textmate
+ cp ${SPARKUP_PY} TextMate/Sparkup.tmbundle/Support/sparkup.py
+vim:
+ mkdir -p vim/ftplugin/html
+ cp ${SPARKUP_PY} vim/ftplugin/html/sparkup.py
View
4 README.md
@@ -39,8 +39,12 @@ This project is inspired by [Zen Coding](http://code.google.com/p/zen-coding/) o
(anything that Zen HTML can parse, Sparkup can too).
The following people have contributed code to the project:
+
- Guillermo O. Freschi (Tordek @ github)
+ Bugfixes to the parsing system
+
- Eric Van Dewoestine (ervandew @ github)
+ Improvements to the VIM plugin
Examples
--------
View
23 vim/README.txt
@@ -0,0 +1,23 @@
+Installation
+------------
+
+ Copy the contents of vim/ftplugin/ to your ~/.vim/ftplugin directory.
+
+ (Assuming your current dir is sparkup/vim/)
+ $ cp -R ftplugin ~/.vim/
+
+Configuration
+-------------
+
+ g:sparkup (Default: 'sparkup') -
+ Location of the sparkup executable. You shouldn't need to change this
+ setting if you used either of the install options above.
+
+ g:sparkupArgs (Default: '--no-last-newline') -
+ Additional args passed to sparkup.
+
+ g:sparkupExecuteMapping (Default: '<c-e>') -
+ Mapping used to execute sparkup.
+
+ g:sparkupNextMapping (Default: '<c-n>') -
+ Mapping used to jump to the next empty tag/attribute.
View
1,087 vim/ftplugin/html/sparkup.py
@@ -0,0 +1,1087 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+version = "0.1.2"
+
+import os
+import fileinput
+import getopt
+import sys
+import re
+
+# ===============================================================================
+
+class Dialect:
+ shortcuts = {}
+ synonyms = {}
+ required = {}
+ short_tags = ()
+
+class HtmlDialect(Dialect):
+ shortcuts = {
+ 'cc:ie': {
+ 'opening_tag': '<!--[if IE]>',
+ 'closing_tag': '<![endif]-->'},
+ 'cc:ie6': {
+ 'opening_tag': '<!--[if lte IE 6]>',
+ 'closing_tag': '<![endif]-->'},
+ 'cc:ie7': {
+ 'opening_tag': '<!--[if lte IE 7]>',
+ 'closing_tag': '<![endif]-->'},
+ 'cc:noie': {
+ 'opening_tag': '<!--[if !IE]><!-->',
+ 'closing_tag': '<!--<![endif]-->'},
+ 'html:4t': {
+ 'expand': True,
+ 'opening_tag':
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' +
+ '<html lang="en">\n' +
+ '<head>\n' +
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
+ ' ' + '<title></title>\n' +
+ '</head>\n' +
+ '<body>',
+ 'closing_tag':
+ '</body>\n' +
+ '</html>'},
+ 'html:4s': {
+ 'expand': True,
+ 'opening_tag':
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">\n' +
+ '<html lang="en">\n' +
+ '<head>\n' +
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
+ ' ' + '<title></title>\n' +
+ '</head>\n' +
+ '<body>',
+ 'closing_tag':
+ '</body>\n' +
+ '</html>'},
+ 'html:xt': {
+ 'expand': True,
+ 'opening_tag':
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' +
+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
+ '<head>\n' +
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
+ ' ' + '<title></title>\n' +
+ '</head>\n' +
+ '<body>',
+ 'closing_tag':
+ '</body>\n' +
+ '</html>'},
+ 'html:xs': {
+ 'expand': True,
+ 'opening_tag':
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' +
+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
+ '<head>\n' +
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
+ ' ' + '<title></title>\n' +
+ '</head>\n' +
+ '<body>',
+ 'closing_tag':
+ '</body>\n' +
+ '</html>'},
+ 'html:xxs': {
+ 'expand': True,
+ 'opening_tag':
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' +
+ '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">\n' +
+ '<head>\n' +
+ ' ' + '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />\n' +
+ ' ' + '<title></title>\n' +
+ '</head>\n' +
+ '<body>',
+ 'closing_tag':
+ '</body>\n' +
+ '</html>'},
+ 'html:5': {
+ 'expand': True,
+ 'opening_tag':
+ '<!DOCTYPE html>\n' +
+ '<html lang="en">\n' +
+ '<head>\n' +
+ ' ' + '<meta charset="UTF-8" />\n' +
+ ' ' + '<title></title>\n' +
+ '</head>\n' +
+ '<body>',
+ 'closing_tag':
+ '</body>\n' +
+ '</html>'},
+ 'input:button': {
+ 'name': 'input',
+ 'attributes': { 'class': 'button', 'type': 'button', 'name': '', 'value': '' }
+ },
+ 'input:password': {
+ 'name': 'input',
+ 'attributes': { 'class': 'text password', 'type': 'password', 'name': '', 'value': '' }
+ },
+ 'input:radio': {
+ 'name': 'input',
+ 'attributes': { 'class': 'radio', 'type': 'radio', 'name': '', 'value': '' }
+ },
+ 'input:checkbox': {
+ 'name': 'input',
+ 'attributes': { 'class': 'checkbox', 'type': 'checkbox', 'name': '', 'value': '' }
+ },
+ 'input:file': {
+ 'name': 'input',
+ 'attributes': { 'class': 'file', 'type': 'file', 'name': '', 'value': '' }
+ },
+ 'input:text': {
+ 'name': 'input',
+ 'attributes': { 'class': 'text', 'type': 'text', 'name': '', 'value': '' }
+ },
+ 'input:submit': {
+ 'name': 'input',
+ 'attributes': { 'class': 'submit', 'type': 'submit', 'value': '' }
+ },
+ 'input:hidden': {
+ 'name': 'input',
+ 'attributes': { 'type': 'hidden', 'name': '', 'value': '' }
+ },
+ 'script:src': {
+ 'name': 'script',
+ 'attributes': { 'src': '' }
+ },
+ 'script:jquery': {
+ 'name': 'script',
+ 'attributes': { 'src': 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' }
+ },
+ 'script:jsapi': {
+ 'name': 'script',
+ 'attributes': { 'src': 'http://www.google.com/jsapi' }
+ },
+ 'script:jsapix': {
+ 'name': 'script',
+ 'text': '\n google.load("jquery", "1.3.2");\n google.setOnLoadCallback(function() {\n \n });\n'
+ },
+ 'link:css': {
+ 'name': 'link',
+ 'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'all' },
+ },
+ 'link:print': {
+ 'name': 'link',
+ 'attributes': { 'rel': 'stylesheet', 'type': 'text/css', 'href': '', 'media': 'print' },
+ },
+ 'link:favicon': {
+ 'name': 'link',
+ 'attributes': { 'rel': 'shortcut icon', 'type': 'image/x-icon', 'href': '' },
+ },
+ 'link:touch': {
+ 'name': 'link',
+ 'attributes': { 'rel': 'apple-touch-icon', 'href': '' },
+ },
+ 'link:rss': {
+ 'name': 'link',
+ 'attributes': { 'rel': 'alternate', 'type': 'application/rss+xml', 'title': 'RSS', 'href': '' },
+ },
+ 'link:atom': {
+ 'name': 'link',
+ 'attributes': { 'rel': 'alternate', 'type': 'application/atom+xml', 'title': 'Atom', 'href': '' },
+ },
+ 'meta:ie7': {
+ 'name': 'meta',
+ 'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=7' },
+ },
+ 'meta:ie8': {
+ 'name': 'meta',
+ 'attributes': { 'http-equiv': 'X-UA-Compatible', 'content': 'IE=8' },
+ },
+ 'form:get': {
+ 'name': 'form',
+ 'attributes': { 'method': 'get' },
+ },
+ 'form:g': {
+ 'name': 'form',
+ 'attributes': { 'method': 'get' },
+ },
+ 'form:post': {
+ 'name': 'form',
+ 'attributes': { 'method': 'post' },
+ },
+ 'form:p': {
+ 'name': 'form',
+ 'attributes': { 'method': 'post' },
+ },
+ }
+ synonyms = {
+ 'checkbox': 'input:checkbox',
+ 'check': 'input:checkbox',
+ 'input:c': 'input:checkbox',
+ 'button': 'input:button',
+ 'input:b': 'input:button',
+ 'input:h': 'input:hidden',
+ 'hidden': 'input:hidden',
+ 'submit': 'input:submit',
+ 'input:s': 'input:submit',
+ 'radio': 'input:radio',
+ 'input:r': 'input:radio',
+ 'text': 'input:text',
+ 'passwd': 'input:password',
+ 'password': 'input:password',
+ 'pw': 'input:password',
+ 'input:t': 'input:text',
+ 'linkcss': 'link:css',
+ 'scriptsrc': 'script:src',
+ 'jquery': 'script:jquery',
+ 'jsapi': 'script:jsapi',
+ 'html5': 'html:5',
+ 'html4': 'html:4s',
+ 'html4s': 'html:4s',
+ 'html4t': 'html:4t',
+ 'xhtml': 'html:xxs',
+ 'xhtmlt': 'html:xt',
+ 'xhtmls': 'html:xs',
+ 'xhtml11': 'html:xxs',
+ 'opt': 'option',
+ 'st': 'strong',
+ 'css': 'style',
+ 'csss': 'link:css',
+ 'css:src': 'link:css',
+ 'csssrc': 'link:css',
+ 'js': 'script',
+ 'jss': 'script:src',
+ 'js:src': 'script:src',
+ 'jssrc': 'script:src',
+ }
+ short_tags = (
+ 'area', 'base', 'basefont', 'br', 'embed', 'hr', \
+ 'input', 'img', 'link', 'param', 'meta')
+ required = {
+ 'a': {'href':''},
+ 'base': {'href':''},
+ 'abbr': {'title': ''},
+ 'acronym':{'title': ''},
+ 'bdo': {'dir': ''},
+ 'link': {'rel': 'stylesheet', 'href': ''},
+ 'style': {'type': 'text/css'},
+ 'script': {'type': 'text/javascript'},
+ 'img': {'src':'', 'alt':''},
+ 'iframe': {'src': '', 'frameborder': '0'},
+ 'embed': {'src': '', 'type': ''},
+ 'object': {'data': '', 'type': ''},
+ 'param': {'name': '', 'value': ''},
+ 'form': {'action': '', 'method': 'post'},
+ 'table': {'cellspacing': '0'},
+ 'input': {'type': '', 'name': '', 'value': ''},
+ 'base': {'href': ''},
+ 'area': {'shape': '', 'coords': '', 'href': '', 'alt': ''},
+ 'select': {'name': ''},
+ 'option': {'value': ''},
+ 'textarea':{'name': ''},
+ 'meta': {'content': ''},
+ }
+
+class Parser:
+ """The parser.
+ """
+
+ # Constructor
+ # ---------------------------------------------------------------------------
+
+ def __init__(self, options=None, str='', dialect=HtmlDialect()):
+ """Constructor.
+ """
+
+ self.tokens = []
+ self.str = str
+ self.options = options
+ self.dialect = dialect
+ self.root = Element(parser=self)
+ self.caret = []
+ self.caret.append(self.root)
+ self._last = []
+
+ # Methods
+ # ---------------------------------------------------------------------------
+
+ def load_string(self, str):
+ """Loads a string to parse.
+ """
+
+ self.str = str
+ self._tokenize()
+ self._parse()
+
+ def render(self):
+ """Renders.
+ Called by [[Router]].
+ """
+
+ # Get the initial render of the root node
+ output = self.root.render()
+
+ # Indent by whatever the input is indented with
+ indent = re.findall("^[\r\n]*(\s*)", self.str)[0]
+ output = indent + output.replace("\n", "\n" + indent)
+
+ # Strip newline if not needed
+ if self.options.has("no-last-newline") \
+ or self.prefix or self.suffix:
+ output = re.sub(r'\n\s*$', '', output)
+
+ # TextMate mode
+ if self.options.has("textmate"):
+ output = self._textmatify(output)
+
+ return output
+
+ # Protected methods
+ # ---------------------------------------------------------------------------
+
+ def _textmatify(self, output):
+ """Returns a version of the output with TextMate placeholders in it.
+ """
+
+ matches = re.findall(r'(></)|("")|(\n\s+)\n|(.|\s)', output)
+ output = ''
+ n = 1
+ for i in matches:
+ if i[0]:
+ output += '>$%i</' % n
+ n += 1
+ elif i[1]:
+ output += '"$%i"' % n
+ n += 1
+ elif i[2]:
+ output += i[2] + '$%i\n' % n
+ n += 1
+ elif i[3]:
+ output += i[3]
+ output += "$0"
+ return output
+
+ def _tokenize(self):
+ """Tokenizes.
+ Initializes [[self.tokens]].
+ """
+
+ str = self.str.strip()
+
+ # Find prefix/suffix
+ while True:
+ match = re.match(r"^(\s*<[^>]+>\s*)", str)
+ if match is None: break
+ if self.prefix is None: self.prefix = ''
+ self.prefix += match.group(0)
+ str = str[len(match.group(0)):]
+
+ while True:
+ match = re.findall(r"(\s*<[^>]+>[\s\n\r]*)$", str)
+ if not match: break
+ if self.suffix is None: self.suffix = ''
+ self.suffix = match[0] + self.suffix
+ str = str[:-len(match[0])]
+
+ # Split by the element separators
+ for token in re.split('(<|>|\+(?!\\s*\+|$))', str):
+ if token.strip() != '':
+ self.tokens.append(Token(token, parser=self))
+
+ def _parse(self):
+ """Takes the tokens and does its thing.
+ Populates [[self.root]].
+ """
+
+ # Carry it over to the root node.
+ if self.prefix or self.suffix:
+ self.root.prefix = self.prefix
+ self.root.suffix = self.suffix
+ self.root.depth += 1
+
+ for token in self.tokens:
+ if token.type == Token.ELEMENT:
+ # Reset the "last elements added" list. We will
+ # repopulate this with the new elements added now.
+ self._last[:] = []
+
+ # Create [[Element]]s from a [[Token]].
+ # They will be created as many as the multiplier specifies,
+ # multiplied by how many carets we have
+ count = 0
+ for caret in self.caret:
+ local_count = 0
+ for i in range(token.multiplier):
+ count += 1
+ local_count += 1
+ new = Element(token, caret,
+ count = count,
+ local_count = local_count,
+ parser = self)
+ self._last.append(new)
+ caret.append(new)
+
+ # For >
+ elif token.type == Token.CHILD:
+ # The last children added.
+ self.caret[:] = self._last
+
+ # For <
+ elif token.type == Token.PARENT:
+ # If we're the root node, don't do anything
+ parent = self.caret[0].parent
+ if parent is not None:
+ self.caret[:] = [parent]
+ return
+
+ # Properties
+ # ---------------------------------------------------------------------------
+
+ # Property: dialect
+ # The dialect of XML
+ dialect = None
+
+ # Property: str
+ # The string
+ str = ''
+
+ # Property: tokens
+ # The list of tokens
+ tokens = []
+
+ # Property: options
+ # Reference to the [[Options]] instance
+ options = None
+
+ # Property: root
+ # The root [[Element]] node.
+ root = None
+
+ # Property: caret
+ # The current insertion point.
+ caret = None
+
+ # Property: _last
+ # List of the last appended stuff
+ _last = None
+
+ # Property: indent
+ # Yeah
+ indent = ''
+
+ # Property: prefix
+ # (String) The trailing tag in the beginning.
+ #
+ # Description:
+ # For instance, in `<div>ul>li</div>`, the `prefix` is `<div>`.
+ prefix = ''
+
+ # Property: suffix
+ # (string) The trailing tag at the end.
+ suffix = ''
+ pass
+
+# ===============================================================================
+
+class Element:
+ """An element.
+ """
+
+ def __init__(self, token=None, parent=None, count=None, local_count=None, \
+ parser=None, opening_tag=None, closing_tag=None, \
+ attributes=None, name=None, text=None):
+ """Constructor.
+
+ This is called by ???.
+
+ Description:
+ All parameters are optional.
+
+ token - (Token) The token (required)
+ parent - (Element) Parent element; `None` if root
+ count - (Int) The number to substitute for `&` (e.g., in `li.item-$`)
+ local_count - (Int) The number to substitute for `$` (e.g., in `li.item-&`)
+ parser - (Parser) The parser
+
+ attributes - ...
+ name - ...
+ text - ...
+ """
+
+ self.children = []
+ self.attributes = {}
+ self.parser = parser
+
+ if token is not None:
+ # Assumption is that token is of type [[Token]] and is
+ # a [[Token.ELEMENT]].
+ self.name = token.name
+ self.attributes = token.attributes.copy()
+ self.text = token.text
+ self.populate = token.populate
+ self.expand = token.expand
+ self.opening_tag = token.opening_tag
+ self.closing_tag = token.closing_tag
+
+ # `count` can be given. This will substitude & in classname and ID
+ if count is not None:
+ for key in self.attributes:
+ attrib = self.attributes[key]
+ attrib = attrib.replace('&', ("%i" % count))
+ if local_count is not None:
+ attrib = attrib.replace('$', ("%i" % local_count))
+ self.attributes[key] = attrib
+
+ # Copy over from parameters
+ if attributes: self.attributes = attribues
+ if name: self.name = name
+ if text: self.text = text
+
+ self._fill_attributes()
+
+ self.parent = parent
+ if parent is not None:
+ self.depth = parent.depth + 1
+
+ if self.populate: self._populate()
+
+ def render(self):
+ """Renders the element, along with it's subelements, into HTML code.
+
+ [Grouped under "Rendering methods"]
+ """
+
+ output = ""
+ try: spaces_count = int(self.parser.options.options['indent-spaces'])
+ except: spaces_count = 4
+ spaces = ' ' * spaces_count
+ indent = self.depth * spaces
+
+ prefix, suffix = ('', '')
+ if self.prefix: prefix = self.prefix + "\n"
+ if self.suffix: suffix = self.suffix
+
+ # Make the guide from the ID (/#header), or the class if there's no ID (/.item)
+ # This is for the start-guide, end-guide and post-tag-guides
+ guide_str = ''
+ if 'id' in self.attributes:
+ guide_str += "#%s" % self.attributes['id']
+ elif 'class' in self.attributes:
+ guide_str += ".%s" % self.attributes['class'].replace(' ', '.')
+
+ # Build the post-tag guide (e.g., </div><!-- /#header -->),
+ # the start guide, and the end guide.
+ guide = ''
+ start_guide = ''
+ end_guide = ''
+ if ((self.name == 'div') and \
+ (('id' in self.attributes) or ('class' in self.attributes))):
+
+ if (self.parser.options.has('post-tag-guides')):
+ guide = "<!-- /%s -->" % guide_str
+
+ if (self.parser.options.has('start-guide-format')):
+ format = self.parser.options.get('start-guide-format')
+ try: start_guide = format % guide_str
+ except: start_guide = (format + " " + guide_str).strip()
+ start_guide = "%s<!-- %s -->\n" % (indent, start_guide)
+
+ if (self.parser.options.has('end-guide-format')):
+ format = self.parser.options.get('end-guide-format')
+ try: end_guide = format % guide_str
+ except: end_guide = (format + " " + guide_str).strip()
+ end_guide = "\n%s<!-- %s -->" % (indent, end_guide)
+
+ # Short, self-closing tags (<br />)
+ short_tags = self.parser.dialect.short_tags
+
+ # When it should be expanded..
+ # (That is, <div>\n...\n</div> or similar -- wherein something must go
+ # inside the opening/closing tags)
+ if len(self.children) > 0 \
+ or self.expand \
+ or prefix or suffix \
+ or (self.parser.options.has('expand-divs') and self.name == 'div'):
+
+ for child in self.children:
+ output += child.render()
+
+ # For expand divs: if there are no children (that is, `output`
+ # is still blank despite above), fill it with a blank line.
+ if (output == ''): output = indent + spaces + "\n"
+
+ # If we're a root node and we have a prefix or suffix...
+ # (Only the root node can have a prefix or suffix.)
+ if prefix or suffix:
+ output = "%s%s%s%s%s\n" % \
+ (indent, prefix, output, suffix, guide)
+
+ # Uh..
+ elif self.name != '' or \
+ self.opening_tag is not None or \
+ self.closing_tag is not None:
+ output = start_guide + \
+ indent + self.get_opening_tag() + "\n" + \
+ output + \
+ indent + self.get_closing_tag() + \
+ guide + end_guide + "\n"
+
+
+ # Short, self-closing tags (<br />)
+ elif self.name in short_tags:
+ output = "%s<%s />\n" % (indent, self.get_default_tag())
+
+ # Tags with text, possibly
+ elif self.name != '' or \
+ self.opening_tag is not None or \
+ self.closing_tag is not None:
+ output = "%s%s%s%s%s%s%s%s" % \
+ (start_guide, indent, self.get_opening_tag(), \
+ self.text, \
+ self.get_closing_tag(), \
+ guide, end_guide, "\n")
+
+ # Else, it's an empty-named element (like the root). Pass.
+ else: pass
+
+
+ return output
+
+ def get_default_tag(self):
+ """Returns the opening tag (without brackets).
+
+ Usage:
+ element.get_default_tag()
+
+ [Grouped under "Rendering methods"]
+ """
+
+ output = '%s' % (self.name)
+ for key, value in self.attributes.iteritems():
+ output += ' %s="%s"' % (key, value)
+ return output
+
+ def get_opening_tag(self):
+ if self.opening_tag is None:
+ return "<%s>" % self.get_default_tag()
+ else:
+ return self.opening_tag
+
+ def get_closing_tag(self):
+ if self.closing_tag is None:
+ return "</%s>" % self.name
+ else:
+ return self.closing_tag
+
+ def append(self, object):
+ """Registers an element as a child of this element.
+
+ Usage:
+ element.append(child)
+
+ Description:
+ Adds a given element `child` to the children list of this element. It
+ will be rendered when [[render()]] is called on the element.
+
+ See also:
+ - [[get_last_child()]]
+
+ [Grouped under "Traversion methods"]
+ """
+
+ self.children.append(object)
+
+ def get_last_child(self):
+ """Returns the last child element which was [[append()]]ed to this element.
+
+ Usage:
+ element.get_last_child()
+
+ Description:
+ This is the same as using `element.children[-1]`.
+
+ [Grouped under "Traversion methods"]
+ """
+
+ return self.children[-1]
+
+ def _populate(self):
+ """Expands with default items.
+
+ This is called when the [[populate]] flag is turned on.
+ """
+
+ if self.name == 'ul':
+ elements = [Element(name='li', parent=self, parser=self.parser)]
+
+ elif self.name == 'dl':
+ elements = [
+ Element(name='dt', parent=self, parser=self.parser),
+ Element(name='dd', parent=self, parser=self.parser)]
+
+ elif self.name == 'table':
+ tr = Element(name='tr', parent=self, parser=self.parser)
+ td = Element(name='td', parent=tr, parser=self.parser)
+ tr.children.append(td)
+ elements = [tr]
+
+ else:
+ elements = []
+
+ for el in elements:
+ self.children.append(el)
+
+ def _fill_attributes(self):
+ """Fills default attributes for certain elements.
+
+ Description:
+ This is called by the constructor.
+
+ [Protected, grouped under "Protected methods"]
+ """
+
+ # Make sure <a>'s have a href, <img>'s have an src, etc.
+ required = self.parser.dialect.required
+
+ for element, attribs in required.iteritems():
+ if self.name == element:
+ for attrib in attribs:
+ if attrib not in self.attributes:
+ self.attributes[attrib] = attribs[attrib]
+
+ # ---------------------------------------------------------------------------
+
+ # Property: last_child
+ # [Read-only]
+ last_child = property(get_last_child)
+
+ # ---------------------------------------------------------------------------
+
+ # Property: parent
+ # (Element) The parent element.
+ parent = None
+
+ # Property: name
+ # (String) The name of the element (e.g., `div`)
+ name = ''
+
+ # Property: attributes
+ # (Dict) The dictionary of attributes (e.g., `{'src': 'image.jpg'}`)
+ attributes = None
+
+ # Property: children
+ # (List of Elements) The children
+ children = None
+
+ # Property: opening_tag
+ # (String or None) The opening tag. Optional; will use `name` and
+ # `attributes` if this is not given.
+ opening_tag = None
+
+ # Property: closing_tag
+ # (String or None) The closing tag
+ closing_tag = None
+
+ text = ''
+ depth = -1
+ expand = False
+ populate = False
+ parser = None
+
+ # Property: prefix
+ # Only the root note can have this.
+ prefix = None
+ suffix = None
+
+# ===============================================================================
+
+class Token:
+ def __init__(self, str, parser=None):
+ """Token.
+
+ Description:
+ str - The string to parse
+
+ In the string `div > ul`, there are 3 tokens. (`div`, `>`, and `ul`)
+
+ For `>`, it will be a `Token` with `type` set to `Token.CHILD`
+ """
+
+ self.str = str.strip()
+ self.attributes = {}
+ self.parser = parser
+
+ # Set the type.
+ if self.str == '<':
+ self.type = Token.PARENT
+ elif self.str == '>':
+ self.type = Token.CHILD
+ elif self.str == '+':
+ self.type = Token.SIBLING
+ else:
+ self.type = Token.ELEMENT
+ self._init_element()
+
+ def _init_element(self):
+ """Initializes. Only called if the token is an element token.
+ [Private]
+ """
+
+ # Get the tag name. Default to DIV if none given.
+ name = re.findall('^([\w\-:]*)', self.str)[0]
+ name = name.lower().replace('-', ':')
+
+ # Find synonyms through this thesaurus
+ synonyms = self.parser.dialect.synonyms
+ if name in synonyms.keys():
+ name = synonyms[name]
+
+ if ':' in name:
+ try: spaces_count = int(self.parser.options.get('indent-spaces'))
+ except: spaces_count = 4
+ indent = ' ' * spaces_count
+
+ shortcuts = self.parser.dialect.shortcuts
+ if name in shortcuts.keys():
+ for key, value in shortcuts[name].iteritems():
+ setattr(self, key, value)
+ if 'html' in name:
+ return
+ else:
+ self.name = name
+
+ elif (name == ''): self.name = 'div'
+ else: self.name = name
+
+ # Look for attributes
+ attribs = []
+ for attrib in re.findall('\[([^\]]*)\]', self.str):
+ attribs.append(attrib)
+ self.str = self.str.replace("[" + attrib + "]", "")
+ if len(attribs) > 0:
+ for attrib in attribs:
+ try: key, value = attrib.split('=', 1)
+ except: key, value = [attrib, '']
+ self.attributes[key] = value
+
+ # Try looking for text
+ text = None
+ for text in re.findall('\{([^\}]*)\}', self.str):
+ self.str = self.str.replace("{" + text + "}", "")
+ if text is not None:
+ self.text = text
+
+ # Get the class names
+ classes = []
+ for classname in re.findall('\.([\$a-zA-Z0-9_\-\&]+)', self.str):
+ classes.append(classname)
+ if len(classes) > 0:
+ try: self.attributes['class']
+ except: self.attributes['class'] = ''
+ self.attributes['class'] += ' ' + ' '.join(classes)
+ self.attributes['class'] = self.attributes['class'].strip()
+
+ # Get the ID
+ id = None
+ for id in re.findall('#([\$a-zA-Z0-9_\-\&]+)', self.str): pass
+ if id is not None:
+ self.attributes['id'] = id
+
+ # See if there's a multiplier (e.g., "li*3")
+ multiplier = None
+ for multiplier in re.findall('\*\s*([0-9]+)', self.str): pass
+ if multiplier is not None:
+ self.multiplier = int(multiplier)
+
+ # Populate flag (e.g., ul+)
+ flags = None
+ for flags in re.findall('[\+\!]+$', self.str): pass
+ if flags is not None:
+ if '+' in flags: self.populate = True
+ if '!' in flags: self.expand = True
+
+ def __str__(self):
+ return self.str
+
+ str = ''
+ parser = None
+
+ # For elements
+ # See the properties of `Element` for description on these.
+ name = ''
+ attributes = None
+ multiplier = 1
+ expand = False
+ populate = False
+ text = ''
+ opening_tag = None
+ closing_tag = None
+
+ # Type
+ type = 0
+ ELEMENT = 2
+ CHILD = 4
+ PARENT = 8
+ SIBLING = 16
+
+# ===============================================================================
+
+class Router:
+ """The router.
+ """
+
+ # Constructor
+ # ---------------------------------------------------------------------------
+
+ def __init__(self):
+ pass
+
+ # Methods
+ # ---------------------------------------------------------------------------
+
+ def start(self, options=None, str=None, ret=None):
+ if (options):
+ self.options = Options(router=self, options=options, argv=None)
+ else:
+ self.options = Options(router=self, argv=sys.argv[1:], options=None)
+
+ if (self.options.has('help')):
+ return self.help()
+
+ elif (self.options.has('version')):
+ return self.version()
+
+ else:
+ return self.parse(str=str, ret=ret)
+
+ def help(self):
+ print "Usage: %s [OPTIONS]" % sys.argv[0]
+ print "Expands input into HTML."
+ print ""
+ for short, long, info in self.options.cmdline_keys:
+ if "Deprecated" in info: continue
+ if not short == '': short = '-%s,' % short
+ if not long == '': long = '--%s' % long.replace("=", "=XXX")
+
+ print "%6s %-25s %s" % (short, long, info)
+ print ""
+ print "\n".join(self.help_content)
+
+ def version(self):
+ print "Uhm, yeah."
+
+ def parse(self, str=None, ret=None):
+ self.parser = Parser(self.options)
+
+ try:
+ # Read the files
+ # for line in fileinput.input(): lines.append(line.rstrip(os.linesep))
+ if str is not None:
+ lines = str
+ else:
+ lines = [sys.stdin.read()]
+ lines = " ".join(lines)
+
+ except KeyboardInterrupt:
+ pass
+
+ except:
+ sys.stderr.write("Reading failed.\n")
+ return
+
+ try:
+ self.parser.load_string(lines)
+ output = self.parser.render()
+ if ret: return output
+ sys.stdout.write(output)
+
+ except:
+ sys.stderr.write("Parse error. Check your input.\n")
+ print sys.exc_info()[0]
+ print sys.exc_info()[1]
+
+ def exit(self):
+ sys.exit()
+
+ help_content = [
+ "Please refer to the manual for more information.",
+ ]
+
+# ===============================================================================
+
+class Options:
+ def __init__(self, router, argv, options=None):
+ # Init self
+ self.router = router
+
+ # `options` can be given as a dict of stuff to preload
+ if options:
+ for k, v in options.iteritems():
+ self.options[k] = v
+ return
+
+ # Prepare for getopt()
+ short_keys, long_keys = "", []
+ for short, long, info in self.cmdline_keys: # 'v', 'version'
+ short_keys += short
+ long_keys.append(long)
+
+ try:
+ getoptions, arguments = getopt.getopt(argv, short_keys, long_keys)
+
+ except getopt.GetoptError:
+ err = sys.exc_info()[1]
+ sys.stderr.write("Options error: %s\n" % err)
+ sys.stderr.write("Try --help for a list of arguments.\n")
+ return router.exit()
+
+ # Sort them out into options
+ options = {}
+ i = 0
+ for option in getoptions:
+ key, value = option # '--version', ''
+ if (value == ''): value = True
+
+ # If the key is long, write it
+ if key[0:2] == '--':
+ clean_key = key[2:]
+ options[clean_key] = value
+
+ # If the key is short, look for the long version of it
+ elif key[0:1] == '-':
+ for short, long, info in self.cmdline_keys:
+ if short == key[1:]:
+ print long
+ options[long] = True
+
+ # Done
+ for k, v in options.iteritems():
+ self.options[k] = v
+
+ def __getattr__(self, attr):
+ return self.get(attr)
+
+ def get(self, attr):
+ try: return self.options[attr]
+ except: return None
+
+ def has(self, attr):
+ try: return self.options.has_key(attr)
+ except: return False
+
+ options = {
+ 'indent-spaces': 4
+ }
+ cmdline_keys = [
+ ('h', 'help', 'Shows help'),
+ ('v', 'version', 'Shows the version'),
+ ('', 'no-guides', 'Deprecated'),
+ ('', 'post-tag-guides', 'Adds comments at the end of DIV tags'),
+ ('', 'textmate', 'Adds snippet info (textmate mode)'),
+ ('', 'indent-spaces=', 'Indent spaces'),
+ ('', 'expand-divs', 'Automatically expand divs'),
+ ('', 'no-last-newline', 'Skip the trailing newline'),
+ ('', 'start-guide-format=', 'To be documented'),
+ ('', 'end-guide-format=', 'To be documented'),
+ ]
+
+ # Property: router
+ # Router
+ router = 1
+
+# ===============================================================================
+
+if __name__ == "__main__":
+ z = Router()
+ z.start()
View
13 vim/ftplugin/html/sparkup.vim
@@ -1,23 +1,20 @@
" Sparkup
" Installation:
-" Option 1:
-" add sparkup's vim directory to vim's runtimepath (the location of the
-" sparkup executable will be determined automatically):
-" set rtp+=~/sparkup/vim
+" Copy the contents of vim/ftplugin/ to your ~/.vim/ftplugin directory.
"
-" Option 2:
-" 1. copy the contents of sparkup/vim to your ~/.vim directory.
-" $ cp -r sparkup/vim/* ~/.vim
-" 2. add sparkup/sparkup to your system path.
+" $ mkdir -p ~/.vim/ftplugin && cp -R vim/ftplugin ~/.vim/ftplugin/
"
" Configuration:
" g:sparkup (Default: 'sparkup') -
" Location of the sparkup executable. You shouldn't need to change this
" setting if you used either of the install options above.
+"
" g:sparkupArgs (Default: '--no-last-newline') -
" Additional args passed to sparkup.
+"
" g:sparkupExecuteMapping (Default: '<c-e>') -
" Mapping used to execute sparkup.
+"
" g:sparkupNextMapping (Default: '<c-n>') -
" Mapping used to jump to the next empty tag/attribute.

0 comments on commit 430c17e

Please sign in to comment.
Something went wrong with that request. Please try again.