From 451bc1f0f63e1c74e8d3f8de2d1bf09e5a2b0232 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 4 May 2010 21:15:17 -0400 Subject: [PATCH] All set. Best of Sparkup + Zen Coding for now --- Commands/Sizzle Expand.tmCommand | 36 + Commands/Sparkup Expand.tmCommand | 16 + Commands/Update Bundle via Git.tmCommand | 26 + Macros/Balance Tag.tmMacro | 40 + Macros/Remove Tag.tmMacro | 61 ++ README => Readme.mkd | 0 Support/applescript.py | 7 + Support/balance_tag.py | 8 + Support/remove_tag.py | 7 + Support/sparkup.py | 1087 +++++++++++++++++++ Support/sparkup.pyc | Bin 0 -> 26976 bytes Support/zen_editor.py | 203 ++++ Support/zen_editor.pyc | Bin 0 -> 8504 bytes Support/zencoding/__init__.py | 0 Support/zencoding/__init__.pyc | Bin 0 -> 158 bytes Support/zencoding/filters/__init__.py | 23 + Support/zencoding/filters/comment.py | 47 + Support/zencoding/filters/escape.py | 32 + Support/zencoding/filters/format-css.py | 25 + Support/zencoding/filters/format.py | 182 ++++ Support/zencoding/filters/haml.py | 143 +++ Support/zencoding/filters/html.py | 135 +++ Support/zencoding/filters/xsl.py | 31 + Support/zencoding/html_matcher.py | 273 +++++ Support/zencoding/html_matcher.pyc | Bin 0 -> 8904 bytes Support/zencoding/my_zen_settings.py | 8 + Support/zencoding/stparser.py | 161 +++ Support/zencoding/stparser.pyc | Bin 0 -> 5221 bytes Support/zencoding/zen_actions.py | 645 +++++++++++ Support/zencoding/zen_actions.pyc | Bin 0 -> 19539 bytes Support/zencoding/zen_core.py | 1238 ++++++++++++++++++++++ Support/zencoding/zen_core.pyc | Bin 0 -> 37387 bytes Support/zencoding/zen_editor.py | 128 +++ Support/zencoding/zen_settings.py | 727 +++++++++++++ Support/zencoding/zen_settings.pyc | Bin 0 -> 30151 bytes info.plist | 17 + sparkup.py | 1 + 37 files changed, 5307 insertions(+) create mode 100644 Commands/Sizzle Expand.tmCommand create mode 100644 Commands/Sparkup Expand.tmCommand create mode 100644 Commands/Update Bundle via Git.tmCommand create mode 100644 Macros/Balance Tag.tmMacro create mode 100644 Macros/Remove Tag.tmMacro rename README => Readme.mkd (100%) create mode 100644 Support/applescript.py create mode 100644 Support/balance_tag.py create mode 100644 Support/remove_tag.py create mode 100644 Support/sparkup.py create mode 100644 Support/sparkup.pyc create mode 100644 Support/zen_editor.py create mode 100644 Support/zen_editor.pyc create mode 100644 Support/zencoding/__init__.py create mode 100644 Support/zencoding/__init__.pyc create mode 100644 Support/zencoding/filters/__init__.py create mode 100644 Support/zencoding/filters/comment.py create mode 100644 Support/zencoding/filters/escape.py create mode 100644 Support/zencoding/filters/format-css.py create mode 100644 Support/zencoding/filters/format.py create mode 100644 Support/zencoding/filters/haml.py create mode 100644 Support/zencoding/filters/html.py create mode 100644 Support/zencoding/filters/xsl.py create mode 100644 Support/zencoding/html_matcher.py create mode 100644 Support/zencoding/html_matcher.pyc create mode 100644 Support/zencoding/my_zen_settings.py create mode 100644 Support/zencoding/stparser.py create mode 100644 Support/zencoding/stparser.pyc create mode 100644 Support/zencoding/zen_actions.py create mode 100644 Support/zencoding/zen_actions.pyc create mode 100644 Support/zencoding/zen_core.py create mode 100644 Support/zencoding/zen_core.pyc create mode 100644 Support/zencoding/zen_editor.py create mode 100644 Support/zencoding/zen_settings.py create mode 100644 Support/zencoding/zen_settings.pyc create mode 100644 info.plist create mode 100644 sparkup.py 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 0000000000000000000000000000000000000000..c0834306570eb56d1a2b6be62b2d64213c22fa10 GIT binary patch literal 26976 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%0A1XtN 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 PnLTgrncNe=Y~%f3faH}n literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..4c28c1c478946fcfb207daaa16451dd6f9265e9f GIT binary patch literal 8504 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 Zlh~Lj=ekqJJMu}F#)=K%|19sd{{hNB5*Ppg literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a68dc8a5070138cadc99eade46fc3e088f8d46c2 GIT binary patch literal 158 zcmcckiI*$mZjE;`0~9a' + 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 0000000000000000000000000000000000000000..70eadfe871df5587f5cc518e5bf2f210e97da5fb GIT binary patch literal 8904 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 c&85XU{?Eu$yL{#H+@8UO$Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c3d6ac98c6900e0d5fef6a6c4aaf168080eab607 GIT binary patch literal 5221 zcmcIoO>^7G5p9qXCDNj7N&YCO@_|xPB~rFUdpG6Us1#eWtW7zo<+@-y&Qg?75HS*A zkpPVudZjIEbI|6J(;jlkCAZ|3TW)*Xe~?3t`4jmA$?G0~q*6Xwk}#U-8T1UgU%zhP zpZ}>=y8nLnn+|0^Gx)!WOaBW^NYp~>P*|WNhk6BS70BUnk&cSgD#{%CQ`DN0{uCWe zQ*WAD)8wcEscAZMs8yoyDz#=Pyhg2A3TLQQrf`;86$;DLxSi~YGn+1+8qR`R%vBBRe@2Vo5b#~hp~I_=eOOD?%w@H zrLq|eO*cu`aYuTnPAbuV@l$i4(9E*Ylp1uB!AaAK<{USGElDK zQ~KP6n@l>AAE}e`g113$h&5r%%vX(64HDfL4EK9cr*Q=QsWsDoG>pTZYCIbb21#lf07yrG z+0e$~acyuS@bdjAj*RcKyL5^7DV3eFV-Lpo^ZhvJE8myH`F=kMhdmxL?tDK?I==7m zy73vM_aP7JvG;y}OS45v#gfxu312J_zg|Jd;7?o&2cL0hGMsc1O{M%e(#@7G5QmaoI}I`*tSR;$2)Ao z$RUH(1v)Fx|Mz=)w!OPB@Pv|96BmfVHBgCLr{J>bz zb$BE5L$JS}s<%;KBCz3kX8@f+-xY6cO|F9EJ+88=L45*JI7l1}LQp-`MC4Ttl{Y^7csksy{FwgC{ye+4vByOu68uH~}kIe2wqe zPm&&_ImRq?2xsuIB}ntd5`*IdMKCKx>~_l%>~Xk`2)fwh2T>dby`HE@l`rQuyeJq9 zR2*6aFJLSP-g@-N+xhL2t>;fZZ`OE~@Q>V_+f0Z{)OeS%Ky;-_4|@i2N7Ns4z=sTf zvk2W5?vuc|2dTb}hN?wp#;Fu>IZIS>s?H_nMzO;u;y`3eNF02R&hdAQOBn8q0Peq2 zyFjN7ol0~7_>f{~BjqN-g!BFl09xj4NHDA&+IUlixJZ$Ox&+H9l1Pz~2bd^Hj=?mF zlp@zGnBYF*Vbqse2BCvJ&Jc_fYG=umMFD0cF=ZRRM5*(vHjBs5TWBifr(9ZS7e00; zEu-Bi)={V=4I#vW^rYgJH!aeUD)am#m4!P5#F6*$P_q)ElM8mEfEtlq*DY(0FH}qUeMQ*AYgX2Dq-J9 z;3FH&Om2kDUIJ{-YJ6<{XEYgD+#yPbiRyICecp|nPCLFetJfZqENR(d}}V}Us6MaJX0#S_T#qw)?~DC;pQu;*NJu~_^qwvf5} z2%>x-+$ntP@7~)J+1rZ&pLYvqLD@-~s}(9S(5lWnu1}onwx(gi;%gh1euL)tH8Vj@ zSZ#qBIIAd?j!+B82~c6Y-37wHR79_51bkg^3jPYTk!58&x(R$Cw_8M7Xkm#!#sOqF zf#^S>sgym{86r~N_DTit&q)+-UKbXGN<$hlT}=sLn-A@uEcnd)=#Zvx-L8$DY#C3P-EeCm0Yl-W^`f zkhmZZ--YTJmu9k~8TRmX@$t2SBqUBoqlEMw2FL%Hq>;b9k47SLj{hh;k)^HYvZ}~b z!Tf?&@=ubJiWF?X1h@vwBcy_a1Zj!$m5@q@zQ!0}LD~1_V_@N9ddUn0Q)T3WwKF3Y zo6V$x89@q{*9=tcp{bPZ+pXITbl2?%1I@3w9hI5@RKXbobTCMh0VEL;R&pTxk;h@~ z{fX2htdJFP^4_Jr6>QY3mC=g(r51`bGMx$VW-ISj*vVtZj#*&>AN+FMk(Y4P5(8ah zH+VngW&{ce{v)r!!ZkEfOsU=lVdXw{_&u(Pv{Q8$F;|>Tphl<)FI+I8?+bW1G8y#S zd=uN`#Ac!4$yxz{@wtH>*4HvV-G%^wDD7-I`d#aNy}XV2CIN zZRD!)rp2MkPfE53OwtHs67)uzJT{`oQ77ao3z_Oeir&S?5BhW o%+s2;C^=UOTzR1_lpOpP`FCl)T%P-3d9i%CyilI6I#Y%J0l9_4H~;_u literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..c4c754c904384d6f6dd3aef0d15af9f1ff12260e GIT binary patch literal 19539 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 jR!6I2lk?RB)rs*Lli*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 0000000000000000000000000000000000000000..c6288c650b2f1a960e906e0432f1013747a1b9be GIT binary patch literal 37387 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..63e938d1970d8cbd88d0766a2172e0d1caf3cf0c GIT binary patch literal 30151 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 Qre22Xr@$GC=PURB0R6*xU;qFB literal 0 HcmV?d00001 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