pmacs3/mode/python.py

718 lines
27 KiB
Python

import commands, os.path, re, string, sys, traceback
import color, completer, context, default, mode, method, regex, tab
import method.introspect
from point import Point
from render import RenderString
from lex import Grammar, PatternRule, RegionRule, OverridePatternRule
from parse import Any, And, Or, Optional, Name, Match, Matchs
from method import Method, arg, Argument
from method.shell import Exec
class StringGrammar1(Grammar):
rules = [
PatternRule('octal', r'\\[0-7]{3}'),
PatternRule('hex', r'\\x[0-9a-fA-F]{2}'),
PatternRule('escaped', r'\\.'),
PatternRule('data', r"[^\\']+"),
]
class StringGrammar2(Grammar):
rules = [
PatternRule('octal', r'\\[0-7]{3}'),
PatternRule('hex', r'\\x[0-9a-fA-F]{2}'),
PatternRule('escaped', r'\\.'),
PatternRule('data', r'[^\\"]+'),
]
class StringGrammar3(Grammar):
rules = [
PatternRule('octal', r'\\[0-7]{3}'),
PatternRule('hex', r'\\x[0-9a-fA-F]{2}'),
PatternRule('escaped', r'\\.'),
PatternRule('data', r"(?:[^\\']|'(?!')|''(?!'))+"),
]
class StringGrammar4(Grammar):
rules = [
PatternRule('octal', r'\\[0-7]{3}'),
PatternRule('hex', r'\\x[0-9a-fA-F]{2}'),
PatternRule('escaped', r'\\.'),
PatternRule('data', r'(?:[^\\"]|"(?!")|""(?!"))+'),
]
class PythonGrammar(Grammar):
rules = [
PatternRule('python.def', '(?<=def )[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('python.class', '(?<=class )[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('python.reserved', '(?:True|None|False|Exception|self)(?![a-zA-Z0-9_])'),
PatternRule('python.keyword', '(?:yield|with|while|try|return|raise|print|pass|or|not|lambda|is|in|import|if|global|from|for|finally|exec|except|else|elif|del|def|continue|class|break|assert|as|and)(?![a-zA-Z0-9_])'),
PatternRule(r"python.builtin", r'(?<!\.)(?:zip|xrange|vars|unicode|unichr|type|tuple|super|sum|str|staticmethod|sorted|slice|setattr|set|round|repr|reduce|raw_input|range|property|pow|ord|open|oct|object|max|min|map|long|locals|list|len|iter|issubclass|isinstance|int|input|id|hex|hash|hasattr|globals|getattr|frozenset|float|filter|file|execfile|eval|enumerate|divmod|dir|dict|delattr|complex|compile|coerce|cmp|classmethod|chr|callable|bool)(?![a-zA-Z0-9_])'),
PatternRule('python.method', r'(?<=\. )[a-zA-Z_][a-zA-Z0-9_]*(?= *\()'),
PatternRule('python.function', r'[a-zA-Z_][a-zA-Z0-9_]*(?= *\()'),
PatternRule('python.system_identifier', '__[a-zA-Z0-9_]+__'),
PatternRule('python.private_identifier', '__[a-zA-Z0-9_]*'),
PatternRule('python.hidden_identifier', '_[a-zA-Z0-9_]*'),
PatternRule('python.identifier', '[a-zA-Z_][a-zA-Z0-9_]*(?![a-zA-Z0-9_\'"])'),
RegionRule('rawstring', 'r"""', StringGrammar4, '"""'),
RegionRule('rawstring', "r'''", StringGrammar3, "'''"),
RegionRule('rawstring', 'r"', StringGrammar2, '"'),
RegionRule('rawstring', "r'", StringGrammar1, "'"),
RegionRule('string', 'u?"""', StringGrammar4, '"""'),
RegionRule('string', "u?'''", StringGrammar3, "'''"),
RegionRule('string', 'u?"', StringGrammar2, '"'),
RegionRule('string', "u?'", StringGrammar1, "'"),
PatternRule('delimiter', r'\(|\)|\[|\]|{|}|,|:|\.|`|=|;|\+=|-=|\*=|/=|//=|%=|&=|\|=|\^=|>>=|<<=|\*\*='),
PatternRule(r"python.integer", r"(?<![\.0-9a-zA-Z_])(?:0|-?[1-9][0-9]*|0[0-7]+|0[xX][0-9a-fA-F]+)[lL]?(?![\.0-9a-zA-Z_])"),
PatternRule(r"python.float", r"(?<![\.0-9a-zA-Z_])(?:-?[0-9]+\.[0-9]*|-?\.[0-9]+|(?:[0-9]|[0-9]+\.[0-9]*|-?\.[0-9]+)[eE][\+-]?[0-9]+)(?![\.0-9a-zA-Z_])"),
PatternRule(r"python.imaginary", r"(?<![\.0-9a-zA-Z_])(?:[0-9]+|(?:[0-9]+\.[0-9]*|\.[0-9]+|(?:[0-9]|[0-9]+\.[0-9]*|\.[0-9]+)[eE][\+-]?[0-9]+)[jJ])(?![\.0-9a-zA-Z_])"),
PatternRule(r"python.operator", r"\+|<>|<<|<=|<|-|>>|>=|>|\*\*|&|\*|\||/|\^|==|//|~|!=|%"),
OverridePatternRule('comment', '#@@:(?P<token>[.a-zA-Z0-9_]+):(?P<mode>[.a-zA-Z0-9_]+) *$'),
PatternRule('comment', '#.*$'),
PatternRule('continuation', r'\\\n$'),
PatternRule('python.decorator', '@[a-zA-Z_][a-zA-Z0-9_]*'),
PatternRule('spaces', ' +'),
PatternRule('eol', r'\n$'),
]
class PythonTabber(tab.StackTabber):
# NOTE: yield might initially seem like an endlevel name, but it's not one.
# NOTE: return should be an endlevel name but for now it can't be one.
endlevel_names = ('pass', 'raise', 'break', 'continue')
startlevel_names = ('if', 'try', 'class', 'def', 'for', 'while', 'try')
def __init__(self, m):
tab.StackTabber.__init__(self, m)
self.base_level = 0
def is_base(self, y):
if y == 0:
# we always know that line 0 is indented at the 0 level
return True
tokens = self.get_tokens(y)
if tokens[0].matchs('python.keyword', self.startlevel_names):
# if a line has no whitespace and begins with something like
# 'while','class','def','if',etc. then we can start at it
return True
else:
# otherwise, we can't be sure that its level is correct
return False
def get_level(self, y):
self._calc_level(y)
return self.lines.get(y)
def _calc_level(self, y):
# ok, so first remember where we are going, and find our starting point
target = y
y = max(0, y - 1)
while not self.is_base(y) and y > 0:
y -= 1
# ok, so clear out our stack and then loop over each line
self.popped = False
self.markers = []
while y <= target:
self.continued = False
self.last_popped = self.popped
self.popped = False
tokens = self.get_tokens(y)
currlvl = self.get_curr_level()
# if we were continuing, let's pop that previous continuation token
# and note that we're continuing
if self.markers and self.markers[-1].name == 'cont':
self.continued = True
self._pop()
# if we haven't reached the target-line yet, we can detect how many
# levels of unindention, if any, the user chose on previous lines
if y < target and len(tokens) > 2:
if self.token_is_space(y, 0):
l = len(tokens[0].string)
else:
l = 0
while currlvl > l:
self._pop()
currlvl = self.get_curr_level()
self.popped = True
# ok, having done all that, we can now process each token
# on the line
for i in range(0, len(tokens)):
currlvl = self._handle_token(currlvl, y, i)
# so let's store the level for this line, as well as some debugging
self.lines[y] = currlvl
self.record[y] = tuple(self.markers)
y += 1
def _handle_close_token(self, currlvl, y, i):
try:
return tab.StackTabber._handle_close_token(self, currlvl, y, i)
except:
return currlvl
def _handle_other_token(self, currlvl, y, i):
w = self.mode.tabwidth
token = self.get_token(y, i)
fqname = token.fqname()
if fqname == 'continuation':
# we need to pop the indentation level over, unless last line was
# also a continued line
if self.continued:
self._opt_append('cont', currlvl, y)
else:
self._opt_append('cont', currlvl + w, y)
elif fqname == 'string.start':
# while inside of a string, there is no indention leve
self._opt_append('string', None, y)
elif fqname == 'string.end':
# since we're done with the string, resume our indentation level
self._opt_pop('string')
elif fqname == 'delimiter':
# we only really care about a colon as part of a one-line statement,
# i.e. "while ok: foo()" or "if True: print 3"
if token.string == ':':
if self.markers and self.markers[-1].name in ('[', '{', '('):
pass
elif self.is_rightmost_token(y, i):
pass
else:
self._pop()
elif fqname == 'python.keyword':
s = token.string
if s in self.endlevel_names and self.is_leftmost_token(y, i):
# we know we'll unindent at least once
self._pop()
self.popped = True
elif s in self.startlevel_names and self.is_leftmost_token(y, i):
# we know we will indent exactly once
self._append(s, currlvl + w, y)
elif s in ('elif', 'else') and self.is_leftmost_token(y, i):
# we know we'll unindent at least to the first if/elif
if not self.popped and not self.last_popped and self._peek_until('if', 'elif'):
self._pop_until('if', 'elif')
currlvl = self.get_curr_level()
self._append(s, currlvl + w, y)
elif s == 'except' and self.is_leftmost_token(y, i):
# we know we'll unindent at least to the first try
if not self.popped and not self.last_popped:
self._pop_until('try')
currlvl = self.get_curr_level()
self._append(s, currlvl + w, y)
elif s == 'finally' and self.is_leftmost_token(y, i):
# we know we'll unindent at least to the first try/except
if not self.popped and not self.last_popped:
self._pop_until('try', 'except')
currlvl = self.get_curr_level()
self._append(s, currlvl + w, y)
return currlvl
class PythonCheckSyntax(Method):
'''Check the syntax of the current python file'''
def _execute(self, w, **vargs):
pythonlib = w.application.config.get('python.lib')
old_path = sys.path
if pythonlib:
sys.path.insert(0, pythonlib)
source = w.buffer.make_string()
try:
code = compile(source, w.buffer.path, 'exec')
w.set_error("Syntax OK")
except Exception, e:
output = traceback.format_exc()
w.application.data_buffer("*PythonSyntax*", output,
switch_to=True,
modename='error')
sys.path = old_path
class PythonDictCleanup(Method):
'''Align assignment blocks and literal dictionaries'''
def _execute(self, w, **vargs):
cursor = w.logical_cursor()
b = w.buffer
# so this is where we will store the groups that we find
groups_by_line = {}
# the regex we will try
regexes = [regex.python_dict_cleanup,
regex.python_assign_cleanup]
# if we aren't in a hash, inform the user and exit
line = b.lines[cursor.y]
myregex = None
for r in regexes:
if r.match(line):
myregex = r
if myregex is None:
raise Exception, "Not a python dict line"
groups_by_line[cursor.y] = myregex.match(line).groups()
# find the beginning of this hash block
start = 0
i = cursor.y - 1
while i >= 0:
line = b.lines[i]
m = myregex.match(line)
if not m:
start = i + 1
break
else:
groups_by_line[i] = m.groups()
i -= 1
# find the end of this hash block
end = len(b.lines) - 1
i = cursor.y + 1
while i < len(b.lines):
line = b.lines[i]
m = myregex.match(line)
if not m:
end = i - 1
break
else:
groups_by_line[i] = m.groups()
i += 1
# assume that the least indented line is correct
indent_w = min([len(groups_by_line[k][0]) for k in groups_by_line])
# find the longest hash key to base all the other padding on
key_w = max([len(groups_by_line[k][1]) for k in groups_by_line])
# for each line, format it correctly
keys = groups_by_line.keys()
keys.sort()
data = ''
for i in keys:
indent_pad = ' ' * indent_w
key = groups_by_line[i][1]
sep = groups_by_line[i][3]
value = groups_by_line[i][5]
key_pad = ' ' * (key_w - len(key))
if sep == '=':
data += indent_pad + key + key_pad + ' ' + sep + ' '
else:
data += indent_pad + key + sep + ' ' + key_pad
data += value + '\n'
# remove the old text and add the new
start_p = Point(0, start)
if end + 1 < len(w.buffer.lines):
end_p = Point(0, end + 1)
else:
end_p = Point(len(w.buffer.lines[-1]), len(w.buffer.lines) - 1)
w.delete(start_p, end_p)
w.insert_string(start_p, data)
class PythonHelp(Exec):
'''Generate a help page on a python object'''
args = [arg('name', t="string", p="Name: ", h='name to get help on')]
def _execute(self, w, **vargs):
name = vargs['name']
stmt = 'try:\n import %s\nexcept:\n pass\nhelp(%s)' % (name, name)
self._doit(w, None, 'python -c "%s"' % stmt)
class PythonInsertTripleSquotes(Method):
'''Insert a triple-quoted string using single-quotes'''
_q = "'''"
def _execute(self, w, **vargs):
w.insert_string_at_cursor('%s%s' % (self._q, self._q))
for i in range(0, 3):
w.backward()
class PythonInsertTripleDquotes(PythonInsertTripleSquotes):
'''Insert a triple-quoted string using double-quotes'''
_q = '"""'
class PythonInitNames(Method):
'''Jump to a function defined in this module'''
def _execute(self, w, **vargs):
w.mode.context.build_name_map()
w.application.set_error("Initialized name maps")
class PythonSemanticComplete(method.introspect.TokenComplete):
_mini_prompt = 'Semantic Complete'
def _min_completion(self, w, t):
a = w.application
a.methods['ipython-path-start'].execute(w, switch=False)
#name = buffer.IperlBuffer.create_name(w.buffer)
name = buffer.IpythonBuffer.create_name(w.buffer)
b = a.get_buffer_by_name(name)
line = w.buffer.lines[t.y]
(x1, x2) = (t.x, t.end_x())
candidates = [t.string + s for s in b.completions(line[x1:x2])]
minlen = None
for candidate in candidates:
if minlen is None:
minlen = len(candidate)
else:
minlen = min(minlen, len(candidate))
return self._prune_candidates(t, minlen, candidates)
class PythonGotoName(Method):
'''Jump to a class or function defined in this module'''
args = [Argument("name", type(""), "pythonname", "Goto Name: ")]
title = 'Name'
def _get_dict(self, w):
return w.mode.context.get_names()
def _execute(self, w, **vargs):
name = vargs['name']
d = self._get_dict(w)
if name in d:
w.goto(Point(0, d[name]))
else:
w.application.set_error("%r %r was not found" % (title, name))
class PythonGotoFunction(PythonGotoName):
'''Jump to a function defined in this module'''
args = [Argument("name", type(""), "pythonfunction", "Goto Function: ")]
title = 'Function'
def _get_dict(self, w):
return w.mode.context.get_functions()
class PythonGotoClass(Method):
'''Jump to a class defined in this module'''
args = [Argument("name", type(""), "pythonclass", "Goto Class: ")]
title = 'Class'
def _get_dict(self, w):
return w.mode.context.get_classes()
class PythonListNames(Method):
'''Show the user all functions defined in this module'''
def _execute(self, w, **vargs):
names = w.mode.context.get_names()
output = '\n'.join(sorted(names)) + "\n"
w.application.data_buffer("*Python-List-Names*", output, switch_to=True)
class PythonBrmFindReferences(Method):
def _execute(self, w, **vargs):
if w.mode.brm is None:
w.set_error('bicycle repairman not installed')
return
base = os.getcwd()
path = w.buffer.path
cursor = w.logical_cursor()
line, col = cursor.y + 1, cursor.x + 1
refs = w.mode.brm.findReferencesByCoordinates(path, line, col)
l, count, tokens = 0, 0, []
if not base.endswith('/'): base += '/'
for r in refs:
f, n, c = r.filename, r.lineno, r.confidence
f = f.replace(base, '')
label = '%s:%d:' % (f, n)
l = max(len(label), l)
tokens.append((label, c))
count += 1
lines = []
for tpl in tokens:
lines.append('%-*s %3d%% confidence' % (l, tpl[0], tpl[1]))
if not tokens:
w.set_error('no references found')
return
data = '\n'.join(lines)
w.application.data_buffer("*References*", data, switch_to=True)
if count == 1:
w.set_error('1 reference found')
else:
w.set_error('%d references found' % count)
class PythonNameCompleter(completer.Completer):
def _get_dict(self, w):
return w.buffer.method.old_window.mode.context.get_names()
def get_candidates(self, s, w=None):
return [n for n in self._get_dict(w) if n.startswith(s)]
class PythonFunctionCompleter(PythonNameCompleter):
def _get_dict(self, w):
return w.buffer.method.old_window.mode.context.get_functions()
class PythonClassCompleter(completer.Completer):
def _get_dict(self, w):
return w.buffer.method.old_window.mode.context.get_classes()
class PythonContext(context.Context):
empty_match = And(Optional(Name('spaces')), Name('eol'))
class_match = And(Optional(Name('spaces')),
Match('python.keyword', 'class'),
Name('spaces'),
Name('python.class'))
func_match = And(Optional(Name('spaces')),
Match('python.keyword', 'def'),
Name('spaces'),
Name('python.def'))
def __init__(self, mode):
self.mode = mode
self.names = None
self.namelines = None
self.classes = None
self.functions = None
# new object methods
def get_functions(self):
if self.functions is None:
self.build_name_map()
return self.functions
def get_classes(self):
if self.classes is None:
self.build_name_map()
return self.classes
def get_function_list(self):
return self._ordered_dict(self.get_functions())
def get_class_list(self):
return self._ordered_dict(self.get_classes())
# overridden object methods
def _init_name_map(self):
self.names = {}
self.classes = {}
self.functions = {}
self.namelines = [(None, None)] * len(self.mode.window.buffer.lines)
def _del_name(self, y, name):
if name:
if name in self.names:
del self.names[name]
if name in self.classes:
del self.classes[name]
if name in self.functions:
del self.functions[name]
self.namelines[y] = (None, None)
def _build_name_map(self, y1, y2, last, curr, stack):
blen = len(self.mode.window.buffer.lines)
highlights = self.mode.window.get_highlighter()
i = y1
abbrev = {}
while i < y2:
tokens = highlights.tokens[i]
g = highlights.tokens[i]
if self.empty_match.match(tokens):
if last is None:
last = i
i += 1
continue
if g[0].name == 'spaces':
j, lvl = 1, len(g[0].string)
else:
j, lvl = 0, 0
while stack and lvl <= stack[-1][0]:
stack.pop(-1)
if last is not None:
curr = '.'.join([x[1] for x in stack])
if curr:
for k in range(last, i):
self.namelines[k] = (curr, None)
last = None
if len(g[j:]) > 3:
found = False
if g[j].name == 'python.keyword' and g[j].string == 'class':
found = True
elif g[j].name == 'python.keyword' and g[j].string == 'def':
found = True
if found:
stack.append([lvl, g[j+2].string])
curr = '.'.join([x[1] for x in stack])
self.names[curr] = i
for k in range(1, len(stack)):
curr = '.'.join(x[1] for x in stack[k:])
if curr not in abbrev:
abbrev[curr] = i
else:
abbrev[curr] = None
else:
curr = '.'.join([x[1] for x in stack])
if i == y2 - 1 and curr != self.namelines[i][0] and y2 < blen:
y2 += 1
if curr:
self.namelines[i] = (curr, None)
i += 1
for name in abbrev:
if abbrev[name] is not None:
self.names[name] = abbrev[name]
if last is not None and y2 < len(self.namelines):
if self.namelines[y2] and self.namelines[y2][0]:
n = len(self.namelines[y2][0].split('.'))
curr = '.'.join([x[1] for x in stack[:n]])
if curr:
for k in range(last, y2):
self.namelines[k] = (curr, None)
# white is for delimiters, operators, numbers
default = ('default', 'default')
# magenta is for reserved words
lo_magenta = ('magenta202', 'default')
hi_magenta = ('magenta505', 'default')
# red is for comments
lo_red = ('red300', 'default')
hi_red = ('red511', 'default')
# orange is unused
hi_orange = ('yellow531', 'default')
lo_orange = ('yellow520', 'default')
# yellow is for class names
hi_yellow = ('yellow551', 'default')
lo_yellow = ('yellow330', 'default')
# green is for strings
lo_green = ('green030', 'default')
hi_green = ('green050', 'default')
# cyan is for keywords and some operators
lo_cyan = ('cyan033', 'default')
hi_cyan = ('cyan155', 'default')
# blue is for functions and methods
lo_blue = ('blue113', 'default')
hi_blue = ('blue225', 'default')
class Python(mode.Fundamental):
description = '''
This programming mode is designed to edit Python source files. It
features parenthesis matching, syntax highlighting, indentation
assistance and syntax checking. It can also find classes and functions
by name, provide scope context in the status bar, and has an optional
context header. Finally, it can semantically complete tokens and
provide help output via the python interpreter.
'''
name = 'Python'
extensions = ['.py']
detection = [re.compile('^#!(?:.+[/ ])python')]
tabbercls = PythonTabber
grammar = PythonGrammar
opentokens = ('delimiter',)
opentags = {'(': ')', '[': ']', '{': '}'}
closetokens = ('delimiter',)
closetags = {')': '(', ']': '[', '}': '{'}
commentc = '#'
colors = {
'python.def': hi_blue,
'python.class': hi_yellow,
'python.decorator': lo_magenta,
'python.reserved': hi_magenta,
'python.keyword': hi_cyan,
'python.builtin': hi_cyan,
'python.method': default,
'python.function': default,
'python.system_identifier': hi_cyan,
'python.private_identifier': default,
'python.hidden_identifier': default,
'python.identifier': default,
'python.integer': default,
'python.float': default,
'python.imaginary': default,
'python.operator': default,
'rawstring.start': lo_green,
'rawstring.end': lo_green,
'rawstring.data': hi_green,
'rawstring.null': hi_green,
'rawstring.escaped': hi_magenta,
}
config = {
'python.lib': '.',
}
lconfig = {
'ignore_suffix': ['.pyc', '.pyo'],
}
actions = [PythonInitNames, PythonListNames, PythonGotoName, PythonHelp,
PythonGotoFunction, PythonGotoClass, PythonCheckSyntax,
PythonDictCleanup, PythonSemanticComplete,
PythonBrmFindReferences,
PythonInsertTripleSquotes, PythonInsertTripleDquotes]
completers = {
"pythonname": PythonNameCompleter(None),
"pythonfunction": PythonFunctionCompleter(None),
"pythonclass": PythonClassCompleter(None),
}
format = "%(flag)s %(bname)s (%(mname)s) %(indent)s %(cursor)s %(perc)s [%(name)s] %(vc-info)s"
header_size = 3
def get_status_names(self):
names = mode.Fundamental.get_status_names(self)
c = self.window.logical_cursor()
names['name'] = self.context.get_line_name(c.y)
return names
# xyz
def get_header(self):
fg, bg = "default", "blue"
if self.tabber is None:
s = "Header support is not available for this mode"
hs = [[RenderString(s=s, attrs=color.build(fg, bg))]]
while len(hs) < 3:
hs.insert(0, [RenderString(s='', attrs=color.build(fg, bg))])
return hs
w = self.window
y = self.window.first.y
if self.window.first.x > 0:
y += 1
lvl = self.tabber.get_level(y)
markers = self.tabber.record[y]
if w.buffer.is_whitespace(y):
ws = None
else:
ws = w.buffer.count_leading_whitespace(y)
hs = []
i = len(markers) - 1
while i >= 0 and len(hs) < 3:
marker = markers[i]
i -= 1
if marker.y == y:
continue
if ws and marker.level > ws:
continue
s = w.buffer.lines[marker.y][:w.width - 1]
hs.insert(0, [RenderString(s=s, attrs=color.build(fg, bg))])
while len(hs) < 3:
hs.insert(0, [RenderString(s='', attrs=color.build(fg, bg))])
return hs
def __init__(self, w):
mode.Fundamental.__init__(self, w)
self.add_bindings('close-paren', (')',))
self.add_bindings('close-brace', ('}',))
self.add_bindings('close-bracket', (']',))
self.add_bindings('python-goto-name', ('C-c M-g',))
self.add_bindings('python-goto-function', ('C-c M-f',))
self.add_bindings('python-goto-class', ('C-c M-c',))
self.add_bindings('python-check-syntax', ('C-c s',))
self.add_bindings('python-dict-cleanup', ('C-c h',))
self.add_bindings('python-insert-triple-squotes', ('C-c M-\'',))
self.add_bindings('python-insert-triple-dquotes', ('C-c M-"',))
self.add_bindings('python-semantic-complete', ('C-c TAB',))
self.context = PythonContext(self)
# bicycle repairman!
try:
import bike
self.brm = bike.init()
# turn off brm's annoying STDERR printing
f = open('/dev/null', 'w')
self.brm.setProgressLogger(f)
self.brm.setWarningLogger(f)
except ImportError:
self.brm = None
install = Python.install