Initial import.

This commit is contained in:
Kenneth Reitz
2010-11-07 05:14:40 -05:00
commit 2357f0ba6d
133 changed files with 32578 additions and 0 deletions
@@ -0,0 +1,4 @@
syntax: glob
*.pyc
envs
.ropeproject
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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)