You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.8 KiB
Python

#!/usr/bin/env python
# encoding: utf-8
"""Parsing of snippet files."""
from collections import defaultdict
class _LineIterator(object):
"""Convenience class that keeps track of line numbers."""
def __init__(self, text):
self._line_index = None
self._lines = enumerate(text.splitlines(True), 1)
def __iter__(self):
return self
def __next__(self):
"""Returns the next line."""
self._line_index, line = next(self._lines)
return line
next = __next__ # for python2
@property
def line_index(self):
"""The 1 based line index in the current file."""
return self._line_index
def _handle_snippet_or_global(line, lines, globals):
"""Parses the snippet that begins at the current line."""
descr = ""
opts = ""
# Ensure this is a snippet
snip = line.split()[0]
# Get and strip options if they exist
remain = line[len(snip):].strip()
words = remain.split()
if len(words) > 2:
# second to last word ends with a quote
if '"' not in words[-1] and words[-2][-1] == '"':
opts = words[-1]
remain = remain[:-len(opts) - 1].rstrip()
# Get and strip description if it exists
remain = remain.strip()
if len(remain.split()) > 1 and remain[-1] == '"':
left = remain[:-1].rfind('"')
if left != -1 and left != 0:
descr, remain = remain[left:], remain[:left]
# The rest is the trigger
trig = remain.strip()
if len(trig.split()) > 1 or "r" in opts:
if trig[0] != trig[-1]:
return "error", ("Invalid multiword trigger: '%s'" % trig,
lines.line_index)
trig = trig[1:-1]
end = "end" + snip
content = ""
found_end = False
for line in lines:
if line.rstrip() == end:
content = content[:-1] # Chomp the last newline
found_end = True
break
content += line
if not found_end:
return "error", ("Missing 'endsnippet' for %r" % trig, lines.line_index)
if snip == "global":
globals[trig].append(content)
elif snip == "snippet":
return "snippet", (trig, content, descr, opts, globals)
else:
return "error", ("Invalid snippet type: '%s'" % snip, lines.line_index)
def _head_tail(line):
"""Returns the first word in 'line' and the rest of 'line' or None if the
line is too short."""
generator = (t.strip() for t in line.split(None, 1))
head = next(generator).strip()
tail = ''
try:
tail = next(generator).strip()
except StopIteration:
pass
return head, tail
def parse_snippets_file(data):
"""Parse 'data' assuming it is a snippet file. Yields events in the
file."""
globals = defaultdict(list)
lines = _LineIterator(data)
for line in lines:
if not line.strip():
continue
head, tail = _head_tail(line)
if head == "extends":
if tail:
yield "extends", ([p.strip() for p in tail.split(',')],)
else:
yield "error", ("'extends' without file types",
lines.line_index)
elif head in ("snippet", "global"):
snippet = _handle_snippet_or_global(line, lines, globals)
if snippet is not None:
yield snippet
elif head == "clearsnippets":
yield "clearsnippets", (tail.split(),)
elif head == "priority":
try:
priority = int(tail.split()[0])
yield "priority", (priority,)
except (ValueError, IndexError):
yield "error", ("Invalid priority %r" % tail, lines.line_index)
elif head and not head.startswith('#'):
yield "error", ("Invalid line %r" % line.rstrip(), lines.line_index)