1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
|
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2008 - 2014 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# See http://www.gnu.org/licenses/ for more information.
"""
Accessing the snippets data.
"""
import collections
import functools
import itertools
import random
import re
import app
import icons
import symbols
textvars = collections.namedtuple('textvars', 'text variables')
# cache parsed snippets
_cache = {}
# match variables in a '-*- ' line
_variables_re = re.compile(r'\s*?([a-z]+(?:-[a-z]+)*)(?::[ \t]*(.*?))?;')
# match expansions $$, $NAME or ${text} (the latter may contain escaped right brace: '\}')
_expansions_re = re.compile(r'\$(?P<_bracket_>\{)?((?(_bracket_)(?:\\\}|[^\}])*|(?:\$|[A-Z]+(?:_[A-Z]+)*)))(?(_bracket_)\})')
# builtin snippets
from .builtin import builtin_snippets
def memoize(f):
"""Decorator memoizing stuff for a name."""
@functools.wraps(f)
def func(name):
try:
result = _cache[name][f]
except KeyError:
result = _cache.setdefault(name, {})[f] = f(name)
return result
return func
def unmemoize(f):
"""Decorator forgetting memoized information for a name."""
@functools.wraps(f)
def func(name, *args, **kwargs):
try:
del _cache[name]
except KeyError:
pass
return f(name, *args, **kwargs)
return func
def settings():
return app.settings("snippets")
def names():
"""Yields the names of available builtin snippets."""
s = settings()
return set(filter(lambda name: not s.value(name+"/deleted"),
itertools.chain(builtin_snippets, s.childGroups())))
def title(name, fallback=True):
"""Returns the title of the specified snippet or the empty string.
If fallback, returns a shortened display of the text if no title is
available.
"""
s = settings()
title = s.value(name+"/title")
if title:
return title
try:
t = builtin_snippets[name]
except KeyError:
pass
else:
if t.title:
return t.title() # call to translate
if fallback:
# no title found, send shorttext instead
return shorttext(name)
def text(name):
"""Returns the full snippet text for the name, or the empty string."""
text = settings().value(name+"/text")
if text:
return text
try:
t = builtin_snippets[name]
except KeyError:
return ""
return t.text
@memoize
def shorttext(name):
"""Returns the abridged text, in most cases usable for display or matching."""
return maketitle(get(name).text)
def maketitle(text):
"""Returns the text abridged, usable as a title."""
lines = _expansions_re.sub(' ... ', text).splitlines()
if not lines:
return ''
start, end = 0, len(lines) - 1
while start < end and (not lines[start] or lines[start].isspace()):
start += 1
while end > start and (not lines[end] or lines[end].isspace()):
end -= 1
if end == start:
return lines[start]
else:
return lines[start] + " ... " + lines[end]
@memoize
def get(name):
"""Returns a tuple (text, variables) for the specified name.
Equivalent to parse(text(name)). See parse().
"""
return parse(text(name))
def parse(text):
"""Parses a piece of text and returns a named tuple (text, variables).
text is the template text, with lines starting with '-*- ' removed.
variables is a dictionary containing variables read from lines starting
with '-*- '.
The syntax is as follows:
-*- name: value; name1: value2; (etc)
Names without value are also possible:
-*- name;
In that case the value is set to True.
"""
lines = text.split('\n')
start = 0
while start < len(lines) and lines[start].startswith('-*- '):
start += 1
t = '\n'.join(lines[start:])
d = dict(m.groups(True) for l in lines[:start] for m in _variables_re.finditer(l))
return textvars(t, d)
def icon(name):
"""Returns an icon if defined."""
d = get(name).variables
icon = d.get('icon')
if icon:
return icons.get(icon)
icon = d.get('symbol')
if icon:
return symbols.icon(icon)
@unmemoize
def delete(name):
"""Deletes a snippet. For builtins, name/deleted is set to true."""
s = settings()
s.remove(name)
if name in builtin_snippets:
s.setValue(name+"/deleted", True)
def name(names):
"""Returns a name to be used for a new snippet..
names is a list of strings for which the newly returned name will be unique.
"""
while True:
u = "n{0:06.0f}".format(random.random()*1000000)
if u not in names:
break
return u
@unmemoize
def save(name, text, title=None):
"""Stores a snippet."""
try:
t = builtin_snippets[name]
except KeyError:
# not builtin
pass
else:
# builtin
if not title or (t.title and title == t.title()):
title = None
if text == t.text:
text = None
s = settings()
if title or text:
s.beginGroup(name)
s.setValue("text", text) if text else s.remove("text")
s.setValue("title", title) if title else s.remove("title")
else:
# the snippet exactly matches the builtin, no saving needed
s.remove(name)
def isoriginal(name):
"""Returns True if the built-in snippet is not changed or deleted."""
return name in builtin_snippets and name not in settings().childGroups()
def expand(text):
r"""Yields tuples (text, expansion) for text.
Parses text for expressions like '$VAR_NAME', '${other text}' or '$$'.
An expansion starts with a '$' and is an uppercase word (which can have
single underscores in the middle), or other text between braces (which may
contain a right brace escaped: '\}', those are already unescaped by this
function).
One of (text, expansion) may be an empty string.
"""
pos = 0
for m in _expansions_re.finditer(text):
expansion = m.group(2) if not m.group(1) else m.group(2).replace('\\}', '}')
yield text[pos:m.start()], expansion
pos = m.end()
if pos < len(text):
yield text[pos:], ''
|