diff --git a/Commands/Sizzle Expand.tmCommand b/Commands/Sizzle Expand.tmCommand new file mode 100644 index 0000000..efe6ad8 --- /dev/null +++ b/Commands/Sizzle Expand.tmCommand @@ -0,0 +1,36 @@ + + + + + beforeRunningCommand + nop + command + #!/usr/bin/env python +import sys; import os; sys.path.append(os.getenv('TM_BUNDLE_SUPPORT')); import sparkup + +# You may change these options to your liking. +# Those starting with # are comments (disabled). +options = { + 'textmate': True, + 'no-last-newline': True, + #'start-guide-format': 'Begin %s', + #'end-guide-format': 'End %s', +} + +sparkup.Router().start(options=options) + fallbackInput + line + input + selection + keyEquivalent + @e + name + Sizzle Expand + output + insertAsSnippet + scope + text.html + uuid + 73A48D2B-D843-42A1-A288-0D1A6380043B + + diff --git a/Commands/Sparkup Expand.tmCommand b/Commands/Sparkup Expand.tmCommand new file mode 100644 index 0000000..3cacb4e --- /dev/null +++ b/Commands/Sparkup Expand.tmCommand @@ -0,0 +1,16 @@ + + + + + beforeRunningCommand + nop + input + selection + name + Sparkup Expand + output + replaceSelectedText + uuid + EF1F0ADB-1A4D-411A-9BDB-FF9837072CB2 + + diff --git a/Commands/Update Bundle via Git.tmCommand b/Commands/Update Bundle via Git.tmCommand new file mode 100644 index 0000000..9c57988 --- /dev/null +++ b/Commands/Update Bundle via Git.tmCommand @@ -0,0 +1,26 @@ + + + + + beforeRunningCommand + nop + command + cd "$TM_BUNDLE_PATH" + +echo "<pre>" +[ -d ".git" ] && git pull +[ ! -d .git ] && echo "You must install the bundle using Git in order to update via this command. More info: http://github.com/kennethreitz/kCode.tmBundle" + +osascript -e 'tell app "TextMate" to reload bundles' + +echo "</pre>" + input + selection + name + Update Bundle via Git + output + discard + uuid + FC3E137B-6E92-40C9-BAF6-ED13182D7269 + + diff --git a/Macros/Balance Tag.tmMacro b/Macros/Balance Tag.tmMacro new file mode 100644 index 0000000..be01ea9 --- /dev/null +++ b/Macros/Balance Tag.tmMacro @@ -0,0 +1,40 @@ + + + + + commands + + + argument + + beforeRunningCommand + nop + command + python "$TM_BUNDLE_SUPPORT"/balance_tag.py + input + document + keyEquivalent + @d + name + start + output + showAsTooltip + uuid + zen-balance-outward-1 + + command + executeCommandWithOptions: + + + command + findNext: + + + keyEquivalent + @d + name + Balance Tag + uuid + zen-balance-outward-2 + + diff --git a/Macros/Remove Tag.tmMacro b/Macros/Remove Tag.tmMacro new file mode 100644 index 0000000..d359e10 --- /dev/null +++ b/Macros/Remove Tag.tmMacro @@ -0,0 +1,61 @@ + + + + + commands + + + argument + + beforeRunningCommand + nop + command + python "$TM_BUNDLE_SUPPORT"/remove_tag.py + input + document + keyEquivalent + @K + name + start + output + showAsTooltip + uuid + zen-remove-tag-1 + + command + executeCommandWithOptions: + + + command + findNext: + + + argument + + beforeRunningCommand + nop + command + python "$TM_BUNDLE_SUPPORT"/applescript.py + input + document + keyEquivalent + @K + name + start + output + showAsTooltip + uuid + zen-remove-tag-2 + + command + executeCommandWithOptions: + + + keyEquivalent + $@ + name + Remove Tag + uuid + zen-remove-tag-3 + + diff --git a/README b/Readme.mkd similarity index 100% rename from README rename to Readme.mkd diff --git a/Support/applescript.py b/Support/applescript.py new file mode 100644 index 0000000..130987d --- /dev/null +++ b/Support/applescript.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from zen_editor import ZenEditor + +editor = ZenEditor() +editor.run_applescript() \ No newline at end of file diff --git a/Support/balance_tag.py b/Support/balance_tag.py new file mode 100644 index 0000000..cde27ea --- /dev/null +++ b/Support/balance_tag.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from zencoding import zen_core as zen_coding +from zen_editor import ZenEditor + +editor = ZenEditor() +zen_coding.run_action('match_pair', editor) diff --git a/Support/remove_tag.py b/Support/remove_tag.py new file mode 100644 index 0000000..0f30a7b --- /dev/null +++ b/Support/remove_tag.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from zencoding import zen_core as zen_coding +from zen_editor import ZenEditor + +editor = ZenEditor() +zen_coding.run_action('remove_tag', editor) \ No newline at end of file diff --git a/Support/sparkup.py b/Support/sparkup.py new file mode 100644 index 0000000..50de2a8 --- /dev/null +++ b/Support/sparkup.py @@ -0,0 +1,1087 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +version = "0.1.3" + +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': ''}, + 'cc:ie6': { + 'opening_tag': ''}, + 'cc:ie7': { + 'opening_tag': ''}, + 'cc:noie': { + 'opening_tag': '', + 'closing_tag': ''}, + 'html:4t': { + 'expand': True, + 'opening_tag': + '\n' + + '\n' + + '\n' + + ' ' + '\n' + + ' ' + '\n' + + '\n' + + '', + 'closing_tag': + '\n' + + ''}, + 'html:4s': { + 'expand': True, + 'opening_tag': + '\n' + + '\n' + + '\n' + + ' ' + '\n' + + ' ' + '\n' + + '\n' + + '', + 'closing_tag': + '\n' + + ''}, + 'html:xt': { + 'expand': True, + 'opening_tag': + '\n' + + '\n' + + '\n' + + ' ' + '\n' + + ' ' + '\n' + + '\n' + + '', + 'closing_tag': + '\n' + + ''}, + 'html:xs': { + 'expand': True, + 'opening_tag': + '\n' + + '\n' + + '\n' + + ' ' + '\n' + + ' ' + '\n' + + '\n' + + '', + 'closing_tag': + '\n' + + ''}, + 'html:xxs': { + 'expand': True, + 'opening_tag': + '\n' + + '\n' + + '\n' + + ' ' + '\n' + + ' ' + '\n' + + '\n' + + '', + 'closing_tag': + '\n' + + ''}, + 'html:5': { + 'expand': True, + 'opening_tag': + '\n' + + '\n' + + '\n' + + ' ' + '\n' + + ' ' + '\n' + + '\n' + + '', + 'closing_tag': + '\n' + + ''}, + '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'(>$%i]+>\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 `
ul>li
`, the `prefix` is `
`. + 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.,
), + # 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 = "" % 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\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" % (indent, end_guide) + + # Short, self-closing tags (
) + short_tags = self.parser.dialect.short_tags + + # When it should be expanded.. + # (That is,
\n...\n
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 (
) + 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 "" % 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 's have a href, '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() diff --git a/Support/sparkup.pyc b/Support/sparkup.pyc new file mode 100644 index 0000000..c083430 Binary files /dev/null and b/Support/sparkup.pyc differ diff --git a/Support/zen_editor.py b/Support/zen_editor.py new file mode 100644 index 0000000..ba75a5b --- /dev/null +++ b/Support/zen_editor.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +High-level editor interface that communicates with TextMate editor. +In order to work correctly, you should set set the commands +input to “Entire Document” + +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +import os +import sys +import zencoding.zen_core as zen +import subprocess +import re + +class ZenEditor(): + def __init__(self, context=None): + self._content = '' + "Editor's content" + + self.apple_script = os.path.join(os.getenv('TM_BUNDLE_SUPPORT'), 'pasteboard.scpt') + zen.set_newline(os.getenv('TM_LINE_ENDING', zen.get_newline())) + self.set_context(context) + + def _get_selected_text(self): + "Returns selected text" + return os.getenv('TM_SELECTED_TEXT', '') + + def set_context(self, context=None): + """ + Setup underlying editor context. You should call this method + before using any Zen Coding action. + @param context: context object + """ + self._content = sys.stdin.read() + + def get_selection_range(self): + """ + Returns character indexes of selected text + @return: list of start and end indexes + """ + line_num = int(os.getenv('TM_INPUT_START_LINE', os.getenv('TM_LINE_NUMBER', 1))) + head_lines = self.get_content().splitlines(True)[0:line_num - 1] + head_len = len(''.join(head_lines)) + start, end = self.get_current_line_range() + + return start + head_len, end + head_len + + + def create_selection(self, start, end=None): + """ + Creates selection from start to end character + indexes. If end is ommited, this method should place caret + and start index + """ + self.set_caret_pos(start) + if end is not None: + selected_text = self.get_content()[start:end] + # copy selected text to Mac OS' pasteboard to use it + # as a part of macros sequence for 'find next' action + subprocess.Popen(['pbcopy', '-pboard', 'find'], stdin=subprocess.PIPE).communicate(selected_text) + + def get_current_line_range(self): + """ + Returns current line's start and end indexes + @return: list of start and end indexes + @example + start, end = zen_editor.get_current_line_range(); + print('%s, %s' % (start, end)) + """ + start = int(os.getenv('TM_INPUT_START_LINE_INDEX', os.getenv('TM_LINE_INDEX', 0))) + return start, start + len(self._get_selected_text()) + + def get_caret_pos(self): + """ Returns current caret position """ + return self.get_selection_range()[0] + + def set_caret_pos(self, pos): + """ + Set new caret position + @type pos: int + """ + # figure out line and column vars + head = zen.split_by_lines(self.get_content()[0:pos]) + line = max(len(head), 1) + column = pos - len(zen.get_newline().join(head[0:-1])) + + subprocess.Popen(['open', 'txmt://open/?line=%d&column=%d' % (line, column)]).communicate() + + def get_current_line(self): + """ + Returns content of current line + @return: str + """ + return os.getenv('TM_CURRENT_LINE', '') + + def replace_content(self, value, start=None, end=None): + """ + Replace editor's content or it's part (from start to + end index). If value contains + caret_placeholder, the editor will put caret into + this position. If you skip start and end + arguments, the whole target's content will be replaced with + value. + + If you pass start argument only, + the value will be placed at start string + index of current content. + + If you pass start and end arguments, + the corresponding substring of current target's content will be + replaced with value + @param value: Content you want to paste + @type value: str + @param start: Start index of editor's content + @type start: int + @param end: End index of editor's content + @type end: int + """ + # For content replacement we need to use macro syntaxt. + # First, create selection and then write AppleScript file that + # will replace selected text with new one + if start is None: start = 0 + if end is None: end = len(self.get_content()) + self.create_selection(start, end) + + value = self.add_placeholders(value) + + fp = open(self.apple_script, 'w') + fp.write('tell application "TextMate" to insert "%s" with as snippet' % (value.replace('\\', '\\\\').replace('"', '\\"'),)) + fp.close() + + + def get_content(self): + """ + Returns editor's content + @return: str + """ + return self._content + + def get_syntax(self): + """ + Returns current editor's syntax mode + @return: str + """ + scope = os.getenv('TM_SCOPE') + default_type = 'html' + doc_type = None + try: + if 'xsl' in scope: + doc_type = 'xsl' + else: + doc_type = re.findall(r'\bhtml|css|xml|haml\b', scope)[-1] + except: + doc_type = default_type + + if not doc_type: doc_type = default_type + + return doc_type + + def get_profile_name(self): + """ + Returns current output profile name (@see zen_coding#setup_profile) + @return {String} + """ + return 'xhtml' + + def run_applescript(self): + """ + TextMate-specific action that calls AppleScript defined in + replace_content() method which replaces selected text + with new one + """ + if os.path.exists(self.apple_script): + subprocess.Popen(['osascript', self.apple_script], stderr=subprocess.PIPE).communicate() + os.remove(self.apple_script) + + def prompt(self, title): + """ + Prompt user with CocoaDialog + @param title: Popup title + @return: str + """ + args = ['CocoaDialog', 'standard-inputbox', '--title', title, '‑‑no‑newline'] + p = subprocess.Popen(args, stdout=subprocess.PIPE).communicate() + + output = p[0].splitlines() + if output[0] == '2' or not output[1]: + return None + else: + return output[1] + + def add_placeholders(self, text): + _ix = [0] + + def get_ix(m): + _ix[0] += 1 + return '$%s' % _ix[0] + + text = re.sub(r'\$', '\\$', text) + return re.sub(zen.get_caret_placeholder(), get_ix, text) diff --git a/Support/zen_editor.pyc b/Support/zen_editor.pyc new file mode 100644 index 0000000..4c28c1c Binary files /dev/null and b/Support/zen_editor.pyc differ diff --git a/Support/zencoding/__init__.py b/Support/zencoding/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Support/zencoding/__init__.pyc b/Support/zencoding/__init__.pyc new file mode 100644 index 0000000..a68dc8a Binary files /dev/null and b/Support/zencoding/__init__.pyc differ diff --git a/Support/zencoding/filters/__init__.py b/Support/zencoding/filters/__init__.py new file mode 100644 index 0000000..d8153dc --- /dev/null +++ b/Support/zencoding/filters/__init__.py @@ -0,0 +1,23 @@ +import os.path +import sys + +# import all filters +__sub_modules = [] +__prefix = 'zencoding.filters' +__filter_dir = os.path.dirname(__file__) +sys.path.append(__filter_dir) + +filter_map = {} +for file in os.listdir(__filter_dir): + name, ext = os.path.splitext(file) + if ext.lower() == '.py': + __sub_modules.append(name) + +__filters = __import__(__prefix, globals(), locals(), __sub_modules) +for key in dir(__filters): + __module = getattr(__filters, key) + if hasattr(__module, '__name__') and __module.__name__.startswith(__prefix + '.') and hasattr(__module, 'process'): + if hasattr(__module, 'alias'): + filter_map[__module.alias] = __module.process + else: + filter_map[__module.__name__[len(__prefix) + 1:]] = __module.process diff --git a/Support/zencoding/filters/comment.py b/Support/zencoding/filters/comment.py new file mode 100644 index 0000000..3ae0058 --- /dev/null +++ b/Support/zencoding/filters/comment.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Comment important tags (with 'id' and 'class' attributes) +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +from zencoding import zen_core as zen_coding + +alias = 'c' +"Filter name alias (if not defined, ZC will use module name)" + +def add_comments(node, i): + + """ + Add comments to tag + @type node: ZenNode + @type i: int + """ + id_attr = node.get_attribute('id') + class_attr = node.get_attribute('class') + nl = zen_coding.get_newline() + + if id_attr or class_attr: + comment_str = '' + padding = node.parent and node.parent.padding or '' + if id_attr: comment_str += '#' + id_attr + if class_attr: comment_str += '.' + class_attr + + node.start = node.start.replace('<', '' + nl + padding + '<', 1) + node.end = node.end.replace('>', '>' + nl + padding + '', 1) + + # replace counters + node.start = zen_coding.replace_counter(node.start, i + 1) + node.end = zen_coding.replace_counter(node.end, i + 1) + +def process(tree, profile): + if profile['tag_nl'] is False: + return tree + + for i, item in enumerate(tree.children): + if item.is_block(): + add_comments(item, i) + process(item, profile) + + return tree \ No newline at end of file diff --git a/Support/zencoding/filters/escape.py b/Support/zencoding/filters/escape.py new file mode 100644 index 0000000..d2b344b --- /dev/null +++ b/Support/zencoding/filters/escape.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Filter for escaping unsafe XML characters: <, >, & +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +import re + +alias = 'e' +"Filter name alias (if not defined, ZC will use module name)" + +char_map = { + '<': '<', + '>': '>', + '&': '&' +} + +re_chars = re.compile(r'[<>&]') + +def escape_chars(text): + return re_chars.sub(lambda m: char_map[m.group(0)], text) + +def process(tree, profile=None): + for item in tree.children: + item.start = escape_chars(item.start) + item.end = escape_chars(item.end) + + process(item) + + return tree \ No newline at end of file diff --git a/Support/zencoding/filters/format-css.py b/Support/zencoding/filters/format-css.py new file mode 100644 index 0000000..f15dc23 --- /dev/null +++ b/Support/zencoding/filters/format-css.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Format CSS properties: add space after property name: +padding:0; -> padding: 0; +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +import re + +alias = 'fc' +"Filter name alias (if not defined, ZC will use module name)" + +re_css_prop = re.compile(r'([\w\-]+\s*:)\s*') + +def process(tree, profile): + for item in tree.children: + # CSS properties are always snippets + if item.type == 'snippet': + item.start = re_css_prop.sub(r'\1 ', item.start) + + process(item, profile) + + return tree \ No newline at end of file diff --git a/Support/zencoding/filters/format.py b/Support/zencoding/filters/format.py new file mode 100644 index 0000000..5e4f715 --- /dev/null +++ b/Support/zencoding/filters/format.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Generic formatting filter: creates proper indentation for each tree node, +placing "%s" placeholder where the actual output should be. You can use +this filter to preformat tree and then replace %s placeholder to whatever you +need. This filter should't be called directly from editor as a part +of abbreviation. +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +""" +import re +from zencoding import zen_core as zen_coding + +alias = '_format' +"Filter name alias (if not defined, ZC will use module name)" + +child_token = '${child}' +placeholder = '%s' + +def get_newline(): + return zen_coding.get_newline() + + +def get_indentation(): + return zen_coding.get_indentation() + +def has_block_sibling(item): + """ + Test if passed node has block-level sibling element + @type item: ZenNode + @return: bool + """ + return item.parent and item.parent.has_block_children() + +def is_very_first_child(item): + """ + Test if passed itrem is very first child of the whole tree + @type tree: ZenNode + """ + return item.parent and not item.parent.parent and not item.previous_sibling + +def should_break_line(node, profile): + """ + Need to add line break before element + @type node: ZenNode + @type profile: dict + @return: bool + """ + if not profile['inline_break']: + return False + + # find toppest non-inline sibling + while node.previous_sibling and node.previous_sibling.is_inline(): + node = node.previous_sibling + + if not node.is_inline(): + return False + + # calculate how many inline siblings we have + node_count = 1 + node = node.next_sibling + while node: + if node.is_inline(): + node_count += 1 + else: + break + node = node.next_sibling + + return node_count >= profile['inline_break'] + +def should_break_child(node, profile): + """ + Need to add newline because item has too many inline children + @type node: ZenNode + @type profile: dict + @return: bool + """ + # we need to test only one child element, because + # has_block_children() method will do the rest + return node.children and should_break_line(node.children[0], profile) + +def process_snippet(item, profile, level=0): + """ + Processes element with snippet type + @type item: ZenNode + @type profile: dict + @param level: Depth level + @type level: int + """ + data = item.source.value; + + if not data: + # snippet wasn't found, process it as tag + return process_tag(item, profile, level) + + item.start = placeholder + item.end = placeholder + + padding = item.parent.padding if item.parent else get_indentation() * level + + if not is_very_first_child(item): + item.start = get_newline() + padding + item.start + + # adjust item formatting according to last line of start property + parts = data.split(child_token) + lines = zen_coding.split_by_lines(parts[0] or '') + padding_delta = get_indentation() + + if len(lines) > 1: + m = re.match(r'^(\s+)', lines[-1]) + if m: + padding_delta = m.group(1) + + item.padding = padding + padding_delta + + return item + +def process_tag(item, profile, level=0): + """ + Processes element with tag type + @type item: ZenNode + @type profile: dict + @param level: Depth level + @type level: int + """ + if not item.name: + # looks like it's a root element + return item + + item.start = placeholder + item.end = placeholder + + is_unary = item.is_unary() and not item.children + + # formatting output + if profile['tag_nl'] is not False: + padding = item.parent.padding if item.parent else get_indentation() * level + force_nl = profile['tag_nl'] is True + should_break = should_break_line(item, profile) + + # formatting block-level elements + if ((item.is_block() or should_break) and item.parent) or force_nl: + # snippet children should take different formatting + if not item.parent or (item.parent.type != 'snippet' and not is_very_first_child(item)): + item.start = get_newline() + padding + item.start + + if item.has_block_children() or should_break_child(item, profile) or (force_nl and not is_unary): + item.end = get_newline() + padding + item.end + + if item.has_tags_in_content() or (force_nl and not item.has_children() and not is_unary): + item.start += get_newline() + padding + get_indentation() + + elif item.is_inline() and has_block_sibling(item) and not is_very_first_child(item): + item.start = get_newline() + padding + item.start + + item.padding = padding + get_indentation() + + return item + +def process(tree, profile, level=0): + """ + Processes simplified tree, making it suitable for output as HTML structure + @type item: ZenNode + @type profile: dict + @param level: Depth level + @type level: int + """ + + for item in tree.children: + if item.type == 'tag': + item = process_tag(item, profile, level) + else: + item = process_snippet(item, profile, level) + + if item.content: + item.content = zen_coding.pad_string(item.content, item.padding) + + process(item, profile, level + 1) + + return tree \ No newline at end of file diff --git a/Support/zencoding/filters/haml.py b/Support/zencoding/filters/haml.py new file mode 100644 index 0000000..cddef1e --- /dev/null +++ b/Support/zencoding/filters/haml.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Filter that produces HAML tree +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +from zencoding import zen_core as zen_coding + +child_token = '${child}' + +def make_attributes_string(tag, profile): + """ + Creates HTML attributes string from tag according to profile settings + @type tag: ZenNode + @type profile: dict + """ + # make attribute string + attrs = '' + attr_quote = profile['attr_quotes'] == 'single' and "'" or '"' + cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or '' + + # use short notation for ID and CLASS attributes + for a in tag.attributes: + name_lower = a['name'].lower() + if name_lower == 'id': + attrs += '#' + (a['value'] or cursor) + elif name_lower == 'class': + attrs += '.' + (a['value'] or cursor) + + other_attrs = [] + + # process other attributes + for a in tag.attributes: + name_lower = a['name'].lower() + if name_lower != 'id' and name_lower != 'class': + attr_name = profile['attr_case'] == 'upper' and a['name'].upper() or name_lower + other_attrs.append(':' + attr_name + ' => ' + attr_quote + (a['value'] or cursor) + attr_quote) + + if other_attrs: + attrs += '{' + ', '.join(other_attrs) + '}' + + return attrs + +def _replace(placeholder, value): + if placeholder: + return placeholder % value + else: + return value + +def process_snippet(item, profile, level=0): + """ + Processes element with snippet type + @type item: ZenNode + @type profile: dict + @type level: int + """ + data = item.source.value + + if not data: + # snippet wasn't found, process it as tag + return process_tag(item, profile, level) + + tokens = data.split(child_token) + if len(tokens) < 2: + start = tokens[0] + end = '' + else: + start, end = tokens + + padding = item.parent and item.parent.padding or '' + + item.start = _replace(item.start, zen_coding.pad_string(start, padding)) + item.end = _replace(item.end, zen_coding.pad_string(end, padding)) + + return item + +def has_block_sibling(item): + """ + Test if passed node has block-level sibling element + @type item: ZenNode + @return: bool + """ + return item.parent and item.parent.has_block_children() + +def process_tag(item, profile, level=0): + """ + Processes element with tag type + @type item: ZenNode + @type profile: dict + @type level: int + """ + if not item.name: + # looks like it's root element + return item + + attrs = make_attributes_string(item, profile) + cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or '' + self_closing = '' + is_unary = item.is_unary() and not item.children + + if profile['self_closing_tag'] and is_unary: + self_closing = '/' + + # define tag name + tag_name = '%' + (profile['tag_case'] == 'upper' and item.name.upper() or item.name.lower()) + + if tag_name.lower() == '%div' and '{' not in attrs: + # omit div tag + tag_name = '' + + item.end = '' + item.start = _replace(item.start, tag_name + attrs + self_closing) + + if not item.children and not is_unary: + item.start += cursor + + return item + +def process(tree, profile, level=0): + """ + Processes simplified tree, making it suitable for output as HTML structure + @type tree: ZenNode + @type profile: dict + @type level: int + """ + if level == 0: + # preformat tree + tree = zen_coding.run_filters(tree, profile, '_format') + + for i, item in enumerate(tree.children): + if item.type == 'tag': + process_tag(item, profile, level) + else: + process_snippet(item, profile, level) + + # replace counters + item.start = zen_coding.replace_counter(item.start, i + 1) + item.end = zen_coding.replace_counter(item.end, i + 1) + process(item, profile, level + 1) + + return tree \ No newline at end of file diff --git a/Support/zencoding/filters/html.py b/Support/zencoding/filters/html.py new file mode 100644 index 0000000..48a7127 --- /dev/null +++ b/Support/zencoding/filters/html.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Filter that produces HTML tree +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +from zencoding import zen_core as zen_coding + +child_token = '${child}' + +def make_attributes_string(tag, profile): + """ + Creates HTML attributes string from tag according to profile settings + @type tag: ZenNode + @type profile: dict + """ + # make attribute string + attrs = '' + attr_quote = profile['attr_quotes'] == 'single' and "'" or '"' + cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or '' + + # process other attributes + for a in tag.attributes: + attr_name = profile['attr_case'] == 'upper' and a['name'].upper() or a['name'].lower() + attrs += ' ' + attr_name + '=' + attr_quote + (a['value'] or cursor) + attr_quote + + return attrs + +def _replace(placeholder, value): + if placeholder: + return placeholder % value + else: + return value + +def process_snippet(item, profile, level): + """ + Processes element with snippet type + @type item: ZenNode + @type profile: dict + @type level: int + """ + data = item.source.value; + + if not data: + # snippet wasn't found, process it as tag + return process_tag(item, profile, level) + + tokens = data.split(child_token) + if len(tokens) < 2: + start = tokens[0] + end = '' + else: + start, end = tokens + + padding = item.parent and item.parent.padding or '' + + item.start = _replace(item.start, zen_coding.pad_string(start, padding)) + item.end = _replace(item.end, zen_coding.pad_string(end, padding)) + + return item + + +def has_block_sibling(item): + """ + Test if passed node has block-level sibling element + @type item: ZenNode + @return: bool + """ + return item.parent and item.parent.has_block_children() + +def process_tag(item, profile, level): + """ + Processes element with tag type + @type item: ZenNode + @type profile: dict + @type level: int + """ + if not item.name: + # looks like it's root element + return item + + attrs = make_attributes_string(item, profile) + cursor = profile['place_cursor'] and zen_coding.get_caret_placeholder() or '' + self_closing = '' + is_unary = item.is_unary() and not item.children + start= '' + end = '' + + if profile['self_closing_tag'] == 'xhtml': + self_closing = ' /' + elif profile['self_closing_tag'] is True: + self_closing = '/' + + # define opening and closing tags + tag_name = profile['tag_case'] == 'upper' and item.name.upper() or item.name.lower() + if is_unary: + start = '<' + tag_name + attrs + self_closing + '>' + item.end = '' + else: + start = '<' + tag_name + attrs + '>' + end = '' + + item.start = _replace(item.start, start) + item.end = _replace(item.end, end) + + if not item.children and not is_unary: + item.start += cursor + + return item + +def process(tree, profile, level=0): + """ + Processes simplified tree, making it suitable for output as HTML structure + @type tree: ZenNode + @type profile: dict + @type level: int + """ + if level == 0: + # preformat tree + tree = zen_coding.run_filters(tree, profile, '_format') + + for i, item in enumerate(tree.children): + if item.type == 'tag': + process_tag(item, profile, level) + else: + process_snippet(item, profile, level) + + # replace counters + item.start = zen_coding.replace_counter(item.start, i + 1) + item.end = zen_coding.replace_counter(item.end, i + 1) + process(item, profile, level + 1) + + return tree diff --git a/Support/zencoding/filters/xsl.py b/Support/zencoding/filters/xsl.py new file mode 100644 index 0000000..b6e84f3 --- /dev/null +++ b/Support/zencoding/filters/xsl.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Filter for trimming "select" attributes from some tags that contains +child elements +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +import re + +tags = { + 'xsl:variable': 1, + 'xsl:with-param': 1 +} + +re_attr = re.compile(r'\s+select\s*=\s*([\'"]).*?\1') + +def trim_attribute(node): + """ + Removes "select" attribute from node + @type node: ZenNode + """ + node.start = re_attr.sub('', node.start) + +def process(tree, profile): + for item in tree.children: + if item.type == 'tag' and item.name.lower() in tags and item.children: + trim_attribute(item) + + process(item, profile) \ No newline at end of file diff --git a/Support/zencoding/html_matcher.py b/Support/zencoding/html_matcher.py new file mode 100644 index 0000000..4d336b4 --- /dev/null +++ b/Support/zencoding/html_matcher.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +Context-independent xHTML pair matcher +Use method match(html, start_ix) to find matching pair. +If pair was found, this function returns a list of indexes where tag pair +starts and ends. If pair wasn't found, None will be returned. + +The last matched (or unmatched) result is saved in last_match +dictionary for later use. + +@author: Sergey Chikuyonok (serge.che@gmail.com) +''' +import re + +start_tag = r'<([\w\:\-]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*)\s*(\/?)>' +end_tag = r'<\/([\w\:\-]+)[^>]*>' +attr = r'([\w\-:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?' + +"Last matched HTML pair" +last_match = { + 'opening_tag': None, # Tag() or Comment() object + 'closing_tag': None, # Tag() or Comment() object + 'start_ix': -1, + 'end_ix': -1 +} + +cur_mode = 'xhtml' +"Current matching mode" + +def set_mode(new_mode): + global cur_mode + if new_mode != 'html': new_mode = 'xhtml' + cur_mode = new_mode + +def make_map(elems): + """ + Create dictionary of elements for faster searching + @param elems: Elements, separated by comma + @type elems: str + """ + obj = {} + for elem in elems.split(','): + obj[elem] = True + + return obj + +# Empty Elements - HTML 4.01 +empty = make_map("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"); + +# Block Elements - HTML 4.01 +block = make_map("address,applet,blockquote,button,center,dd,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul"); + +# Inline Elements - HTML 4.01 +inline = make_map("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); + +# Elements that you can, intentionally, leave open +# (and which close themselves) +close_self = make_map("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); + +# Attributes that have their values filled in disabled="disabled" +fill_attrs = make_map("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); + +#Special Elements (can contain anything) +# serge.che: parsing data inside elements is a "feature" +special = make_map("style"); + +class Tag(): + """Matched tag""" + def __init__(self, match, ix): + """ + @type match: MatchObject + @param match: Matched HTML tag + @type ix: int + @param ix: Tag's position + """ + global cur_mode + + name = match.group(1).lower() + self.name = name + self.full_tag = match.group(0) + self.start = ix + self.end = ix + len(self.full_tag) + self.unary = ( len(match.groups()) > 2 and bool(match.group(3)) ) or (name in empty and cur_mode == 'html') + self.type = 'tag' + self.close_self = (name in close_self and cur_mode == 'html') + +class Comment(): + "Matched comment" + def __init__(self, start, end): + self.start = start + self.end = end + self.type = 'comment' + +def make_range(opening_tag=None, closing_tag=None, ix=0): + """ + Makes selection ranges for matched tag pair + @type opening_tag: Tag + @type closing_tag: Tag + @type ix: int + @return list + """ + start_ix, end_ix = -1, -1 + + if opening_tag and not closing_tag: # unary element + start_ix = opening_tag.start + end_ix = opening_tag.end + elif opening_tag and closing_tag: # complete element + if (opening_tag.start < ix and opening_tag.end > ix) or (closing_tag.start <= ix and closing_tag.end > ix): + start_ix = opening_tag.start + end_ix = closing_tag.end; + else: + start_ix = opening_tag.end + end_ix = closing_tag.start + + return start_ix, end_ix + +def save_match(opening_tag=None, closing_tag=None, ix=0): + """ + Save matched tag for later use and return found indexes + @type opening_tag: Tag + @type closing_tag: Tag + @type ix: int + @return list + """ + last_match['opening_tag'] = opening_tag; + last_match['closing_tag'] = closing_tag; + + last_match['start_ix'], last_match['end_ix'] = make_range(opening_tag, closing_tag, ix) + + return last_match['start_ix'] != -1 and (last_match['start_ix'], last_match['end_ix']) or (None, None) + +def match(html, start_ix, mode='xhtml'): + """ + Search for matching tags in html, starting from + start_ix position. The result is automatically saved + in last_match property + """ + return _find_pair(html, start_ix, mode, save_match) + +def find(html, start_ix, mode='xhtml'): + """ + Search for matching tags in html, starting from + start_ix position. + """ + return _find_pair(html, start_ix, mode) + +def get_tags(html, start_ix, mode='xhtml'): + """ + Search for matching tags in html, starting from + start_ix position. The difference between + match function itself is that get_tags + method doesn't save matched result in last_match property + and returns array of opening and closing tags + This method is generally used for lookups + """ + return _find_pair(html, start_ix, mode, lambda op, cl=None, ix=0: (op, cl) if op and op.type == 'tag' else None) + + +def _find_pair(html, start_ix, mode='xhtml', action=make_range): + """ + Search for matching tags in html, starting from + start_ix position + + @param html: Code to search + @type html: str + + @param start_ix: Character index where to start searching pair + (commonly, current caret position) + @type start_ix: int + + @param action: Function that creates selection range + @type action: function + + @return: list + """ + + forward_stack = [] + backward_stack = [] + opening_tag = None + closing_tag = None + html_len = len(html) + + set_mode(mode) + + def has_match(substr, start=None): + if start is None: + start = ix + + return html.find(substr, start) == start + + + def find_comment_start(start_pos): + while start_pos: + if html[start_pos] == '<' and has_match('') + ix + 3; + if ix < start_ix and end_ix >= start_ix: + return action(Comment(ix, end_ix)) + elif ch == '-' and has_match('-->'): # found comment end + # search left until comment start is reached + ix = find_comment_start(ix) + + ix -= 1 + + if not opening_tag: + return action(None) + + # find closing tag + if not closing_tag: + ix = start_ix + while ix < html_len: + ch = html[ix] + if ch == '<': + check_str = html[ix:] + m = re.match(start_tag, check_str) + if m: # found opening tag + tmp_tag = Tag(m, ix); + if not tmp_tag.unary: + forward_stack.append(tmp_tag) + else: + m = re.match(end_tag, check_str) + if m: #found closing tag + tmp_tag = Tag(m, ix); + if forward_stack and forward_stack[-1].name == tmp_tag.name: + forward_stack.pop() + else: # found matched closing tag + closing_tag = tmp_tag; + break + elif has_match('') + 3 + continue + elif ch == '-' and has_match('-->'): + # looks like cursor was inside comment with invalid HTML + if not forward_stack or forward_stack[-1].type != 'comment': + end_ix = ix + 3 + return action(Comment( find_comment_start(ix), end_ix )) + + ix += 1 + + return action(opening_tag, closing_tag, start_ix) \ No newline at end of file diff --git a/Support/zencoding/html_matcher.pyc b/Support/zencoding/html_matcher.pyc new file mode 100644 index 0000000..70eadfe Binary files /dev/null and b/Support/zencoding/html_matcher.pyc differ diff --git a/Support/zencoding/my_zen_settings.py b/Support/zencoding/my_zen_settings.py new file mode 100644 index 0000000..30814e2 --- /dev/null +++ b/Support/zencoding/my_zen_settings.py @@ -0,0 +1,8 @@ +my_zen_settings = { + 'html': { + 'abbreviations': { + 'jq': '', + 'demo': '
' + } + } +} \ No newline at end of file diff --git a/Support/zencoding/stparser.py b/Support/zencoding/stparser.py new file mode 100644 index 0000000..75d81bb --- /dev/null +++ b/Support/zencoding/stparser.py @@ -0,0 +1,161 @@ +''' +Zen Coding's settings parser +Created on Jun 14, 2009 + +@author: sergey +''' +from copy import deepcopy + +import re +import types +from zen_settings import zen_settings + +_original_settings = deepcopy(zen_settings) + +TYPE_ABBREVIATION = 'zen-tag', +TYPE_EXPANDO = 'zen-expando', +TYPE_REFERENCE = 'zen-reference'; +""" Reference to another abbreviation or tag """ + +re_tag = r'^<([\w\-]+(?:\:[\w\-]+)?)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*)\s*(\/?)>' +"Regular expression for XML tag matching" + +re_attrs = r'([\w\-]+)\s*=\s*([\'"])(.*?)\2' +"Regular expression for matching XML attributes" + +class Entry: + """ + Unified object for parsed data + """ + def __init__(self, entry_type, key, value): + """ + @type entry_type: str + @type key: str + @type value: dict + """ + self.type = entry_type + self.key = key + self.value = value + +def _make_expando(key, value): + """ + Make expando from string + @type key: str + @type value: str + @return: Entry + """ + return Entry(TYPE_EXPANDO, key, value) + +def _make_abbreviation(key, tag_name, attrs, is_empty=False): + """ + Make abbreviation from string + @param key: Abbreviation key + @type key: str + @param tag_name: Expanded element's tag name + @type tag_name: str + @param attrs: Expanded element's attributes + @type attrs: str + @param is_empty: Is expanded element empty or not + @type is_empty: bool + @return: dict + """ + result = { + 'name': tag_name, + 'is_empty': is_empty + }; + + if attrs: + result['attributes'] = []; + for m in re.findall(re_attrs, attrs): + result['attributes'].append({ + 'name': m[0], + 'value': m[2] + }) + + return Entry(TYPE_ABBREVIATION, key, result) + +def _parse_abbreviations(obj): + """ + Parses all abbreviations inside dictionary + @param obj: dict + """ + for key, value in obj.items(): + key = key.strip() + if key[-1] == '+': +# this is expando, leave 'value' as is + obj[key] = _make_expando(key, value) + else: + m = re.search(re_tag, value) + if m: + obj[key] = _make_abbreviation(key, m.group(1), m.group(2), (m.group(3) == '/')) + else: +# assume it's reference to another abbreviation + obj[key] = Entry(TYPE_REFERENCE, key, value) + +def parse(settings): + """ + Parse user's settings. This function must be called *before* any activity + in zen coding (for example, expanding abbreviation) + @type settings: dict + """ + for p, value in settings.items(): + if p == 'abbreviations': + _parse_abbreviations(value) + elif p == 'extends': + settings[p] = [v.strip() for v in value.split(',')] + elif type(value) == types.DictType: + parse(value) + + +def extend(parent, child): + """ + Recursevly extends parent dictionary with children's keys. Used for merging + default settings with user's + @type parent: dict + @type child: dict + """ + for p, value in child.items(): + if type(value) == types.DictType: + if p not in parent: + parent[p] = {} + extend(parent[p], value) + else: + parent[p] = value + + + +def create_maps(obj): + """ + Create hash maps on certain string properties of zen settings + @type obj: dict + """ + for p, value in obj.items(): + if p == 'element_types': + for k, v in value.items(): + if isinstance(v, str): + value[k] = [el.strip() for el in v.split(',')] + elif type(value) == types.DictType: + create_maps(value) + + +if __name__ == '__main__': + pass + +def get_settings(user_settings=None): + """ + Main function that gather all settings and returns parsed dictionary + @param user_settings: A dictionary of user-defined settings + """ + settings = deepcopy(_original_settings) + create_maps(settings) + + if user_settings: + user_settings = deepcopy(user_settings) + create_maps(user_settings) + extend(settings, user_settings) + + # now we need to parse final set of settings + parse(settings) + + return settings + \ No newline at end of file diff --git a/Support/zencoding/stparser.pyc b/Support/zencoding/stparser.pyc new file mode 100644 index 0000000..c3d6ac9 Binary files /dev/null and b/Support/zencoding/stparser.pyc differ diff --git a/Support/zencoding/zen_actions.py b/Support/zencoding/zen_actions.py new file mode 100644 index 0000000..6fd1727 --- /dev/null +++ b/Support/zencoding/zen_actions.py @@ -0,0 +1,645 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Middleware layer that communicates between editor and Zen Coding. +This layer describes all available Zen Coding actions, like +"Expand Abbreviation". +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +""" +from zencoding import zen_core as zen_coding +from zencoding import html_matcher +import re + +def find_abbreviation(editor): + """ + Search for abbreviation in editor from current caret position + @param editor: Editor instance + @type editor: ZenEditor + @return: str + """ + start, end = editor.get_selection_range() + if start != end: + # abbreviation is selected by user + return editor.get_content()[start, end]; + + # search for new abbreviation from current caret position + cur_line_start, cur_line_end = editor.get_current_line_range() + return zen_coding.extract_abbreviation(editor.get_content()[cur_line_start:start]) + +def expand_abbreviation(editor, syntax=None, profile_name=None): + """ + Find from current caret position and expand abbreviation in editor + @param editor: Editor instance + @type editor: ZenEditor + @param syntax: Syntax type (html, css, etc.) + @type syntax: str + @param profile_name: Output profile name (html, xml, xhtml) + @type profile_name: str + @return: True if abbreviation was expanded successfully + """ + if syntax is None: syntax = editor.get_syntax() + if profile_name is None: profile_name = editor.get_profile_name() + + range_start, caret_pos = editor.get_selection_range() + abbr = find_abbreviation(editor) + content = '' + + if abbr: + content = zen_coding.expand_abbreviation(abbr, syntax, profile_name) + if content: + editor.replace_content(content, caret_pos - len(abbr), caret_pos) + return True + + return False + +def expand_abbreviation_with_tab(editor, syntax, profile_name='xhtml'): + """ + A special version of expandAbbreviation function: if it can't + find abbreviation, it will place Tab character at caret position + @param editor: Editor instance + @type editor: ZenEditor + @param syntax: Syntax type (html, css, etc.) + @type syntax: str + @param profile_name: Output profile name (html, xml, xhtml) + @type profile_name: str + """ + if not expand_abbreviation(editor, syntax, profile_name): + editor.replace_content(zen_coding.get_variable('indentation'), editor.get_caret_pos()) + + return True + +def match_pair(editor, direction='out', syntax=None): + """ + Find and select HTML tag pair + @param editor: Editor instance + @type editor: ZenEditor + @param direction: Direction of pair matching: 'in' or 'out'. + @type direction: str + """ + direction = direction.lower() + if syntax is None: syntax = editor.get_profile_name() + + range_start, range_end = editor.get_selection_range() + cursor = range_end + content = editor.get_content() + rng = None + + old_open_tag = html_matcher.last_match['opening_tag'] + old_close_tag = html_matcher.last_match['closing_tag'] + + if direction == 'in' and old_open_tag and range_start != range_end: +# user has previously selected tag and wants to move inward + if not old_close_tag: +# unary tag was selected, can't move inward + return False + elif old_open_tag.start == range_start: + if content[old_open_tag.end] == '<': +# test if the first inward tag matches the entire parent tag's content + _r = html_matcher.find(content, old_open_tag.end + 1, syntax) + if _r[0] == old_open_tag.end and _r[1] == old_close_tag.start: + rng = html_matcher.match(content, old_open_tag.end + 1, syntax) + else: + rng = (old_open_tag.end, old_close_tag.start) + else: + rng = (old_open_tag.end, old_close_tag.start) + else: + new_cursor = content[0:old_close_tag.start].find('<', old_open_tag.end) + search_pos = new_cursor + 1 if new_cursor != -1 else old_open_tag.end + rng = html_matcher.match(content, search_pos, syntax) + else: + rng = html_matcher.match(content, cursor, syntax) + + if rng and rng[0] is not None: + editor.create_selection(rng[0], rng[1]) + return True + else: + return False + +def match_pair_inward(editor): + return match_pair(editor, 'in') + +def match_pair_outward(editor): + return match_pair(editor, 'out') + +def narrow_to_non_space(text, start, end): + """ + Narrow down text indexes, adjusting selection to non-space characters + @type text: str + @type start: int + @type end: int + @return: list + """ + # narrow down selection until first non-space character + while start < end: + if not text[start].isspace(): + break + + start += 1 + + while end > start: + end -= 1 + if not text[end].isspace(): + end += 1 + break + + return start, end + +def wrap_with_abbreviation(editor, abbr, syntax=None, profile_name=None): + """ + Wraps content with abbreviation + @param editor: Editor instance + @type editor: ZenEditor + @param syntax: Syntax type (html, css, etc.) + @type syntax: str + @param profile_name: Output profile name (html, xml, xhtml) + @type profile_name: str + """ + if not abbr: return None + + if syntax is None: syntax = editor.get_syntax() + if profile_name is None: profile_name = editor.get_profile_name() + + start_offset, end_offset = editor.get_selection_range() + content = editor.get_content() + + if start_offset == end_offset: + # no selection, find tag pair + rng = html_matcher.match(content, start_offset, profile_name) + + if rng[0] is None: # nothing to wrap + return None + else: + start_offset, end_offset = rng + + start_offset, end_offset = narrow_to_non_space(content, start_offset, end_offset) + line_bounds = get_line_bounds(content, start_offset) + padding = get_line_padding(content[line_bounds[0]:line_bounds[1]]) + + new_content = content[start_offset:end_offset] + result = zen_coding.wrap_with_abbreviation(abbr, unindent_text(new_content, padding), syntax, profile_name) + + if result: + editor.replace_content(result, start_offset, end_offset) + return True + + return False + +def unindent(editor, text): + """ + Unindent content, thus preparing text for tag wrapping + @param editor: Editor instance + @type editor: ZenEditor + @param text: str + @return str + """ + return unindent_text(text, get_current_line_padding(editor)) + +def unindent_text(text, pad): + """ + Removes padding at the beginning of each text's line + @type text: str + @type pad: str + """ + lines = zen_coding.split_by_lines(text) + + for i,line in enumerate(lines): + if line.startswith(pad): + lines[i] = line[len(pad):] + + return zen_coding.get_newline().join(lines) + +def get_current_line_padding(editor): + """ + Returns padding of current editor's line + @return str + """ + return get_line_padding(editor.get_current_line()) + +def get_line_padding(line): + """ + Returns padding of current editor's line + @return str + """ + m = re.match(r'^(\s+)', line) + return m and m.group(0) or '' + +def find_new_edit_point(editor, inc=1, offset=0): + """ + Search for new caret insertion point + @param editor: Editor instance + @type editor: ZenEditor + @param inc: Search increment: -1 — search left, 1 — search right + @param offset: Initial offset relative to current caret position + @return: -1 if insertion point wasn't found + """ + cur_point = editor.get_caret_pos() + offset + content = editor.get_content() + max_len = len(content) + next_point = -1 + re_empty_line = r'^\s+$' + + def get_line(ix): + start = ix + while start >= 0: + c = content[start] + if c == '\n' or c == '\r': break + start -= 1 + + return content[start:ix] + + while cur_point < max_len and cur_point > 0: + cur_point += inc + cur_char = content[cur_point] + next_char = content[cur_point + 1] + prev_char = content[cur_point - 1] + + if cur_char in '"\'': + if next_char == cur_char and prev_char == '=': + # empty attribute + next_point = cur_point + 1 + elif cur_char == '>' and next_char == '<': + # between tags + next_point = cur_point + 1 + elif cur_char in '\r\n': + # empty line + if re.search(re_empty_line, get_line(cur_point - 1)): + next_point = cur_point + + if next_point != -1: break + + return next_point + +def prev_edit_point(editor): + """ + Move caret to previous edit point + @param editor: Editor instance + @type editor: ZenEditor + """ + cur_pos = editor.get_caret_pos() + new_point = find_new_edit_point(editor, -1) + + if new_point == cur_pos: + # we're still in the same point, try searching from the other place + new_point = find_new_edit_point(editor, -1, -2) + + if new_point != -1: + editor.set_caret_pos(new_point) + return True + + return False + +def next_edit_point(editor): + """ + Move caret to next edit point + @param editor: Editor instance + @type editor: ZenEditor + """ + new_point = find_new_edit_point(editor, 1) + if new_point != -1: + editor.set_caret_pos(new_point) + return True + + return False + +def insert_formatted_newline(editor, mode='html'): + """ + Inserts newline character with proper indentation + @param editor: Editor instance + @type editor: ZenEditor + @param mode: Syntax mode (only 'html' is implemented) + @type mode: str + """ + caret_pos = editor.get_caret_pos() + nl = zen_coding.get_newline() + pad = zen_coding.get_variable('indentation') + + if mode == 'html': + # let's see if we're breaking newly created tag + pair = html_matcher.get_tags(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name()) + + if pair[0] and pair[1] and pair[0]['type'] == 'tag' and pair[0]['end'] == caret_pos and pair[1]['start'] == caret_pos: + editor.replace_content(nl + pad + zen_coding.get_caret_placeholder() + nl, caret_pos) + else: + editor.replace_content(nl, caret_pos) + else: + editor.replace_content(nl, caret_pos) + + return True + +def select_line(editor): + """ + Select line under cursor + @param editor: Editor instance + @type editor: ZenEditor + """ + start, end = editor.get_current_line_range(); + editor.create_selection(start, end) + return True + +def go_to_matching_pair(editor): + """ + Moves caret to matching opening or closing tag + @param editor: Editor instance + @type editor: ZenEditor + """ + content = editor.get_content() + caret_pos = editor.get_caret_pos() + + if content[caret_pos] == '<': + # looks like caret is outside of tag pair + caret_pos += 1 + + tags = html_matcher.get_tags(content, caret_pos, editor.get_profile_name()) + + if tags and tags[0]: + # match found + open_tag, close_tag = tags + + if close_tag: # exclude unary tags + if open_tag['start'] <= caret_pos and open_tag['end'] >= caret_pos: + editor.set_caret_pos(close_tag['start']) + elif close_tag['start'] <= caret_pos and close_tag['end'] >= caret_pos: + editor.set_caret_pos(open_tag['start']) + + return True + + return False + + +def merge_lines(editor): + """ + Merge lines spanned by user selection. If there's no selection, tries to find + matching tags and use them as selection + @param editor: Editor instance + @type editor: ZenEditor + """ + start, end = editor.get_selection_range() + if start == end: + # find matching tag + pair = html_matcher.match(editor.get_content(), editor.get_caret_pos(), editor.get_profile_name()) + if pair and pair[0] is not None: + start, end = pair + + if start != end: + # got range, merge lines + text = editor.get_content()[start:end] + lines = map(lambda s: re.sub(r'^\s+', '', s), zen_coding.split_by_lines(text)) + text = re.sub(r'\s{2,}', ' ', ''.join(lines)) + editor.replace_content(text, start, end) + editor.create_selection(start, start + len(text)) + return True + + return False + +def toggle_comment(editor): + """ + Toggle comment on current editor's selection or HTML tag/CSS rule + @type editor: ZenEditor + """ + syntax = editor.get_syntax() + if syntax == 'css': + return toggle_css_comment(editor) + else: + return toggle_html_comment(editor) + +def toggle_html_comment(editor): + """ + Toggle HTML comment on current selection or tag + @type editor: ZenEditor + @return: True if comment was toggled + """ + start, end = editor.get_selection_range() + content = editor.get_content() + + if start == end: + # no selection, find matching tag + pair = html_matcher.get_tags(content, editor.get_caret_pos(), editor.get_profile_name()) + if pair and pair[0]: # found pair + start = pair[0].start + end = pair[1] and pair[1].end or pair[0].end + + return generic_comment_toggle(editor, '', start, end) + +def toggle_css_comment(editor): + """ + Simple CSS commenting + @type editor: ZenEditor + @return: True if comment was toggled + """ + start, end = editor.get_selection_range() + + if start == end: + # no selection, get current line + start, end = editor.get_current_line_range() + + # adjust start index till first non-space character + start, end = narrow_to_non_space(editor.get_content(), start, end) + + return generic_comment_toggle(editor, '/*', '*/', start, end) + +def search_comment(text, pos, start_token, end_token): + """ + Search for nearest comment in str, starting from index from + @param text: Where to search + @type text: str + @param pos: Search start index + @type pos: int + @param start_token: Comment start string + @type start_token: str + @param end_token: Comment end string + @type end_token: str + @return: None if comment wasn't found, list otherwise + """ + start_ch = start_token[0] + end_ch = end_token[0] + comment_start = -1 + comment_end = -1 + + def has_match(tx, start): + return text[start:start + len(tx)] == tx + + + # search for comment start + while pos: + pos -= 1 + if text[pos] == start_ch and has_match(start_token, pos): + comment_start = pos + break + + if comment_start != -1: + # search for comment end + pos = comment_start + content_len = len(text) + while content_len >= pos: + pos += 1 + if text[pos] == end_ch and has_match(end_token, pos): + comment_end = pos + len(end_token) + break + + if comment_start != -1 and comment_end != -1: + return comment_start, comment_end + else: + return None + +def generic_comment_toggle(editor, comment_start, comment_end, range_start, range_end): + """ + Generic comment toggling routine + @type editor: ZenEditor + @param comment_start: Comment start token + @type comment_start: str + @param comment_end: Comment end token + @type comment_end: str + @param range_start: Start selection range + @type range_start: int + @param range_end: End selection range + @type range_end: int + @return: bool + """ + content = editor.get_content() + caret_pos = [editor.get_caret_pos()] + new_content = None + + def adjust_caret_pos(m): + caret_pos[0] -= len(m.group(0)) + return '' + + def remove_comment(text): + """ + Remove comment markers from string + @param {Sting} str + @return {String} + """ + text = re.sub(r'^' + re.escape(comment_start) + r'\s*', adjust_caret_pos, text) + return re.sub(r'\s*' + re.escape(comment_end) + '$', '', text) + + def has_match(tx, start): + return content[start:start + len(tx)] == tx + + # first, we need to make sure that this substring is not inside comment + comment_range = search_comment(content, caret_pos[0], comment_start, comment_end) + + if comment_range and comment_range[0] <= range_start and comment_range[1] >= range_end: + # we're inside comment, remove it + range_start, range_end = comment_range + new_content = remove_comment(content[range_start:range_end]) + else: + # should add comment + # make sure that there's no comment inside selection + new_content = '%s %s %s' % (comment_start, re.sub(re.escape(comment_start) + r'\s*|\s*' + re.escape(comment_end), '', content[range_start:range_end]), comment_end) + + # adjust caret position + caret_pos[0] += len(comment_start) + 1 + + # replace editor content + if new_content is not None: + d = caret_pos[0] - range_start + new_content = new_content[0:d] + zen_coding.get_caret_placeholder() + new_content[d:] + editor.replace_content(unindent(editor, new_content), range_start, range_end) + return True + + return False + +def split_join_tag(editor, profile_name=None): + """ + Splits or joins tag, e.g. transforms it into a short notation and vice versa: +
: join +
: split + @param editor: Editor instance + @type editor: ZenEditor + @param profile_name: Profile name + @type profile_name: str + """ + caret_pos = editor.get_caret_pos() + profile = zen_coding.get_profile(profile_name or editor.get_profile_name()) + caret = zen_coding.get_caret_placeholder() + + # find tag at current position + pair = html_matcher.get_tags(editor.get_content(), caret_pos, profile_name or editor.get_profile_name()) + if pair and pair[0]: + new_content = pair[0].full_tag + + if pair[1]: # join tag + closing_slash = '' + if profile['self_closing_tag'] is True: + closing_slash = '/' + elif profile['self_closing_tag'] == 'xhtml': + closing_slash = ' /' + + new_content = re.sub(r'\s*>$', closing_slash + '>', new_content) + + # add caret placeholder + if len(new_content) + pair[0].start < caret_pos: + new_content += caret + else: + d = caret_pos - pair[0].start + new_content = new_content[0:d] + caret + new_content[d:] + + editor.replace_content(new_content, pair[0].start, pair[1].end) + else: # split tag + nl = zen_coding.get_newline() + pad = zen_coding.get_variable('indentation') + + # define tag content depending on profile + tag_content = profile['tag_nl'] is True and nl + pad + caret + nl or caret + + new_content = '%s%s' % (re.sub(r'\s*\/>$', '>', new_content), tag_content, pair[0].name) + editor.replace_content(new_content, pair[0].start, pair[0].end) + + return True + else: + return False + + +def get_line_bounds(text, pos): + """ + Returns line bounds for specific character position + @type text: str + @param pos: Where to start searching + @type pos: int + @return: list + """ + start = 0 + end = len(text) - 1 + + # search left + for i in range(pos - 1, 0, -1): + if text[i] in '\n\r': + start = i + 1 + break + + # search right + for i in range(pos, len(text)): + if text[i] in '\n\r': + end = i + break + + return start, end + +def remove_tag(editor): + """ + Gracefully removes tag under cursor + @type editor: ZenEditor + """ + caret_pos = editor.get_caret_pos() + content = editor.get_content() + + # search for tag + pair = html_matcher.get_tags(content, caret_pos, editor.get_profile_name()) + if pair and pair[0]: + if not pair[1]: + # simply remove unary tag + editor.replace_content(zen_coding.get_caret_placeholder(), pair[0].start, pair[0].end) + else: + tag_content_range = narrow_to_non_space(content, pair[0].end, pair[1].start) + start_line_bounds = get_line_bounds(content, tag_content_range[0]) + start_line_pad = get_line_padding(content[start_line_bounds[0]:start_line_bounds[1]]) + tag_content = content[tag_content_range[0]:tag_content_range[1]] + + tag_content = unindent_text(tag_content, start_line_pad) + editor.replace_content(zen_coding.get_caret_placeholder() + tag_content, pair[0].start, pair[1].end) + + return True + else: + return False \ No newline at end of file diff --git a/Support/zencoding/zen_actions.pyc b/Support/zencoding/zen_actions.pyc new file mode 100644 index 0000000..c4c754c Binary files /dev/null and b/Support/zencoding/zen_actions.pyc differ diff --git a/Support/zencoding/zen_core.py b/Support/zencoding/zen_core.py new file mode 100644 index 0000000..92411ea --- /dev/null +++ b/Support/zencoding/zen_core.py @@ -0,0 +1,1238 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +''' +Core Zen Coding library. Contains various text manipulation functions: + +== Expand abbreviation +Expands abbreviation like ul#nav>li*5>a into a XHTML string. +=== How to use +First, you have to extract current string (where cursor is) from your test +editor and use find_abbr_in_line() method to extract abbreviation. +If abbreviation was found, this method will return it as well as position index +of abbreviation inside current line. If abbreviation wasn't +found, method returns empty string. With abbreviation found, you should call +parse_into_tree() method to transform abbreviation into a tag tree. +This method returns Tag object on success, None on failure. Then +simply call to_string() method of returned Tag object +to transoform tree into a XHTML string + +You can setup output profile using setup_profile() method +(see default_profile definition for available options) + + +Created on Apr 17, 2009 + +@author: Sergey Chikuyonok (http://chikuyonok.ru) +''' +from zen_settings import zen_settings +import re +import stparser + +newline = '\n' +"Newline symbol" + +caret_placeholder = '{%::zen-caret::%}' + +default_tag = 'div' + +re_tag = re.compile(r'<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>\s]+))?)*\s*(\/?)>$') + +profiles = {} +"Available output profiles" + +default_profile = { + 'tag_case': 'lower', # values are 'lower', 'upper' + 'attr_case': 'lower', # values are 'lower', 'upper' + 'attr_quotes': 'double', # values are 'single', 'double' + + 'tag_nl': 'decide', # each tag on new line, values are True, False, 'decide' + + 'place_cursor': True, # place cursor char — | (pipe) — in output + + 'indent': True, # indent tags + + 'inline_break': 3, # how many inline elements should be to force line break (set to 0 to disable) + + 'self_closing_tag': 'xhtml' # use self-closing style for writing empty elements, e.g.
or
. + # values are True, False, 'xhtml' +} + +basic_filters = 'html'; +"Filters that will be applied for unknown syntax" + +def char_at(text, pos): + """ + Returns character at specified index of text. + If index if out of range, returns empty string + """ + return text[pos] if pos < len(text) else '' + +def has_deep_key(obj, key): + """ + Check if obj dictionary contains deep key. For example, + example, it will allow you to test existance of my_dict[key1][key2][key3], + testing existance of my_dict[key1] first, then my_dict[key1][key2], + and finally my_dict[key1][key2][key3] + @param obj: Dictionary to test + @param obj: dict + @param key: Deep key to test. Can be list (like ['key1', 'key2', 'key3']) or + string (like 'key1.key2.key3') + @type key: list, tuple, str + @return: bool + """ + if isinstance(key, str): + key = key.split('.') + + last_obj = obj + for v in key: + if hasattr(last_obj, v): + last_obj = getattr(last_obj, v) + elif last_obj.has_key(v): + last_obj = last_obj[v] + else: + return False + + return True + + +def is_allowed_char(ch): + """ + Test if passed symbol is allowed in abbreviation + @param ch: Symbol to test + @type ch: str + @return: bool + """ + return ch.isalnum() or ch in "#.>+*:$-_!@[]()|" + +def split_by_lines(text, remove_empty=False): + """ + Split text into lines. Set remove_empty to true to filter out + empty lines + @param text: str + @param remove_empty: bool + @return list + """ + lines = text.splitlines() + + return remove_empty and [line for line in lines if line.strip()] or lines + +def make_map(prop): + """ + Helper function that transforms string into dictionary for faster search + @param prop: Key name in zen_settings['html'] dictionary + @type prop: str + """ + obj = {} + for a in zen_settings['html'][prop].split(','): + obj[a] = True + + zen_settings['html'][prop] = obj + +def create_profile(options): + """ + Create profile by adding default values for passed optoin set + @param options: Profile options + @type options: dict + """ + for k, v in default_profile.items(): + options.setdefault(k, v) + + return options + +def setup_profile(name, options = {}): + """ + @param name: Profile name + @type name: str + @param options: Profile options + @type options: dict + """ + profiles[name.lower()] = create_profile(options); + +def get_newline(): + """ + Returns newline symbol which is used in editor. This function must be + redefined to return current editor's settings + @return: str + """ + return newline + +def set_newline(char): + """ + Sets newline character used in Zen Coding + """ + global newline + newline = char + +def string_to_hash(text): + """ + Helper function that transforms string into hash + @return: dict + """ + obj = {} + items = text.split(",") + for i in items: + obj[i] = True + + return obj + +def pad_string(text, pad): + """ + Indents string with space characters (whitespace or tab) + @param text: Text to indent + @type text: str + @param pad: Indentation level (number) or indentation itself (string) + @type pad: int, str + @return: str + """ + pad_str = '' + result = '' + if isinstance(pad, basestring): + pad_str = pad + else: + pad_str = get_indentation() * pad + + nl = get_newline() + + lines = split_by_lines(text) + + if lines: + result += lines[0] + for line in lines[1:]: + result += nl + pad_str + line + + return result + +def is_snippet(abbr, doc_type = 'html'): + """ + Check is passed abbreviation is a snippet + @return bool + """ + return get_snippet(doc_type, abbr) and True or False + +def is_ends_with_tag(text): + """ + Test is string ends with XHTML tag. This function used for testing if '<' + symbol belogs to tag or abbreviation + @type text: str + @return: bool + """ + return re_tag.search(text) != None + +def get_elements_collection(resource, type): + """ + Returns specified elements collection (like 'empty', 'block_level') from + resource. If collections wasn't found, returns empty object + @type resource: dict + @type type: str + @return: dict + """ + if 'element_types' in resource and type in resource['element_types']: + return resource['element_types'][type] + else: + return {} + +def replace_variables(text, vars=zen_settings['variables']): + """ + Replace variables like ${var} in string + @param text: str + @return: str + """ + return re.sub(r'\$\{([\w\-]+)\}', lambda m: m.group(1) in vars and vars[m.group(1)] or m.group(0), text) + +def get_abbreviation(res_type, abbr): + """ + Returns abbreviation value from data set + @param res_type: Resource type (html, css, ...) + @type res_type: str + @param abbr: Abbreviation name + @type abbr: str + @return dict, None + """ + return get_settings_resource(res_type, abbr, 'abbreviations') + +def get_snippet(res_type, snippet_name): + """ + Returns snippet value from data set + @param res_type: Resource type (html, css, ...) + @type res_type: str + @param snippet_name: Snippet name + @type snippet_name: str + @return dict, None + """ + return get_settings_resource(res_type, snippet_name, 'snippets'); + +def get_variable(name): + """ + Returns variable value + @return: str + """ + return zen_settings['variables'][name] + +def set_variable(name, value): + """ + Set variable value + """ + zen_settings['variables'][name] = value + +def get_indentation(): + """ + Returns indentation string + @return {String} + """ + return get_variable('indentation'); + +def create_resource_chain(syntax, name): + """ + Creates resource inheritance chain for lookups + @param syntax: Syntax name + @type syntax: str + @param name: Resource name + @type name: str + @return: list + """ + result = [] + + if syntax in zen_settings: + resource = zen_settings[syntax] + if name in resource: + result.append(resource[name]) + if 'extends' in resource: + # find resource in ancestors + for type in resource['extends']: + if has_deep_key(zen_settings, [type, name]): + result.append(zen_settings[type][name]) + + return result + +def get_resource(syntax, name): + """ + Get resource collection from settings file for specified syntax. + It follows inheritance chain if resource wasn't directly found in + syntax settings + @param syntax: Syntax name + @type syntax: str + @param name: Resource name + @type name: str + """ + chain = create_resource_chain(syntax, name) + return chain[0] if chain else None + +def get_settings_resource(syntax, abbr, name): + """ + Returns resurce value from data set with respect of inheritance + @param syntax: Resource syntax (html, css, ...) + @type syntax: str + @param abbr: Abbreviation name + @type abbr: str + @param name: Resource name ('snippets' or 'abbreviation') + @type name: str + @return dict, None + """ + for item in create_resource_chain(syntax, name): + if abbr in item: + return item[abbr] + + return None + +def get_word(ix, text): + """ + Get word, starting at ix character of text + @param ix: int + @param text: str + """ + m = re.match(r'^[\w\-:\$]+', text[ix:]) + return m.group(0) if m else '' + +def extract_attributes(attr_set): + """ + Extract attributes and their values from attribute set + @param attr_set: str + """ + attr_set = attr_set.strip() + loop_count = 100 # endless loop protection + re_string = r'^(["\'])((?:(?!\1)[^\\]|\\.)*)\1' + result = [] + + while attr_set and loop_count: + loop_count -= 1 + attr_name = get_word(0, attr_set) + attr = None + if attr_name: + attr = {'name': attr_name, 'value': ''} + + # let's see if attribute has value + ch = attr_set[len(attr_name)] if len(attr_set) > len(attr_name) else '' + if ch == '=': + ch2 = attr_set[len(attr_name) + 1] + if ch2 in '"\'': + # we have a quoted string + m = re.match(re_string, attr_set[len(attr_name) + 1:]) + if m: + attr['value'] = m.group(2) + attr_set = attr_set[len(attr_name) + len(m.group(0)) + 1:].strip() + else: + # something wrong, break loop + attr_set = '' + else: + # unquoted string + m = re.match(r'^(.+?)(\s|$)', attr_set[len(attr_name) + 1:]) + if m: + attr['value'] = m.group(1) + attr_set = attr_set[len(attr_name) + len(m.group(1)) + 1:].strip() + else: + # something wrong, break loop + attr_set = '' + + else: + attr_set = attr_set[len(attr_name):].strip() + else: + # something wrong, can't extract attribute name + break + + if attr: result.append(attr) + + return result + +def parse_attributes(text): + """ + Parses tag attributes extracted from abbreviation + """ + +# Example of incoming data: +# #header +# .some.data +# .some.data#header +# [attr] +# #item[attr=Hello other="World"].class + + result = [] + class_name = None + char_map = {'#': 'id', '.': 'class'} + + # walk char-by-char + i = 0 + il = len(text) + + while i < il: + ch = text[i] + + if ch == '#': # id + val = get_word(i, text[1:]) + result.append({'name': char_map[ch], 'value': val}) + i += len(val) + 1 + + elif ch == '.': #class + val = get_word(i, text[1:]) + if not class_name: + # remember object pointer for value modification + class_name = {'name': char_map[ch], 'value': ''} + result.append(class_name) + + if class_name['value']: + class_name['value'] += ' ' + val + else: + class_name['value'] = val + + i += len(val) + 1 + + elif ch == '[': # begin attribute set + # search for end of set + end_ix = text.find(']', i) + if end_ix == -1: + # invalid attribute set, stop searching + i = len(text) + else: + result.extend(extract_attributes(text[i + 1:end_ix])) + i = end_ix + else: + i += 1 + + + return result + +class AbbrGroup(object): + """ + Abreviation's group element + """ + def __init__(self, parent=None): + """ + @param parent: Parent group item element + @type parent: AbbrGroup + """ + self.expr = '' + self.parent = parent + self.children = [] + + def add_child(self): + child = AbbrGroup(self) + self.children.append(child) + return child + + def clean_up(self): + for item in self.children: + expr = item.expr + if not expr: + self.children.remove(item) + else: + # remove operators at the and of expression + item.clean_up() + +def split_by_groups(abbr): + """ + Split abbreviation by groups + @type abbr: str + @return: AbbrGroup + """ + root = AbbrGroup() + last_parent = root + cur_item = root.add_child() + stack = [] + i = 0 + il = len(abbr) + + while i < il: + ch = abbr[i] + if ch == '(': + # found new group + operator = i and abbr[i - 1] or '' + if operator == '>': + stack.append(cur_item) + last_parent = cur_item + else: + stack.append(last_parent) + cur_item = None + elif ch == ')': + last_parent = stack.pop() + cur_item = None + next_char = char_at(abbr, i + 1) + if next_char == '+' or next_char == '>': + # next char is group operator, skip it + i += 1 + else: + if ch == '+' or ch == '>': + # skip operator if it's followed by parenthesis + next_char = char_at(abbr, i + 1) + if next_char == '(': + i += 1 + continue + + if not cur_item: + cur_item = last_parent.add_child() + cur_item.expr += ch + + i += 1 + + root.clean_up() + return root + +def rollout_tree(tree, parent=None): + """ + Roll outs basic Zen Coding tree into simplified, DOM-like tree. + The simplified tree, for example, represents each multiplied element + as a separate element sets with its own content, if exists. + + The simplified tree element contains some meta info (tag name, attributes, + etc.) as well as output strings, which are exactly what will be outputted + after expanding abbreviation. This tree is used for filtering: + you can apply filters that will alter output strings to get desired look + of expanded abbreviation. + + @type tree: Tag + @param parent: ZenNode + """ + if not parent: + parent = ZenNode(tree) + + how_many = 1 + tag_content = '' + + for child in tree.children: + how_many = child.count + + if child.repeat_by_lines: + # it's a repeating element + tag_content = split_by_lines(child.get_content(), True) + how_many = max(len(tag_content), 1) + else: + tag_content = child.get_content() + + for j in range(how_many): + tag = ZenNode(child) + parent.add_child(tag) + + if child.children: + rollout_tree(child, tag) + + add_point = tag.find_deepest_child() or tag + + if tag_content: + if isinstance(tag_content, basestring): + add_point.content = tag_content + else: + add_point.content = tag_content[j] or '' + + return parent + +def run_filters(tree, profile, filter_list): + """ + Runs filters on tree + @type tree: ZenNode + @param profile: str, object + @param filter_list: str, list + @return: ZenNode + """ + import filters + + if isinstance(profile, basestring) and profile in profiles: + profile = profiles[profile]; + + if not profile: + profile = profiles['plain'] + + if isinstance(filter_list, basestring): + filter_list = re.split(r'[\|,]', filter_list) + + for name in filter_list: + name = name.strip() + if name and name in filters.filter_map: + tree = filters.filter_map[name](tree, profile) + + return tree + +def abbr_to_primary_tree(abbr, doc_type='html'): + """ + Transforms abbreviation into a primary internal tree. This tree should'n + be used ouside of this scope + @param abbr: Abbreviation to transform + @type abbr: str + @param doc_type: Document type (xsl, html), a key of dictionary where to + search abbreviation settings + @type doc_type: str + @return: Tag + """ + root = Tag('', 1, doc_type) + token = re.compile(r'([\+>])?([a-z@\!\#\.][a-z0-9:\-]*)((?:(?:[#\.][\w\-\$]+)|(?:\[[^\]]+\]))+)?(\*(\d*))?(\+$)?', re.IGNORECASE) + + if not abbr: + return None + + def expando_replace(m): + ex = m.group(0) + a = get_abbreviation(doc_type, ex) + return a and a.value or ex + + def token_expander(operator, tag_name, attrs, has_multiplier, multiplier, has_expando): + multiply_by_lines = (has_multiplier and not multiplier) + multiplier = multiplier and int(multiplier) or 1 + + tag_ch = tag_name[0] + if tag_ch == '#' or tag_ch == '.': + if attrs: attrs = tag_name + attrs + else: attrs = tag_name + tag_name = default_tag + + if has_expando: + tag_name += '+' + + current = is_snippet(tag_name, doc_type) and Snippet(tag_name, multiplier, doc_type) or Tag(tag_name, multiplier, doc_type) + + if attrs: + attrs = parse_attributes(attrs) + for attr in attrs: + current.add_attribute(attr['name'], attr['value']) + + # dive into tree + if operator == '>' and token_expander.last: + token_expander.parent = token_expander.last; + + token_expander.parent.add_child(current) + token_expander.last = current + + if multiply_by_lines: + root.multiply_elem = current + + return '' + + # replace expandos + abbr = re.sub(r'([a-z][a-z0-9]*)\+$', expando_replace, abbr) + + token_expander.parent = root + token_expander.last = None + + +# abbr = re.sub(token, lambda m: token_expander(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5), m.group(6), m.group(7)), abbr) + # Issue from Einar Egilsson + abbr = token.sub(lambda m: token_expander(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5), m.group(6)), abbr) + + root.last = token_expander.last + + # empty 'abbr' variable means that abbreviation was expanded successfully, + # non-empty variable means there was a syntax error + return not abbr and root or None; + +def expand_group(group, doc_type, parent): + """ + Expand single group item + @param group: AbbrGroup + @param doc_type: str + @param parent: Tag + """ + tree = abbr_to_primary_tree(group.expr, doc_type) + last_item = None + + if tree: + for item in tree.children: + last_item = item + parent.add_child(last_item) + else: + raise Exception('InvalidGroup') + + + # set repeating element to the topmost node + root = parent + while root.parent: + root = root.parent + + root.last = tree.last + if tree.multiply_elem: + root.multiply_elem = tree.multiply_elem + + # process child groups + if group.children: + add_point = last_item.find_deepest_child() or last_item + for child in group.children: + expand_group(child, doc_type, add_point) + +def replace_unescaped_symbol(text, symbol, replace): + """ + Replaces unescaped symbols in text. For example, the '$' symbol + will be replaced in 'item$count', but not in 'item\$count'. + @param text: Original string + @type text: str + @param symbol: Symbol to replace + @type symbol: st + @param replace: Symbol replacement + @type replace: str, function + @return: str + """ + i = 0 + il = len(text) + sl = len(symbol) + match_count = 0 + + while i < il: + if text[i] == '\\': + # escaped symbol, skip next character + i += sl + 1 + elif text[i:i + sl] == symbol: + # have match + cur_sl = sl + match_count += 1 + new_value = replace + if callable(new_value): + replace_data = replace(text, symbol, i, match_count) + if replace_data: + cur_sl = len(replace_data[0]) + new_value = replace_data[1] + else: + new_value = False + + if new_value is False: # skip replacement + i += 1 + continue + + text = text[0:i] + new_value + text[i + cur_sl:] + # adjust indexes + il = len(text) + i += len(new_value) + else: + i += 1 + + return text + +def run_action(name, *args, **kwargs): + """ + Runs Zen Coding action. For list of available actions and their + arguments see zen_actions.py file. + @param name: Action name + @type name: str + @param args: Additional arguments. It may be array of arguments + or inline arguments. The first argument should be zen_editor instance + @type args: list + @example + zen_coding.run_actions('expand_abbreviation', zen_editor) + zen_coding.run_actions('wrap_with_abbreviation', zen_editor, 'div') + """ + import zen_actions + + try: + if hasattr(zen_actions, name): + return getattr(zen_actions, name)(*args, **kwargs) + except: + return False + +def expand_abbreviation(abbr, syntax='html', profile_name='plain'): + """ + Expands abbreviation into a XHTML tag string + @type abbr: str + @return: str + """ + tree_root = parse_into_tree(abbr, syntax); + if tree_root: + tree = rollout_tree(tree_root) + apply_filters(tree, syntax, profile_name, tree_root.filters) + return replace_variables(tree.to_string()) + + return '' + +def extract_abbreviation(text): + """ + Extracts abbreviations from text stream, starting from the end + @type text: str + @return: Abbreviation or empty string + """ + cur_offset = len(text) + start_index = -1 + brace_count = 0 + + while True: + cur_offset -= 1 + if cur_offset < 0: + # moved at string start + start_index = 0 + break + + ch = text[cur_offset] + + if ch == ']': + brace_count += 1 + elif ch == '[': + brace_count -= 1 + else: + if brace_count: + # respect all characters inside attribute sets + continue + if not is_allowed_char(ch) or (ch == '>' and is_ends_with_tag(text[0:cur_offset + 1])): + # found stop symbol + start_index = cur_offset + 1 + break + + return text[start_index:] if start_index != -1 else '' + +def parse_into_tree(abbr, doc_type='html'): + """ + Parses abbreviation into a node set + @param abbr: Abbreviation to transform + @type abbr: str + @param doc_type: Document type (xsl, html), a key of dictionary where to + search abbreviation settings + @type doc_type: str + @return: Tag + """ + # remove filters from abbreviation + filter_list = [] + + def filter_replace(m): + filter_list.append(m.group(1)) + return '' + + re_filter = re.compile(r'\|([\w\|\-]+)$') + abbr = re_filter.sub(filter_replace, abbr) + + # split abbreviation by groups + group_root = split_by_groups(abbr) + tree_root = Tag('', 1, doc_type) + + # then recursively expand each group item + try: + for item in group_root.children: + expand_group(item, doc_type, tree_root) + except: + # there's invalid group, stop parsing + return None + + tree_root.filters = ''.join(filter_list) + return tree_root + +def is_inside_tag(html, cursor_pos): + re_tag = re.compile(r'^<\/?\w[\w\:\-]*.*?>') + + # search left to find opening brace + pos = cursor_pos + while pos > -1: + if html[pos] == '<': break + pos -= 1 + + + if pos != -1: + m = re_tag.match(html[pos:]); + if m and cursor_pos > pos and cursor_pos < pos + len(m.group(0)): + return True + + return False + +def wrap_with_abbreviation(abbr, text, doc_type='html', profile='plain'): + """ + Wraps passed text with abbreviation. Text will be placed inside last + expanded element + @param abbr: Abbreviation + @type abbr: str + + @param text: Text to wrap + @type text: str + + @param doc_type: Document type (html, xml, etc.) + @type doc_type: str + + @param profile: Output profile's name. + @type profile: str + @return {String} + """ + tree_root = parse_into_tree(abbr, doc_type) + if tree_root: + repeat_elem = tree_root.multiply_elem or tree_root.last + repeat_elem.set_content(text) + repeat_elem.repeat_by_lines = bool(tree_root.multiply_elem) + + tree = rollout_tree(tree_root) + apply_filters(tree, doc_type, profile, tree_root.filters); + return replace_variables(tree.to_string()) + + return None + +def get_caret_placeholder(): + """ + Returns caret placeholder + @return: str + """ + if callable(caret_placeholder): + return caret_placeholder() + else: + return caret_placeholder + +def set_caret_placeholder(value): + """ + Set caret placeholder: a string (like '|') or function. + You may use a function as a placeholder generator. For example, + TextMate uses ${0}, ${1}, ..., ${n} natively for quick Tab-switching + between them. + @param {String|Function} + """ + global caret_placeholder + caret_placeholder = value + +def apply_filters(tree, syntax, profile, additional_filters=None): + """ + Applies filters to tree according to syntax + @param tree: Tag tree to apply filters to + @type tree: ZenNode + @param syntax: Syntax name ('html', 'css', etc.) + @type syntax: str + @param profile: Profile or profile's name + @type profile: str, object + @param additional_filters: List or pipe-separated string of additional filters to apply + @type additional_filters: str, list + + @return: ZenNode + """ + _filters = get_resource(syntax, 'filters') or basic_filters + + if additional_filters: + _filters += '|' + if isinstance(additional_filters, basestring): + _filters += additional_filters + else: + _filters += '|'.join(additional_filters) + + if not _filters: + # looks like unknown syntax, apply basic filters + _filters = basic_filters + + return run_filters(tree, profile, _filters) + +def replace_counter(text, value): + """ + Replaces '$' character in string assuming it might be escaped with '\' + @type text: str + @type value: str, int + @return: str + """ + symbol = '$' + value = str(value) + + def replace_func(tx, symbol, pos, match_num): + if tx[pos + 1] == '{': + # it's a variable, skip it + return False + + # replace sequense of $ symbols with padded number + j = pos + 1 + while tx[j] == '$' and char_at(tx, j + 1) != '{': j += 1 + return (tx[pos:j], value.zfill(j - pos)) + + return replace_unescaped_symbol(text, symbol, replace_func) + +def get_profile(name): + """ + Get profile by it's name. If profile wasn't found, returns 'plain' profile + """ + return profiles[name] if name in profiles else profiles['plain'] + +def update_settings(settings): + globals()['zen_settings'] = settings + +class Tag(object): + def __init__(self, name, count=1, doc_type='html'): + """ + @param name: Tag name + @type name: str + @param count: How many times this tag must be outputted + @type count: int + @param doc_type: Document type (xsl, html) + @type doc_type: str + """ + name = name.lower() + + abbr = get_abbreviation(doc_type, name) + + if abbr and abbr.type == stparser.TYPE_REFERENCE: + abbr = get_abbreviation(doc_type, abbr.value) + + self.name = abbr and abbr.value['name'] or name.replace('+', '') + self.count = count + self.children = [] + self.attributes = [] + self.multiply_elem = None + self.__attr_hash = {} + self._abbr = abbr + self.__content = '' + self.repeat_by_lines = False + self._res = zen_settings.has_key(doc_type) and zen_settings[doc_type] or {} + self.parent = None + + # add default attributes + if self._abbr and 'attributes' in self._abbr.value: + for a in self._abbr.value['attributes']: + self.add_attribute(a['name'], a['value']) + + def add_child(self, tag): + """ + Add new child + @type tag: Tag + """ + tag.parent = self + self.children.append(tag) + + def add_attribute(self, name, value): + """ + Add attribute to tag. If the attribute with the same name already exists, + it will be overwritten, but if it's name is 'class', it will be merged + with the existed one + @param name: Attribute nama + @type name: str + @param value: Attribute value + @type value: str + """ + + # the only place in Tag where pipe (caret) character may exist + # is the attribute: escape it with internal placeholder + value = replace_unescaped_symbol(value, '|', get_caret_placeholder()); + + if name in self.__attr_hash: +# attribue already exists + a = self.__attr_hash[name] + if name == 'class': +# 'class' is a magic attribute + if a['value']: + value = ' ' + value + a['value'] += value + else: + a['value'] = value + else: + a = {'name': name, 'value': value} + self.__attr_hash[name] = a + self.attributes.append(a) + + def has_tags_in_content(self): + """ + This function tests if current tags' content contains XHTML tags. + This function is mostly used for output formatting + """ + return self.get_content() and re_tag.search(self.get_content()) + + def get_content(self): + return self.__content + + def set_content(self, value): + self.__content = value + + def set_content(self, content): #@DuplicatedSignature + self.__content = content + + def get_content(self): #@DuplicatedSignature + return self.__content + + def find_deepest_child(self): + """ + Search for deepest and latest child of current element. + Returns None if there's no children + @return Tag or None + """ + if not self.children: + return None + + deepest_child = self + while True: + deepest_child = deepest_child.children[-1] + if not deepest_child.children: + break + + return deepest_child + +class Snippet(Tag): + def __init__(self, name, count=1, doc_type='html'): + super(Snippet, self).__init__(name, count, doc_type) + self.value = replace_unescaped_symbol(get_snippet(doc_type, name), '|', get_caret_placeholder()) + self.attributes = {'id': get_caret_placeholder(), 'class': get_caret_placeholder()} + self._res = zen_settings[doc_type] + + def is_block(self): + return True + +class ZenNode(object): + """ + Creates simplified tag from Zen Coding tag + """ + def __init__(self, tag): + """ + @type tag: Tag + """ + self.type = 'snippet' if isinstance(tag, Snippet) else 'tag' + self.name = tag.name + self.attributes = tag.attributes + self.children = []; + + self.source = tag + "Source element from which current tag was created" + + # relations + self.parent = None + self.next_sibling = None + self.previous_sibling = None + + # output params + self.start = '' + self.end = '' + self.content = '' + self.padding = '' + + def add_child(self, tag): + """ + @type tag: ZenNode + """ + tag.parent = self + + if self.children: + last_child = self.children[-1] + tag.previous_sibling = last_child + last_child.next_sibling = tag + + self.children.append(tag) + + def get_attribute(self, name): + """ + Get attribute's value. + @type name: str + @return: None if attribute wasn't found + """ + name = name.lower() + for attr in self.attributes: + if attr['name'].lower() == name: + return attr['value'] + + return None + + def is_unary(self): + """ + Test if current tag is unary (no closing tag) + @return: bool + """ + if self.type == 'snippet': + return False + + return (self.source._abbr and self.source._abbr.value['is_empty']) or (self.name in get_elements_collection(self.source._res, 'empty')) + + def is_inline(self): + """ + Test if current tag is inline-level (like , ) + @return: bool + """ + return self.name in get_elements_collection(self.source._res, 'inline_level') + + def is_block(self): + """ + Test if current element is block-level + @return: bool + """ + return self.type == 'snippet' or not self.is_inline() + + def has_tags_in_content(self): + """ + This function tests if current tags' content contains xHTML tags. + This function is mostly used for output formatting + """ + return self.content and re_tag.search(self.content) + + def has_children(self): + """ + Check if tag has child elements + @return: bool + """ + return bool(self.children) + + def has_block_children(self): + """ + Test if current tag contains block-level children + @return: bool + """ + if self.has_tags_in_content() and self.is_block(): + return True + + for item in self.children: + if item.is_block(): + return True + + return False + + def find_deepest_child(self): + """ + Search for deepest and latest child of current element + Returns None if there's no children + @return: ZenNode|None + """ + if not self.children: + return None + + deepest_child = self + while True: + deepest_child = deepest_child.children[-1] + if not deepest_child.children: + break + + return deepest_child + + def to_string(self): + "@return {String}" + content = ''.join([item.to_string() for item in self.children]) + return self.start + self.content + content + self.end + +# create default profiles +setup_profile('xhtml'); +setup_profile('html', {'self_closing_tag': False}); +setup_profile('xml', {'self_closing_tag': True, 'tag_nl': True}); +setup_profile('plain', {'tag_nl': False, 'indent': False, 'place_cursor': False}); + +# This method call explicity loads default settings from zen_settings.py on start up +# Comment this line if you want to load data from other resources (like editor's +# native snippet) +update_settings(stparser.get_settings()) diff --git a/Support/zencoding/zen_core.pyc b/Support/zencoding/zen_core.pyc new file mode 100644 index 0000000..c6288c6 Binary files /dev/null and b/Support/zencoding/zen_core.pyc differ diff --git a/Support/zencoding/zen_editor.py b/Support/zencoding/zen_editor.py new file mode 100644 index 0000000..4474d8c --- /dev/null +++ b/Support/zencoding/zen_editor.py @@ -0,0 +1,128 @@ +''' +High-level editor interface that communicates with underlying editor (like +Espresso, Coda, etc.) or browser. +Basically, you should call set_context(obj) method to +set up undelying editor context before using any other method. + +This interface is used by zen_actions.py for performing different +actions like Expand abbreviation + +@example +import zen_editor +zen_editor.set_context(obj); +//now you are ready to use editor object +zen_editor.get_selection_range(); + +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +''' +class ZenEditor(): + def __init__(self): + pass + + def set_context(self, context): + """ + Setup underlying editor context. You should call this method + before using any Zen Coding action. + @param context: context object + """ + pass + + def get_selection_range(self): + """ + Returns character indexes of selected text + @return: list of start and end indexes + @example + start, end = zen_editor.get_selection_range(); + print('%s, %s' % (start, end)) + """ + return 0, 0 + + + def create_selection(self, start, end=None): + """ + Creates selection from start to end character + indexes. If end is ommited, this method should place caret + and start index + @type start: int + @type end: int + @example + zen_editor.create_selection(10, 40) + # move caret to 15th character + zen_editor.create_selection(15) + """ + pass + + def get_current_line_range(self): + """ + Returns current line's start and end indexes + @return: list of start and end indexes + @example + start, end = zen_editor.get_current_line_range(); + print('%s, %s' % (start, end)) + """ + return 0, 0 + + def get_caret_pos(self): + """ Returns current caret position """ + return 0 + + def set_caret_pos(self, pos): + """ + Set new caret position + @type pos: int + """ + pass + + def get_current_line(self): + """ + Returns content of current line + @return: str + """ + return '' + + def replace_content(self, value, start=None, end=None): + """ + Replace editor's content or it's part (from start to + end index). If value contains + caret_placeholder, the editor will put caret into + this position. If you skip start and end + arguments, the whole target's content will be replaced with + value. + + If you pass start argument only, + the value will be placed at start string + index of current content. + + If you pass start and end arguments, + the corresponding substring of current target's content will be + replaced with value + @param value: Content you want to paste + @type value: str + @param start: Start index of editor's content + @type start: int + @param end: End index of editor's content + @type end: int + """ + pass + + def get_content(self): + """ + Returns editor's content + @return: str + """ + return '' + + def get_syntax(self): + """ + Returns current editor's syntax mode + @return: str + """ + return 'html' + + def get_profile_name(self): + """ + Returns current output profile name (@see zen_coding#setup_profile) + @return {String} + """ + return 'xhtml' diff --git a/Support/zencoding/zen_settings.py b/Support/zencoding/zen_settings.py new file mode 100644 index 0000000..9173afe --- /dev/null +++ b/Support/zencoding/zen_settings.py @@ -0,0 +1,727 @@ +""" +Zen Coding settings +@author Sergey Chikuyonok (serge.che@gmail.com) +@link http://chikuyonok.ru +""" +zen_settings = { + +# Variables that can be placed inside snippets or abbreviations as ${variable} +# ${child} variable is reserved, don't use it + 'variables': { + 'lang': 'en', + 'locale': 'en-US', + 'charset': 'UTF-8', + 'profile': 'xhtml', + +# Inner element indentation + 'indentation': '\t' + }, + + # common settings are used for quick injection of user-defined snippets + 'common': { + + }, + + 'css': { + 'extends': 'common', + 'snippets': { + "@i": "@import url(|);", + "@m": "@media print {\n\t|\n}", + "@f": "@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}", + "!": "!important", + "pos": "position:|;", + "pos:s": "position:static;", + "pos:a": "position:absolute;", + "pos:r": "position:relative;", + "pos:f": "position:fixed;", + "t": "top:|;", + "t:a": "top:auto;", + "r": "right:|;", + "r:a": "right:auto;", + "b": "bottom:|;", + "b:a": "bottom:auto;", + "l": "left:|;", + "l:a": "left:auto;", + "z": "z-index:|;", + "z:a": "z-index:auto;", + "fl": "float:|;", + "fl:n": "float:none;", + "fl:l": "float:left;", + "fl:r": "float:right;", + "cl": "clear:|;", + "cl:n": "clear:none;", + "cl:l": "clear:left;", + "cl:r": "clear:right;", + "cl:b": "clear:both;", + "d": "display:|;", + "d:n": "display:none;", + "d:b": "display:block;", + "d:ib": "display:inline;", + "d:li": "display:list-item;", + "d:ri": "display:run-in;", + "d:cp": "display:compact;", + "d:tb": "display:table;", + "d:itb": "display:inline-table;", + "d:tbcp": "display:table-caption;", + "d:tbcl": "display:table-column;", + "d:tbclg": "display:table-column-group;", + "d:tbhg": "display:table-header-group;", + "d:tbfg": "display:table-footer-group;", + "d:tbr": "display:table-row;", + "d:tbrg": "display:table-row-group;", + "d:tbc": "display:table-cell;", + "d:rb": "display:ruby;", + "d:rbb": "display:ruby-base;", + "d:rbbg": "display:ruby-base-group;", + "d:rbt": "display:ruby-text;", + "d:rbtg": "display:ruby-text-group;", + "v": "visibility:|;", + "v:v": "visibility:visible;", + "v:h": "visibility:hidden;", + "v:c": "visibility:collapse;", + "ov": "overflow:|;", + "ov:v": "overflow:visible;", + "ov:h": "overflow:hidden;", + "ov:s": "overflow:scroll;", + "ov:a": "overflow:auto;", + "ovx": "overflow-x:|;", + "ovx:v": "overflow-x:visible;", + "ovx:h": "overflow-x:hidden;", + "ovx:s": "overflow-x:scroll;", + "ovx:a": "overflow-x:auto;", + "ovy": "overflow-y:|;", + "ovy:v": "overflow-y:visible;", + "ovy:h": "overflow-y:hidden;", + "ovy:s": "overflow-y:scroll;", + "ovy:a": "overflow-y:auto;", + "ovs": "overflow-style:|;", + "ovs:a": "overflow-style:auto;", + "ovs:s": "overflow-style:scrollbar;", + "ovs:p": "overflow-style:panner;", + "ovs:m": "overflow-style:move;", + "ovs:mq": "overflow-style:marquee;", + "zoo": "zoom:1;", + "cp": "clip:|;", + "cp:a": "clip:auto;", + "cp:r": "clip:rect(|);", + "bxz": "box-sizing:|;", + "bxz:cb": "box-sizing:content-box;", + "bxz:bb": "box-sizing:border-box;", + "bxsh": "box-shadow:|;", + "bxsh:n": "box-shadow:none;", + "bxsh:w": "-webkit-box-shadow:0 0 0 #000;", + "bxsh:m": "-moz-box-shadow:0 0 0 0 #000;", + "m": "margin:|;", + "m:a": "margin:auto;", + "m:0": "margin:0;", + "m:2": "margin:0 0;", + "m:3": "margin:0 0 0;", + "m:4": "margin:0 0 0 0;", + "mt": "margin-top:|;", + "mt:a": "margin-top:auto;", + "mr": "margin-right:|;", + "mr:a": "margin-right:auto;", + "mb": "margin-bottom:|;", + "mb:a": "margin-bottom:auto;", + "ml": "margin-left:|;", + "ml:a": "margin-left:auto;", + "p": "padding:|;", + "p:0": "padding:0;", + "p:2": "padding:0 0;", + "p:3": "padding:0 0 0;", + "p:4": "padding:0 0 0 0;", + "pt": "padding-top:|;", + "pr": "padding-right:|;", + "pb": "padding-bottom:|;", + "pl": "padding-left:|;", + "w": "width:|;", + "w:a": "width:auto;", + "h": "height:|;", + "h:a": "height:auto;", + "maw": "max-width:|;", + "maw:n": "max-width:none;", + "mah": "max-height:|;", + "mah:n": "max-height:none;", + "miw": "min-width:|;", + "mih": "min-height:|;", + "o": "outline:|;", + "o:n": "outline:none;", + "oo": "outline-offset:|;", + "ow": "outline-width:|;", + "os": "outline-style:|;", + "oc": "outline-color:#000;", + "oc:i": "outline-color:invert;", + "bd": "border:|;", + "bd+": "border:1px solid #000;", + "bd:n": "border:none;", + "bdbk": "border-break:|;", + "bdbk:c": "border-break:close;", + "bdcl": "border-collapse:|;", + "bdcl:c": "border-collapse:collapse;", + "bdcl:s": "border-collapse:separate;", + "bdc": "border-color:#000;", + "bdi": "border-image:url(|);", + "bdi:n": "border-image:none;", + "bdi:w": "-webkit-border-image:url(|) 0 0 0 0 stretch stretch;", + "bdi:m": "-moz-border-image:url(|) 0 0 0 0 stretch stretch;", + "bdti": "border-top-image:url(|);", + "bdti:n": "border-top-image:none;", + "bdri": "border-right-image:url(|);", + "bdri:n": "border-right-image:none;", + "bdbi": "border-bottom-image:url(|);", + "bdbi:n": "border-bottom-image:none;", + "bdli": "border-left-image:url(|);", + "bdli:n": "border-left-image:none;", + "bdci": "border-corner-image:url(|);", + "bdci:n": "border-corner-image:none;", + "bdci:c": "border-corner-image:continue;", + "bdtli": "border-top-left-image:url(|);", + "bdtli:n": "border-top-left-image:none;", + "bdtli:c": "border-top-left-image:continue;", + "bdtri": "border-top-right-image:url(|);", + "bdtri:n": "border-top-right-image:none;", + "bdtri:c": "border-top-right-image:continue;", + "bdbri": "border-bottom-right-image:url(|);", + "bdbri:n": "border-bottom-right-image:none;", + "bdbri:c": "border-bottom-right-image:continue;", + "bdbli": "border-bottom-left-image:url(|);", + "bdbli:n": "border-bottom-left-image:none;", + "bdbli:c": "border-bottom-left-image:continue;", + "bdf": "border-fit:|;", + "bdf:c": "border-fit:clip;", + "bdf:r": "border-fit:repeat;", + "bdf:sc": "border-fit:scale;", + "bdf:st": "border-fit:stretch;", + "bdf:ow": "border-fit:overwrite;", + "bdf:of": "border-fit:overflow;", + "bdf:sp": "border-fit:space;", + "bdl": "border-length:|;", + "bdl:a": "border-length:auto;", + "bdsp": "border-spacing:|;", + "bds": "border-style:|;", + "bds:n": "border-style:none;", + "bds:h": "border-style:hidden;", + "bds:dt": "border-style:dotted;", + "bds:ds": "border-style:dashed;", + "bds:s": "border-style:solid;", + "bds:db": "border-style:double;", + "bds:dtds": "border-style:dot-dash;", + "bds:dtdtds": "border-style:dot-dot-dash;", + "bds:w": "border-style:wave;", + "bds:g": "border-style:groove;", + "bds:r": "border-style:ridge;", + "bds:i": "border-style:inset;", + "bds:o": "border-style:outset;", + "bdw": "border-width:|;", + "bdt": "border-top:|;", + "bdt+": "border-top:1px solid #000;", + "bdt:n": "border-top:none;", + "bdtw": "border-top-width:|;", + "bdts": "border-top-style:|;", + "bdts:n": "border-top-style:none;", + "bdtc": "border-top-color:#000;", + "bdr": "border-right:|;", + "bdr+": "border-right:1px solid #000;", + "bdr:n": "border-right:none;", + "bdrw": "border-right-width:|;", + "bdrs": "border-right-style:|;", + "bdrs:n": "border-right-style:none;", + "bdrc": "border-right-color:#000;", + "bdb": "border-bottom:|;", + "bdb+": "border-bottom:1px solid #000;", + "bdb:n": "border-bottom:none;", + "bdbw": "border-bottom-width:|;", + "bdbs": "border-bottom-style:|;", + "bdbs:n": "border-bottom-style:none;", + "bdbc": "border-bottom-color:#000;", + "bdl": "border-left:|;", + "bdl+": "border-left:1px solid #000;", + "bdl:n": "border-left:none;", + "bdlw": "border-left-width:|;", + "bdls": "border-left-style:|;", + "bdls:n": "border-left-style:none;", + "bdlc": "border-left-color:#000;", + "bdrs": "border-radius:|;", + "bdtrrs": "border-top-right-radius:|;", + "bdtlrs": "border-top-left-radius:|;", + "bdbrrs": "border-bottom-right-radius:|;", + "bdblrs": "border-bottom-left-radius:|;", + "bg": "background:|;", + "bg+": "background:#FFF url(|) 0 0 no-repeat;", + "bg:n": "background:none;", + "bg:ie": "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='|x.png');", + "bgc": "background-color:#FFF;", + "bgi": "background-image:url(|);", + "bgi:n": "background-image:none;", + "bgr": "background-repeat:|;", + "bgr:n": "background-repeat:no-repeat;", + "bgr:x": "background-repeat:repeat-x;", + "bgr:y": "background-repeat:repeat-y;", + "bga": "background-attachment:|;", + "bga:f": "background-attachment:fixed;", + "bga:s": "background-attachment:scroll;", + "bgp": "background-position:0 0;", + "bgpx": "background-position-x:|;", + "bgpy": "background-position-y:|;", + "bgbk": "background-break:|;", + "bgbk:bb": "background-break:bounding-box;", + "bgbk:eb": "background-break:each-box;", + "bgbk:c": "background-break:continuous;", + "bgcp": "background-clip:|;", + "bgcp:bb": "background-clip:border-box;", + "bgcp:pb": "background-clip:padding-box;", + "bgcp:cb": "background-clip:content-box;", + "bgcp:nc": "background-clip:no-clip;", + "bgo": "background-origin:|;", + "bgo:pb": "background-origin:padding-box;", + "bgo:bb": "background-origin:border-box;", + "bgo:cb": "background-origin:content-box;", + "bgz": "background-size:|;", + "bgz:a": "background-size:auto;", + "bgz:ct": "background-size:contain;", + "bgz:cv": "background-size:cover;", + "c": "color:#000;", + "tbl": "table-layout:|;", + "tbl:a": "table-layout:auto;", + "tbl:f": "table-layout:fixed;", + "cps": "caption-side:|;", + "cps:t": "caption-side:top;", + "cps:b": "caption-side:bottom;", + "ec": "empty-cells:|;", + "ec:s": "empty-cells:show;", + "ec:h": "empty-cells:hide;", + "lis": "list-style:|;", + "lis:n": "list-style:none;", + "lisp": "list-style-position:|;", + "lisp:i": "list-style-position:inside;", + "lisp:o": "list-style-position:outside;", + "list": "list-style-type:|;", + "list:n": "list-style-type:none;", + "list:d": "list-style-type:disc;", + "list:c": "list-style-type:circle;", + "list:s": "list-style-type:square;", + "list:dc": "list-style-type:decimal;", + "list:dclz": "list-style-type:decimal-leading-zero;", + "list:lr": "list-style-type:lower-roman;", + "list:ur": "list-style-type:upper-roman;", + "lisi": "list-style-image:|;", + "lisi:n": "list-style-image:none;", + "q": "quotes:|;", + "q:n": "quotes:none;", + "q:ru": "quotes:'\00AB' '\00BB' '\201E' '\201C';", + "q:en": "quotes:'\201C' '\201D' '\2018' '\2019';", + "ct": "content:|;", + "ct:n": "content:normal;", + "ct:oq": "content:open-quote;", + "ct:noq": "content:no-open-quote;", + "ct:cq": "content:close-quote;", + "ct:ncq": "content:no-close-quote;", + "ct:a": "content:attr(|);", + "ct:c": "content:counter(|);", + "ct:cs": "content:counters(|);", + "coi": "counter-increment:|;", + "cor": "counter-reset:|;", + "va": "vertical-align:|;", + "va:sup": "vertical-align:super;", + "va:t": "vertical-align:top;", + "va:tt": "vertical-align:text-top;", + "va:m": "vertical-align:middle;", + "va:bl": "vertical-align:baseline;", + "va:b": "vertical-align:bottom;", + "va:tb": "vertical-align:text-bottom;", + "va:sub": "vertical-align:sub;", + "ta": "text-align:|;", + "ta:l": "text-align:left;", + "ta:c": "text-align:center;", + "ta:r": "text-align:right;", + "tal": "text-align-last:|;", + "tal:a": "text-align-last:auto;", + "tal:l": "text-align-last:left;", + "tal:c": "text-align-last:center;", + "tal:r": "text-align-last:right;", + "td": "text-decoration:|;", + "td:n": "text-decoration:none;", + "td:u": "text-decoration:underline;", + "td:o": "text-decoration:overline;", + "td:l": "text-decoration:line-through;", + "te": "text-emphasis:|;", + "te:n": "text-emphasis:none;", + "te:ac": "text-emphasis:accent;", + "te:dt": "text-emphasis:dot;", + "te:c": "text-emphasis:circle;", + "te:ds": "text-emphasis:disc;", + "te:b": "text-emphasis:before;", + "te:a": "text-emphasis:after;", + "th": "text-height:|;", + "th:a": "text-height:auto;", + "th:f": "text-height:font-size;", + "th:t": "text-height:text-size;", + "th:m": "text-height:max-size;", + "ti": "text-indent:|;", + "ti:-": "text-indent:-9999px;", + "tj": "text-justify:|;", + "tj:a": "text-justify:auto;", + "tj:iw": "text-justify:inter-word;", + "tj:ii": "text-justify:inter-ideograph;", + "tj:ic": "text-justify:inter-cluster;", + "tj:d": "text-justify:distribute;", + "tj:k": "text-justify:kashida;", + "tj:t": "text-justify:tibetan;", + "to": "text-outline:|;", + "to+": "text-outline:0 0 #000;", + "to:n": "text-outline:none;", + "tr": "text-replace:|;", + "tr:n": "text-replace:none;", + "tt": "text-transform:|;", + "tt:n": "text-transform:none;", + "tt:c": "text-transform:capitalize;", + "tt:u": "text-transform:uppercase;", + "tt:l": "text-transform:lowercase;", + "tw": "text-wrap:|;", + "tw:n": "text-wrap:normal;", + "tw:no": "text-wrap:none;", + "tw:u": "text-wrap:unrestricted;", + "tw:s": "text-wrap:suppress;", + "tsh": "text-shadow:|;", + "tsh+": "text-shadow:0 0 0 #000;", + "tsh:n": "text-shadow:none;", + "lh": "line-height:|;", + "whs": "white-space:|;", + "whs:n": "white-space:normal;", + "whs:p": "white-space:pre;", + "whs:nw": "white-space:nowrap;", + "whs:pw": "white-space:pre-wrap;", + "whs:pl": "white-space:pre-line;", + "whsc": "white-space-collapse:|;", + "whsc:n": "white-space-collapse:normal;", + "whsc:k": "white-space-collapse:keep-all;", + "whsc:l": "white-space-collapse:loose;", + "whsc:bs": "white-space-collapse:break-strict;", + "whsc:ba": "white-space-collapse:break-all;", + "wob": "word-break:|;", + "wob:n": "word-break:normal;", + "wob:k": "word-break:keep-all;", + "wob:l": "word-break:loose;", + "wob:bs": "word-break:break-strict;", + "wob:ba": "word-break:break-all;", + "wos": "word-spacing:|;", + "wow": "word-wrap:|;", + "wow:nm": "word-wrap:normal;", + "wow:n": "word-wrap:none;", + "wow:u": "word-wrap:unrestricted;", + "wow:s": "word-wrap:suppress;", + "lts": "letter-spacing:|;", + "f": "font:|;", + "f+": "font:1em Arial,sans-serif;", + "fw": "font-weight:|;", + "fw:n": "font-weight:normal;", + "fw:b": "font-weight:bold;", + "fw:br": "font-weight:bolder;", + "fw:lr": "font-weight:lighter;", + "fs": "font-style:|;", + "fs:n": "font-style:normal;", + "fs:i": "font-style:italic;", + "fs:o": "font-style:oblique;", + "fv": "font-variant:|;", + "fv:n": "font-variant:normal;", + "fv:sc": "font-variant:small-caps;", + "fz": "font-size:|;", + "fza": "font-size-adjust:|;", + "fza:n": "font-size-adjust:none;", + "ff": "font-family:|;", + "ff:s": "font-family:serif;", + "ff:ss": "font-family:sans-serif;", + "ff:c": "font-family:cursive;", + "ff:f": "font-family:fantasy;", + "ff:m": "font-family:monospace;", + "fef": "font-effect:|;", + "fef:n": "font-effect:none;", + "fef:eg": "font-effect:engrave;", + "fef:eb": "font-effect:emboss;", + "fef:o": "font-effect:outline;", + "fem": "font-emphasize:|;", + "femp": "font-emphasize-position:|;", + "femp:b": "font-emphasize-position:before;", + "femp:a": "font-emphasize-position:after;", + "fems": "font-emphasize-style:|;", + "fems:n": "font-emphasize-style:none;", + "fems:ac": "font-emphasize-style:accent;", + "fems:dt": "font-emphasize-style:dot;", + "fems:c": "font-emphasize-style:circle;", + "fems:ds": "font-emphasize-style:disc;", + "fsm": "font-smooth:|;", + "fsm:a": "font-smooth:auto;", + "fsm:n": "font-smooth:never;", + "fsm:aw": "font-smooth:always;", + "fst": "font-stretch:|;", + "fst:n": "font-stretch:normal;", + "fst:uc": "font-stretch:ultra-condensed;", + "fst:ec": "font-stretch:extra-condensed;", + "fst:c": "font-stretch:condensed;", + "fst:sc": "font-stretch:semi-condensed;", + "fst:se": "font-stretch:semi-expanded;", + "fst:e": "font-stretch:expanded;", + "fst:ee": "font-stretch:extra-expanded;", + "fst:ue": "font-stretch:ultra-expanded;", + "op": "opacity:|;", + "op:ie": "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);", + "op:ms": "-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';", + "rz": "resize:|;", + "rz:n": "resize:none;", + "rz:b": "resize:both;", + "rz:h": "resize:horizontal;", + "rz:v": "resize:vertical;", + "cur": "cursor:|;", + "cur:a": "cursor:auto;", + "cur:d": "cursor:default;", + "cur:c": "cursor:crosshair;", + "cur:ha": "cursor:hand;", + "cur:he": "cursor:help;", + "cur:m": "cursor:move;", + "cur:p": "cursor:pointer;", + "cur:t": "cursor:text;", + "pgbb": "page-break-before:|;", + "pgbb:au": "page-break-before:auto;", + "pgbb:al": "page-break-before:always;", + "pgbb:l": "page-break-before:left;", + "pgbb:r": "page-break-before:right;", + "pgbi": "page-break-inside:|;", + "pgbi:au": "page-break-inside:auto;", + "pgbi:av": "page-break-inside:avoid;", + "pgba": "page-break-after:|;", + "pgba:au": "page-break-after:auto;", + "pgba:al": "page-break-after:always;", + "pgba:l": "page-break-after:left;", + "pgba:r": "page-break-after:right;", + "orp": "orphans:|;", + "wid": "widows:|;" + } + }, + + 'html': { + 'extends': 'common', + 'filters': 'html', + 'snippets': { + 'cc:ie6': '', + 'cc:ie': '', + 'cc:noie': '\n\t${child}|\n', + 'html:4t': '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + '\n\t${child}|\n\n' + + '', + + 'html:4s': '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + '\n\t${child}|\n\n' + + '', + + 'html:xt': '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + '\n\t${child}|\n\n' + + '', + + 'html:xs': '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + '\n\t${child}|\n\n' + + '', + + 'html:xxs': '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + '\n\t${child}|\n\n' + + '', + + 'html:5': '\n' + + '\n' + + '\n' + + ' \n' + + ' \n' + + '\n' + + '\n\t${child}|\n\n' + + '' + }, + + 'abbreviations': { + 'a': '
', + 'a:link': '', + 'a:mail': '', + 'abbr': '', + 'acronym': '', + 'base': '', + 'bdo': '', + 'bdo:r': '', + 'bdo:l': '', + 'link:css': '', + 'link:print': '', + 'link:favicon': '', + 'link:touch': '', + 'link:rss': '', + 'link:atom': '', + 'meta:utf': '', + 'meta:win': '', + 'meta:compat': '', + 'style': '', + 'script': '', + 'script:src': '', + 'img': '', + 'iframe': '', + 'embed': '', + 'object': '', + 'param': '', + 'map': '', + 'area': '', + 'area:d': '', + 'area:c': '', + 'area:r': '', + 'area:p': '', + 'link': '', + 'form': '
', + 'form:get': '
', + 'form:post': '
', + 'label': '', + 'input': '', + 'input:hidden': '', + 'input:h': '', + 'input:text': '', + 'input:t': '', + 'input:search': '', + 'input:email': '', + 'input:url': '', + 'input:password': '', + 'input:p': '', + 'input:datetime': '', + 'input:date': '', + 'input:datetime-local': '', + 'input:month': '', + 'input:week': '', + 'input:time': '', + 'input:number': '', + 'input:color': '', + 'input:checkbox': '', + 'input:c': '', + 'input:radio': '', + 'input:r': '', + 'input:range': '', + 'input:file': '', + 'input:f': '', + 'input:submit': '', + 'input:s': '', + 'input:image': '', + 'input:i': '', + 'input:reset': '', + 'input:button': '', + 'input:b': '', + 'select': '', + 'option': '', + 'textarea': '', + 'menu:context': '', + 'menu:c': '', + 'menu:toolbar': '', + 'menu:t': '', + 'video': '', + 'audio': '', + 'html:xml': '', + 'bq': '
', + 'acr': '', + 'fig': '
', + 'ifr': '', + 'emb': '', + 'obj': '', + 'src': '', + 'cap': '', + 'colg': '', + 'fst': '
', + 'btn': '', + 'optg': '', + 'opt': '', + 'tarea': '', + 'leg': '', + 'sect': '
', + 'art': '
', + 'hdr': '
', + 'ftr': '
', + 'adr': '
', + 'dlg': '', + 'str': '', + 'prog': '', + 'fset': '
', + 'datag': '', + 'datal': '', + 'kg': '', + 'out': '', + 'det': '
', + 'cmd': '', + +# expandos + 'ol+': 'ol>li', + 'ul+': 'ul>li', + 'dl+': 'dl>dt+dd', + 'map+': 'map>area', + 'table+': 'table>tr>td', + 'colgroup+': 'colgroup>col', + 'colg+': 'colgroup>col', + 'tr+': 'tr>td', + 'select+': 'select>option', + 'optgroup+': 'optgroup>option', + 'optg+': 'optgroup>option' + + }, + + 'element_types': { + 'empty': 'area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed,keygen,command', + 'block_level': 'address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,link,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul,h1,h2,h3,h4,h5,h6', + 'inline_level': 'a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var' + } + }, + + 'xsl': { + 'extends': 'common,html', + 'filters': 'html, xsl', + 'abbreviations': { + 'tm': '', + 'tmatch': 'tm', + 'tn': '', + 'tname': 'tn', + 'xsl:when': '', + 'wh': 'xsl:when', + 'var': '|', + 'vare': '', + 'if': '', + 'call': '', + 'attr': '', + 'wp': '', + 'par': '', + 'val': '', + 'co': '', + 'each': '', + 'ap': '', + +# expandos + 'choose+': 'xsl:choose>xsl:when+xsl:otherwise' + } + }, + + 'haml': { + 'filters': 'haml', + 'extends': 'html' + } +} \ No newline at end of file diff --git a/Support/zencoding/zen_settings.pyc b/Support/zencoding/zen_settings.pyc new file mode 100644 index 0000000..63e938d Binary files /dev/null and b/Support/zencoding/zen_settings.pyc differ diff --git a/info.plist b/info.plist new file mode 100644 index 0000000..9e97386 --- /dev/null +++ b/info.plist @@ -0,0 +1,17 @@ + + + + + name + kCode + ordering + + zen-remove-tag-3 + zen-balance-outward-2 + EF1F0ADB-1A4D-411A-9BDB-FF9837072CB2 + FC3E137B-6E92-40C9-BAF6-ED13182D7269 + + uuid + 3F3B4BEF-F826-47EA-8575-876F1AEBB999 + + diff --git a/sparkup.py b/sparkup.py new file mode 100644 index 0000000..14b201c --- /dev/null +++ b/sparkup.py @@ -0,0 +1 @@ +sparkup \ No newline at end of file