mirror of
https://github.com/kennethreitz-archive/krvim.git
synced 2026-06-05 23:40:18 +00:00
Initial import.
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
syntax: glob
|
||||
*.pyc
|
||||
envs
|
||||
.ropeproject
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
if __name__ == '__main__':
|
||||
py_src = file('complexity.py').read()
|
||||
vim_src = file('base.vim').read()
|
||||
combined_src = vim_src % dict(python_source=py_src)
|
||||
file('complexity.vim', 'w').write(combined_src)
|
||||
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import os
|
||||
import compiler#{{{
|
||||
from compiler.visitor import ASTVisitor
|
||||
|
||||
def compute_code_complexity(code):
|
||||
return ModuleComplexity(code).compute_complexity()
|
||||
|
||||
#}}}
|
||||
|
||||
class ASTComplexity(ASTVisitor):
|
||||
def __init__(self, node):
|
||||
ASTVisitor.__init__(self)
|
||||
self.score = 1
|
||||
self._in_conditional = False
|
||||
self.results = ComplexityResults()
|
||||
self.process_root_node(node)
|
||||
|
||||
def process_root_node(self, node):
|
||||
for child in node.getChildNodes():
|
||||
compiler.walk(child, self, walker=self)
|
||||
|
||||
def dispatch_children(self, node):#{{{
|
||||
for child in node.getChildNodes():
|
||||
self.dispatch(child)
|
||||
|
||||
def visitFunction(self, node):
|
||||
score=ASTComplexity(node).score
|
||||
score = ComplexityScore(name=node.name,
|
||||
type_='function',
|
||||
score=score,
|
||||
start_line=node.lineno,
|
||||
end_line=self.highest_line_in_node(node))
|
||||
self.results.add(score)
|
||||
|
||||
def visitClass(self, node):
|
||||
complexity = ASTComplexity(node)
|
||||
self.results.add(ComplexityScore(
|
||||
name=node.name,
|
||||
type_='class',
|
||||
score=complexity.score,
|
||||
start_line=node.lineno,
|
||||
end_line=self.highest_line_in_node(node)))
|
||||
for score in complexity.results.ordered_by_line():
|
||||
score.name = '%s.%s' % (node.name, score.name)
|
||||
self.results.add(score)
|
||||
|
||||
def highest_line_in_node(self, node, highest=0):
|
||||
children = node.getChildNodes()
|
||||
if node.lineno > highest:
|
||||
highest = node.lineno
|
||||
child_lines = map(self.highest_line_in_node,
|
||||
node.getChildNodes())
|
||||
lines = [node.lineno] + child_lines
|
||||
return max(lines)
|
||||
|
||||
def visitIf(self, node):
|
||||
tests = self._tests_for_if(node)
|
||||
self.score += len(tests)
|
||||
self._in_conditional = True
|
||||
for test in tests:
|
||||
self.dispatch(test)
|
||||
self._in_conditional = False
|
||||
self.dispatch_children(node)
|
||||
|
||||
def _tests_for_if(self, if_node):
|
||||
try:
|
||||
return [test for test, body in if_node.tests]
|
||||
except AttributeError:
|
||||
return [if_node.test]
|
||||
|
||||
visitGenExprIf = visitListCompIf = visitIfExp = visitIf
|
||||
|
||||
def __processDecisionPoint(self, node):
|
||||
self.score += 1
|
||||
self.dispatch_children(node)
|
||||
|
||||
visitFor = visitGenExprFor \
|
||||
= visitListCompFor \
|
||||
= visitWhile = __processDecisionPoint
|
||||
|
||||
def _visit_logical_operator(self, node):
|
||||
self.dispatch_children(node)
|
||||
if self._in_conditional:
|
||||
self.score += len(node.getChildren()) - 1
|
||||
|
||||
visitAnd = _visit_logical_operator
|
||||
visitOr = _visit_logical_operator
|
||||
|
||||
def visitTryExcept(self, node):
|
||||
self.dispatch_children(node)
|
||||
self.score += len(node.handlers)
|
||||
#}}}
|
||||
|
||||
class ModuleComplexity:
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
self.node = compiler.parse(code)
|
||||
|
||||
def compute_complexity(self):
|
||||
complexity = ASTComplexity(self.node)
|
||||
self.add_module_results(complexity, self.code)
|
||||
return complexity
|
||||
|
||||
def add_module_results(self, complexity, code_or_node):
|
||||
end_line = max(1, code_or_node.count('\n') + 1)
|
||||
complexity.results.add(
|
||||
ComplexityScore(name='<module>',
|
||||
type_='module',
|
||||
score=complexity.score,
|
||||
start_line=1,
|
||||
end_line=end_line))
|
||||
|
||||
class ComplexityResults:#{{{
|
||||
def __init__(self):
|
||||
self._scores = []
|
||||
|
||||
def add(self, score):
|
||||
self._scores.append(score)
|
||||
|
||||
def ordered_by_line(self):
|
||||
OBJECT_SORT_PRIORITY = ['module', 'function', 'class']
|
||||
def sort_key(score):
|
||||
return (score.start_line,
|
||||
OBJECT_SORT_PRIORITY.index(score.type_))
|
||||
return sorted(self._scores, key=sort_key)
|
||||
|
||||
def named(self, name):
|
||||
return [s for s in self._scores if s.name == name][0]
|
||||
|
||||
|
||||
class ComplexityScore:
|
||||
def __init__(self, name, type_, score, start_line, end_line):
|
||||
self.name = name
|
||||
self.type_ = type_
|
||||
self.score = score
|
||||
self.start_line = start_line
|
||||
self.end_line = end_line
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
'ComplexityScore(name=%s, score=%s, start_line=%s, end_line=%s)'
|
||||
% (repr(self.name),
|
||||
repr(self.score),
|
||||
repr(self.start_line),
|
||||
repr(self.end_line)))
|
||||
|
||||
|
||||
def complexity_name(complexity):
|
||||
if complexity > 14:
|
||||
return 'high_complexity'
|
||||
elif complexity > 7:
|
||||
return 'medium_complexity'
|
||||
else:
|
||||
return 'low_complexity'
|
||||
|
||||
|
||||
def show_complexity():
|
||||
current_file = vim.current.buffer.name
|
||||
try:
|
||||
scores = compute_scores_for(current_file)
|
||||
except (IndentationError, SyntaxError):
|
||||
return
|
||||
|
||||
old_complexities = get_old_complexities(current_file)
|
||||
new_complexities = compute_new_complexities(scores)
|
||||
line_changes = compute_line_changes(old_complexities, new_complexities)
|
||||
update_line_markers(line_changes)
|
||||
|
||||
|
||||
def compute_scores_for(filename=None, code=None):
|
||||
if filename:
|
||||
code = open(filename).read()
|
||||
scores = compute_code_complexity(code).results.ordered_by_line()
|
||||
return scores
|
||||
|
||||
|
||||
def get_old_complexities(current_file):
|
||||
lines = list_current_signs(current_file)
|
||||
|
||||
old_complexities = {}
|
||||
for line in lines:
|
||||
if '=' not in line:
|
||||
continue
|
||||
|
||||
tokens = line.split()
|
||||
variables = dict(token.split('=') for token in tokens)
|
||||
line = int(variables['line'])
|
||||
complexity = variables['name']
|
||||
old_complexities[line] = complexity
|
||||
|
||||
return old_complexities
|
||||
|
||||
|
||||
def list_current_signs(current_file):
|
||||
vim.command('redir => s:complexity_sign_list')
|
||||
vim.command('silent sign place file=%s' % current_file)
|
||||
vim.command('redir END')
|
||||
|
||||
sign_list = vim.eval('s:complexity_sign_list')
|
||||
lines = [line.strip() for line in sign_list.split('\n')]
|
||||
return lines
|
||||
|
||||
|
||||
def compute_line_changes(cached_complexities, new_scores):
|
||||
changes = {}
|
||||
for line, complexity in new_scores.iteritems():
|
||||
if complexity != cached_complexities.get(line, None):
|
||||
changes[line] = complexity
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def compute_new_complexities(scores):
|
||||
new_scores = {}
|
||||
for score in scores:
|
||||
for line in range(score.start_line, score.end_line + 1):
|
||||
new_scores[line] = complexity_name(score.score)
|
||||
return new_scores
|
||||
|
||||
|
||||
def update_line_markers(line_changes):
|
||||
filename = vim.current.buffer.name
|
||||
for line, complexity in line_changes.iteritems():
|
||||
vim.command(':sign unplace %i' % line)
|
||||
vim.command(':sign place %i line=%i name=%s file=%s' %
|
||||
(line, line, complexity, filename))#}}}
|
||||
|
||||
|
||||
def main():
|
||||
if sys.stdin.isatty() and len(sys.argv) < 2:
|
||||
print "Missing filename"
|
||||
return
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
file_name = sys.argv[1]
|
||||
content = None
|
||||
else:
|
||||
file_name = None
|
||||
content = sys.stdin.read()
|
||||
|
||||
try:
|
||||
for score in compute_scores_for(file_name, content):
|
||||
print score.start_line, score.end_line, score.score, score.type_
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 'vim' not in globals():
|
||||
main()
|
||||
@@ -0,0 +1,290 @@
|
||||
" complexity.vim
|
||||
" Gary Bernhardt (http://blog.extracheese.org)
|
||||
"
|
||||
" This will add cyclomatic complexity annotations to your source code. It is
|
||||
" no longer wrong (as previous versions were!)
|
||||
|
||||
if !has('signs')
|
||||
finish
|
||||
endif
|
||||
if !has('python')
|
||||
finish
|
||||
endif
|
||||
python << endpython
|
||||
import vim
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import os
|
||||
import compiler#{{{
|
||||
from compiler.visitor import ASTVisitor
|
||||
|
||||
def compute_code_complexity(code):
|
||||
return ModuleComplexity(code).compute_complexity()
|
||||
|
||||
#}}}
|
||||
|
||||
class ASTComplexity(ASTVisitor):
|
||||
def __init__(self, node):
|
||||
ASTVisitor.__init__(self)
|
||||
self.score = 1
|
||||
self._in_conditional = False
|
||||
self.results = ComplexityResults()
|
||||
self.process_root_node(node)
|
||||
|
||||
def process_root_node(self, node):
|
||||
for child in node.getChildNodes():
|
||||
compiler.walk(child, self, walker=self)
|
||||
|
||||
def dispatch_children(self, node):#{{{
|
||||
for child in node.getChildNodes():
|
||||
self.dispatch(child)
|
||||
|
||||
def visitFunction(self, node):
|
||||
score=ASTComplexity(node).score
|
||||
score = ComplexityScore(name=node.name,
|
||||
type_='function',
|
||||
score=score,
|
||||
start_line=node.lineno,
|
||||
end_line=self.highest_line_in_node(node))
|
||||
self.results.add(score)
|
||||
|
||||
def visitClass(self, node):
|
||||
complexity = ASTComplexity(node)
|
||||
self.results.add(ComplexityScore(
|
||||
name=node.name,
|
||||
type_='class',
|
||||
score=complexity.score,
|
||||
start_line=node.lineno,
|
||||
end_line=self.highest_line_in_node(node)))
|
||||
for score in complexity.results.ordered_by_line():
|
||||
score.name = '%s.%s' % (node.name, score.name)
|
||||
self.results.add(score)
|
||||
|
||||
def highest_line_in_node(self, node, highest=0):
|
||||
children = node.getChildNodes()
|
||||
if node.lineno > highest:
|
||||
highest = node.lineno
|
||||
child_lines = map(self.highest_line_in_node,
|
||||
node.getChildNodes())
|
||||
lines = [node.lineno] + child_lines
|
||||
return max(lines)
|
||||
|
||||
def visitIf(self, node):
|
||||
tests = self._tests_for_if(node)
|
||||
self.score += len(tests)
|
||||
self._in_conditional = True
|
||||
for test in tests:
|
||||
self.dispatch(test)
|
||||
self._in_conditional = False
|
||||
self.dispatch_children(node)
|
||||
|
||||
def _tests_for_if(self, if_node):
|
||||
try:
|
||||
return [test for test, body in if_node.tests]
|
||||
except AttributeError:
|
||||
return [if_node.test]
|
||||
|
||||
visitGenExprIf = visitListCompIf = visitIfExp = visitIf
|
||||
|
||||
def __processDecisionPoint(self, node):
|
||||
self.score += 1
|
||||
self.dispatch_children(node)
|
||||
|
||||
visitFor = visitGenExprFor \
|
||||
= visitListCompFor \
|
||||
= visitWhile = __processDecisionPoint
|
||||
|
||||
def _visit_logical_operator(self, node):
|
||||
self.dispatch_children(node)
|
||||
if self._in_conditional:
|
||||
self.score += len(node.getChildren()) - 1
|
||||
|
||||
visitAnd = _visit_logical_operator
|
||||
visitOr = _visit_logical_operator
|
||||
|
||||
def visitTryExcept(self, node):
|
||||
self.dispatch_children(node)
|
||||
self.score += len(node.handlers)
|
||||
#}}}
|
||||
|
||||
class ModuleComplexity:
|
||||
def __init__(self, code):
|
||||
self.code = code
|
||||
self.node = compiler.parse(code)
|
||||
|
||||
def compute_complexity(self):
|
||||
complexity = ASTComplexity(self.node)
|
||||
self.add_module_results(complexity, self.code)
|
||||
return complexity
|
||||
|
||||
def add_module_results(self, complexity, code_or_node):
|
||||
end_line = max(1, code_or_node.count('\n') + 1)
|
||||
complexity.results.add(
|
||||
ComplexityScore(name='<module>',
|
||||
type_='module',
|
||||
score=complexity.score,
|
||||
start_line=1,
|
||||
end_line=end_line))
|
||||
|
||||
class ComplexityResults:#{{{
|
||||
def __init__(self):
|
||||
self._scores = []
|
||||
|
||||
def add(self, score):
|
||||
self._scores.append(score)
|
||||
|
||||
def ordered_by_line(self):
|
||||
OBJECT_SORT_PRIORITY = ['module', 'function', 'class']
|
||||
def sort_key(score):
|
||||
return (score.start_line,
|
||||
OBJECT_SORT_PRIORITY.index(score.type_))
|
||||
return sorted(self._scores, key=sort_key)
|
||||
|
||||
def named(self, name):
|
||||
return [s for s in self._scores if s.name == name][0]
|
||||
|
||||
|
||||
class ComplexityScore:
|
||||
def __init__(self, name, type_, score, start_line, end_line):
|
||||
self.name = name
|
||||
self.type_ = type_
|
||||
self.score = score
|
||||
self.start_line = start_line
|
||||
self.end_line = end_line
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
'ComplexityScore(name=%s, score=%s, start_line=%s, end_line=%s)'
|
||||
% (repr(self.name),
|
||||
repr(self.score),
|
||||
repr(self.start_line),
|
||||
repr(self.end_line)))
|
||||
|
||||
|
||||
def complexity_name(complexity):
|
||||
if complexity > 14:
|
||||
return 'high_complexity'
|
||||
elif complexity > 7:
|
||||
return 'medium_complexity'
|
||||
else:
|
||||
return 'low_complexity'
|
||||
|
||||
|
||||
def show_complexity():
|
||||
current_file = vim.current.buffer.name
|
||||
try:
|
||||
scores = compute_scores_for(current_file)
|
||||
except (IndentationError, SyntaxError):
|
||||
return
|
||||
|
||||
old_complexities = get_old_complexities(current_file)
|
||||
new_complexities = compute_new_complexities(scores)
|
||||
line_changes = compute_line_changes(old_complexities, new_complexities)
|
||||
update_line_markers(line_changes)
|
||||
|
||||
|
||||
def compute_scores_for(filename=None, code=None):
|
||||
if filename:
|
||||
code = open(filename).read()
|
||||
scores = compute_code_complexity(code).results.ordered_by_line()
|
||||
return scores
|
||||
|
||||
|
||||
def get_old_complexities(current_file):
|
||||
lines = list_current_signs(current_file)
|
||||
|
||||
old_complexities = {}
|
||||
for line in lines:
|
||||
if '=' not in line:
|
||||
continue
|
||||
|
||||
tokens = line.split()
|
||||
variables = dict(token.split('=') for token in tokens)
|
||||
line = int(variables['line'])
|
||||
complexity = variables['name']
|
||||
old_complexities[line] = complexity
|
||||
|
||||
return old_complexities
|
||||
|
||||
|
||||
def list_current_signs(current_file):
|
||||
vim.command('redir => s:complexity_sign_list')
|
||||
vim.command('silent sign place file=%s' % current_file)
|
||||
vim.command('redir END')
|
||||
|
||||
sign_list = vim.eval('s:complexity_sign_list')
|
||||
lines = [line.strip() for line in sign_list.split('\n')]
|
||||
return lines
|
||||
|
||||
|
||||
def compute_line_changes(cached_complexities, new_scores):
|
||||
changes = {}
|
||||
for line, complexity in new_scores.iteritems():
|
||||
if complexity != cached_complexities.get(line, None):
|
||||
changes[line] = complexity
|
||||
|
||||
return changes
|
||||
|
||||
|
||||
def compute_new_complexities(scores):
|
||||
new_scores = {}
|
||||
for score in scores:
|
||||
for line in range(score.start_line, score.end_line + 1):
|
||||
new_scores[line] = complexity_name(score.score)
|
||||
return new_scores
|
||||
|
||||
|
||||
def update_line_markers(line_changes):
|
||||
filename = vim.current.buffer.name
|
||||
for line, complexity in line_changes.iteritems():
|
||||
vim.command(':sign unplace %i' % line)
|
||||
vim.command(':sign place %i line=%i name=%s file=%s' %
|
||||
(line, line, complexity, filename))#}}}
|
||||
|
||||
|
||||
def main():
|
||||
if sys.stdin.isatty() and len(sys.argv) < 2:
|
||||
print "Missing filename"
|
||||
return
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
file_name = sys.argv[1]
|
||||
content = None
|
||||
else:
|
||||
file_name = None
|
||||
content = sys.stdin.read()
|
||||
|
||||
try:
|
||||
for score in compute_scores_for(file_name, content):
|
||||
print score.start_line, score.end_line, score.score, score.type_
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 'vim' not in globals():
|
||||
main()
|
||||
|
||||
endpython
|
||||
|
||||
function! ShowComplexity()
|
||||
python << END
|
||||
show_complexity()
|
||||
END
|
||||
" no idea why it is needed to update colors each time
|
||||
" to actually see the colors
|
||||
hi low_complexity guifg=#004400 guibg=#004400
|
||||
hi medium_complexity guifg=#bbbb00 guibg=#bbbb00
|
||||
hi high_complexity guifg=#ff2222 guibg=#ff2222
|
||||
endfunction
|
||||
|
||||
hi SignColumn guifg=fg guibg=bg
|
||||
hi low_complexity guifg=#004400 guibg=#004400
|
||||
hi medium_complexity guifg=#bbbb00 guibg=#bbbb00
|
||||
hi high_complexity guifg=#ff2222 guibg=#ff2222
|
||||
sign define low_complexity text=XX texthl=low_complexity
|
||||
sign define medium_complexity text=XX texthl=medium_complexity
|
||||
sign define high_complexity text=XX texthl=high_complexity
|
||||
|
||||
autocmd! BufReadPost,BufWritePost,FileReadPost,FileWritePost *.py call ShowComplexity()
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import compiler
|
||||
from compiler.visitor import ASTVisitor
|
||||
|
||||
|
||||
class NodeVisitor(ASTVisitor):
|
||||
def __init__(self, code, stats=None, description=None):
|
||||
ASTVisitor.__init__(self)
|
||||
ast = compiler.parse(code)
|
||||
self.node_types = set()
|
||||
self.visit_node(ast.node)
|
||||
#for child in ast.getChildNodes():
|
||||
#compiler.walk(child, self, walker=self)
|
||||
all_types = set(line.strip()
|
||||
for line
|
||||
in file('python_ast_node_types.txt').readlines())
|
||||
self.untouched_nodes = sorted(all_types - self.node_types)
|
||||
|
||||
def visit_node(self, node):
|
||||
self.node_types.add(node.__class__.__name__)
|
||||
for child in node.getChildNodes():
|
||||
self.visit_node(child)
|
||||
|
||||
|
||||
visitor = NodeVisitor(file('everything.py').read())
|
||||
print 'Nodes not touched: %s' % visitor.untouched_nodes
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# This file aims to contain every Python syntax node. It's almost there.
|
||||
from __future__ import with_statement
|
||||
|
||||
a + a
|
||||
a and b
|
||||
a.x = a
|
||||
assert a
|
||||
a = a
|
||||
a, a = a
|
||||
[a, a] = a
|
||||
'a'
|
||||
a += a
|
||||
`a`
|
||||
a & a
|
||||
a | a
|
||||
a ^ a
|
||||
break
|
||||
a()
|
||||
class a: pass
|
||||
a < a
|
||||
continue
|
||||
@a
|
||||
def a(): pass
|
||||
a(a=a)
|
||||
{}
|
||||
a / a
|
||||
a[...]
|
||||
exec a
|
||||
a // a
|
||||
for a in a: pass
|
||||
from a import a
|
||||
(a for a in a if a)
|
||||
a.a
|
||||
global a
|
||||
if a: pass
|
||||
import a
|
||||
lambda: a
|
||||
a << a
|
||||
[]
|
||||
[a for a in a]
|
||||
[a for a in a if a]
|
||||
a % a
|
||||
a * a
|
||||
not a
|
||||
a or a
|
||||
a ** a
|
||||
raise a
|
||||
print a,
|
||||
print a
|
||||
return a
|
||||
a >> a
|
||||
a[a:a]
|
||||
a[a:a:a]
|
||||
a - a
|
||||
try:
|
||||
a
|
||||
except:
|
||||
a
|
||||
finally:
|
||||
a
|
||||
()
|
||||
+a
|
||||
-a
|
||||
while a: pass
|
||||
with a as a:
|
||||
pass
|
||||
yield a
|
||||
|
||||
# Thankst to Josh Lee (jleedev) for pointing this missing node out.
|
||||
~x
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
Add
|
||||
And
|
||||
AssAttr
|
||||
AssList
|
||||
AssName
|
||||
AssTuple
|
||||
Assert
|
||||
Assign
|
||||
AugAssign
|
||||
Backquote
|
||||
Bitand
|
||||
Bitor
|
||||
Bitxor
|
||||
Break
|
||||
CallFunc
|
||||
Class
|
||||
Compare
|
||||
Const
|
||||
Continue
|
||||
Decorators
|
||||
Dict
|
||||
Discard
|
||||
Div
|
||||
Ellipsis
|
||||
Expression
|
||||
Exec
|
||||
FloorDiv
|
||||
For
|
||||
From
|
||||
Function
|
||||
GenExpr
|
||||
GenExprFor
|
||||
GenExprIf
|
||||
GenExprInner
|
||||
Getattr
|
||||
Global
|
||||
If
|
||||
Import
|
||||
Invert
|
||||
Keyword
|
||||
Lambda
|
||||
LeftShift
|
||||
List
|
||||
ListComp
|
||||
ListCompFor
|
||||
ListCompIf
|
||||
Mod
|
||||
Module
|
||||
Mul
|
||||
Name
|
||||
Not
|
||||
Or
|
||||
Pass
|
||||
Power
|
||||
Print
|
||||
Printnl
|
||||
Raise
|
||||
Return
|
||||
RightShift
|
||||
Slice
|
||||
Sliceobj
|
||||
Stmt
|
||||
Sub
|
||||
Subscript
|
||||
TryExcept
|
||||
TryFinally
|
||||
Tuple
|
||||
UnaryAdd
|
||||
UnarySub
|
||||
While
|
||||
With
|
||||
Yield
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
import nose
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
nose_args = sys.argv + [r'-m',
|
||||
r'((?:^|[b_.-])(:?[Tt]est|When|describe|should|it))',
|
||||
r'--with-doctest',
|
||||
r'--doctest-extension=']
|
||||
nose.run(argv=nose_args)
|
||||
|
||||
Reference in New Issue
Block a user